Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	fix CI (#1884)
Browse files* fix CI
* add playwright as dev dep
* lint
* clean up spaces-config with community tools
* try to make mongo run?
- .env +1 -1
- .eslintrc.cjs +1 -0
- .github/workflows/lint-and-test.yml +1 -0
- Dockerfile +1 -1
- PRIVACY.md +0 -1
- package-lock.json +33 -0
- package.json +1 -0
- src/lib/components/NavMenu.svelte +0 -1
- src/lib/components/WelcomeModal.svelte +0 -1
- src/lib/components/chat/ChatWindow.svelte +3 -1
- src/lib/components/chat/MarkdownRenderer.svelte +1 -5
- src/lib/migrations/routines/06-trim-message-updates.ts +5 -1
- src/lib/server/api/routes/groups/misc.ts +0 -30
- src/lib/server/endpoints/openai/endpointOai.ts +2 -2
- src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +12 -5
- src/lib/server/models.ts +8 -7
- src/lib/server/router/arch.ts +2 -2
- src/lib/server/router/endpoint.ts +12 -6
- src/lib/server/textGeneration/reasoning.ts +3 -3
- src/lib/server/textGeneration/title.ts +3 -3
- src/lib/utils/marked.ts +17 -16
- src/styles/highlight-js.css +34 -34
    	
        .env
    CHANGED
    
    | @@ -4,7 +4,7 @@ | |
| 4 | 
             
            ### Models ###
         | 
| 5 | 
             
            # Models are sourced exclusively from an OpenAI-compatible base URL.
         | 
| 6 | 
             
            # Example: https://router.huggingface.co/v1
         | 
| 7 | 
            -
            OPENAI_BASE_URL=
         | 
| 8 |  | 
| 9 | 
             
            # Canonical auth token for any OpenAI-compatible provider
         | 
| 10 | 
             
            OPENAI_API_KEY=#your provider API key (works for HF router, OpenAI, LM Studio, etc.)
         | 
|  | |
| 4 | 
             
            ### Models ###
         | 
| 5 | 
             
            # Models are sourced exclusively from an OpenAI-compatible base URL.
         | 
| 6 | 
             
            # Example: https://router.huggingface.co/v1
         | 
| 7 | 
            +
            OPENAI_BASE_URL=https://router.huggingface.co/v1
         | 
| 8 |  | 
| 9 | 
             
            # Canonical auth token for any OpenAI-compatible provider
         | 
| 10 | 
             
            OPENAI_API_KEY=#your provider API key (works for HF router, OpenAI, LM Studio, etc.)
         | 
    	
        .eslintrc.cjs
    CHANGED
    
    | @@ -24,6 +24,7 @@ module.exports = { | |
| 24 | 
             
            		extraFileExtensions: [".svelte"],
         | 
| 25 | 
             
            	},
         | 
| 26 | 
             
            	rules: {
         | 
|  | |
| 27 | 
             
            		"require-yield": "off",
         | 
| 28 | 
             
            		"@typescript-eslint/no-explicit-any": "error",
         | 
| 29 | 
             
            		"@typescript-eslint/no-non-null-assertion": "error",
         | 
|  | |
| 24 | 
             
            		extraFileExtensions: [".svelte"],
         | 
| 25 | 
             
            	},
         | 
| 26 | 
             
            	rules: {
         | 
| 27 | 
            +
            		"no-empty": "off",
         | 
| 28 | 
             
            		"require-yield": "off",
         | 
| 29 | 
             
            		"@typescript-eslint/no-explicit-any": "error",
         | 
| 30 | 
             
            		"@typescript-eslint/no-non-null-assertion": "error",
         | 
    	
        .github/workflows/lint-and-test.yml
    CHANGED
    
    | @@ -39,6 +39,7 @@ jobs: | |
| 39 | 
             
                      cache: "npm"
         | 
| 40 | 
             
                  - run: |
         | 
| 41 | 
             
                      npm ci
         | 
|  | |
| 42 | 
             
                  - name: "Tests"
         | 
| 43 | 
             
                    run: |
         | 
| 44 | 
             
                      npm run test
         | 
|  | |
| 39 | 
             
                      cache: "npm"
         | 
| 40 | 
             
                  - run: |
         | 
| 41 | 
             
                      npm ci
         | 
| 42 | 
            +
                      npx playwright install
         | 
| 43 | 
             
                  - name: "Tests"
         | 
| 44 | 
             
                    run: |
         | 
| 45 | 
             
                      npm run test
         | 
    	
        Dockerfile
    CHANGED
    
    | @@ -21,7 +21,7 @@ RUN touch /app/.env.local | |
| 21 |  | 
| 22 | 
             
            USER root
         | 
| 23 | 
             
            RUN apt-get update
         | 
| 24 | 
            -
            RUN apt-get install -y libgomp1
         | 
| 25 |  | 
| 26 | 
             
            # ensure npm cache dir exists before adjusting ownership
         | 
| 27 | 
             
            RUN mkdir -p /home/user/.npm && chown -R 1000:1000 /home/user/.npm
         | 
|  | |
| 21 |  | 
| 22 | 
             
            USER root
         | 
| 23 | 
             
            RUN apt-get update
         | 
| 24 | 
            +
            RUN apt-get install -y libgomp1 libcurl4
         | 
| 25 |  | 
| 26 | 
             
            # ensure npm cache dir exists before adjusting ownership
         | 
| 27 | 
             
            RUN mkdir -p /home/user/.npm && chown -R 1000:1000 /home/user/.npm
         | 
    	
        PRIVACY.md
    CHANGED
    
    | @@ -26,7 +26,6 @@ Security and routing facts | |
| 26 |  | 
| 27 | 
             
            External providers are responsible for their own security and data handling. Please consult each provider’s respective security and privacy policies via the Inference Providers documentation linked above.
         | 
| 28 |  | 
| 29 | 
            -
             | 
| 30 | 
             
            ## Technical details
         | 
| 31 |  | 
| 32 | 
             
            [](https://github.com/huggingface/chat-ui)
         | 
|  | |
| 26 |  | 
| 27 | 
             
            External providers are responsible for their own security and data handling. Please consult each provider’s respective security and privacy policies via the Inference Providers documentation linked above.
         | 
| 28 |  | 
|  | |
| 29 | 
             
            ## Technical details
         | 
| 30 |  | 
| 31 | 
             
            [](https://github.com/huggingface/chat-ui)
         | 
    	
        package-lock.json
    CHANGED
    
    | @@ -74,6 +74,7 @@ | |
| 74 | 
             
            				"js-yaml": "^4.1.0",
         | 
| 75 | 
             
            				"minimist": "^1.2.8",
         | 
| 76 | 
             
            				"mongodb-memory-server": "^10.1.2",
         | 
|  | |
| 77 | 
             
            				"prettier": "^3.5.3",
         | 
| 78 | 
             
            				"prettier-plugin-svelte": "^3.2.6",
         | 
| 79 | 
             
            				"prettier-plugin-tailwindcss": "^0.6.11",
         | 
| @@ -7968,6 +7969,38 @@ | |
| 7968 | 
             
            			"dev": true,
         | 
| 7969 | 
             
            			"license": "MIT"
         | 
| 7970 | 
             
            		},
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 7971 | 
             
            		"node_modules/postcss": {
         | 
| 7972 | 
             
            			"version": "8.5.4",
         | 
| 7973 | 
             
            			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
         | 
|  | |
| 74 | 
             
            				"js-yaml": "^4.1.0",
         | 
| 75 | 
             
            				"minimist": "^1.2.8",
         | 
| 76 | 
             
            				"mongodb-memory-server": "^10.1.2",
         | 
| 77 | 
            +
            				"playwright": "^1.55.1",
         | 
| 78 | 
             
            				"prettier": "^3.5.3",
         | 
| 79 | 
             
            				"prettier-plugin-svelte": "^3.2.6",
         | 
| 80 | 
             
            				"prettier-plugin-tailwindcss": "^0.6.11",
         | 
|  | |
| 7969 | 
             
            			"dev": true,
         | 
| 7970 | 
             
            			"license": "MIT"
         | 
| 7971 | 
             
            		},
         | 
| 7972 | 
            +
            		"node_modules/playwright": {
         | 
| 7973 | 
            +
            			"version": "1.55.1",
         | 
| 7974 | 
            +
            			"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
         | 
| 7975 | 
            +
            			"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
         | 
| 7976 | 
            +
            			"devOptional": true,
         | 
| 7977 | 
            +
            			"license": "Apache-2.0",
         | 
| 7978 | 
            +
            			"dependencies": {
         | 
| 7979 | 
            +
            				"playwright-core": "1.55.1"
         | 
| 7980 | 
            +
            			},
         | 
| 7981 | 
            +
            			"bin": {
         | 
| 7982 | 
            +
            				"playwright": "cli.js"
         | 
| 7983 | 
            +
            			},
         | 
| 7984 | 
            +
            			"engines": {
         | 
| 7985 | 
            +
            				"node": ">=18"
         | 
| 7986 | 
            +
            			},
         | 
| 7987 | 
            +
            			"optionalDependencies": {
         | 
| 7988 | 
            +
            				"fsevents": "2.3.2"
         | 
| 7989 | 
            +
            			}
         | 
| 7990 | 
            +
            		},
         | 
| 7991 | 
            +
            		"node_modules/playwright-core": {
         | 
| 7992 | 
            +
            			"version": "1.55.1",
         | 
| 7993 | 
            +
            			"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
         | 
| 7994 | 
            +
            			"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
         | 
| 7995 | 
            +
            			"devOptional": true,
         | 
| 7996 | 
            +
            			"license": "Apache-2.0",
         | 
| 7997 | 
            +
            			"bin": {
         | 
| 7998 | 
            +
            				"playwright-core": "cli.js"
         | 
| 7999 | 
            +
            			},
         | 
| 8000 | 
            +
            			"engines": {
         | 
| 8001 | 
            +
            				"node": ">=18"
         | 
| 8002 | 
            +
            			}
         | 
| 8003 | 
            +
            		},
         | 
| 8004 | 
             
            		"node_modules/postcss": {
         | 
| 8005 | 
             
            			"version": "8.5.4",
         | 
| 8006 | 
             
            			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
         | 
    	
        package.json
    CHANGED
    
    | @@ -48,6 +48,7 @@ | |
| 48 | 
             
            		"js-yaml": "^4.1.0",
         | 
| 49 | 
             
            		"minimist": "^1.2.8",
         | 
| 50 | 
             
            		"mongodb-memory-server": "^10.1.2",
         | 
|  | |
| 51 | 
             
            		"prettier": "^3.5.3",
         | 
| 52 | 
             
            		"prettier-plugin-svelte": "^3.2.6",
         | 
| 53 | 
             
            		"prettier-plugin-tailwindcss": "^0.6.11",
         | 
|  | |
| 48 | 
             
            		"js-yaml": "^4.1.0",
         | 
| 49 | 
             
            		"minimist": "^1.2.8",
         | 
| 50 | 
             
            		"mongodb-memory-server": "^10.1.2",
         | 
| 51 | 
            +
            		"playwright": "^1.55.1",
         | 
| 52 | 
             
            		"prettier": "^3.5.3",
         | 
| 53 | 
             
            		"prettier-plugin-svelte": "^3.2.6",
         | 
| 54 | 
             
            		"prettier-plugin-tailwindcss": "^0.6.11",
         | 
    	
        src/lib/components/NavMenu.svelte
    CHANGED
    
    | @@ -42,7 +42,6 @@ | |
| 42 |  | 
| 43 | 
             
            	let {
         | 
| 44 | 
             
            		conversations = $bindable(),
         | 
| 45 | 
            -
            		canLogin,
         | 
| 46 | 
             
            		user,
         | 
| 47 | 
             
            		p = $bindable(0),
         | 
| 48 | 
             
            		ondeleteConversation,
         | 
|  | |
| 42 |  | 
| 43 | 
             
            	let {
         | 
| 44 | 
             
            		conversations = $bindable(),
         | 
|  | |
| 45 | 
             
            		user,
         | 
| 46 | 
             
            		p = $bindable(0),
         | 
| 47 | 
             
            		ondeleteConversation,
         | 
    	
        src/lib/components/WelcomeModal.svelte
    CHANGED
    
    | @@ -1,6 +1,5 @@ | |
| 1 | 
             
            <script lang="ts">
         | 
| 2 | 
             
            	import Modal from "$lib/components/Modal.svelte";
         | 
| 3 | 
            -
            	import Logo from "$lib/components/icons/Logo.svelte";
         | 
| 4 | 
             
            	import IconOmni from "$lib/components/icons/IconOmni.svelte";
         | 
| 5 | 
             
            	import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
         | 
| 6 |  | 
|  | |
| 1 | 
             
            <script lang="ts">
         | 
| 2 | 
             
            	import Modal from "$lib/components/Modal.svelte";
         | 
|  | |
| 3 | 
             
            	import IconOmni from "$lib/components/icons/IconOmni.svelte";
         | 
| 4 | 
             
            	import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
         | 
| 5 |  | 
    	
        src/lib/components/chat/ChatWindow.svelte
    CHANGED
    
    | @@ -298,7 +298,9 @@ | |
| 298 |  | 
| 299 | 
             
            					const blob = await response.blob();
         | 
| 300 | 
             
            					const name = attachment.src.split("/").pop() ?? "attachment";
         | 
| 301 | 
            -
            					loadedFiles.push( | 
|  | |
|  | |
| 302 | 
             
            				} catch (err) {
         | 
| 303 | 
             
            					console.error("Error loading attachment:", err);
         | 
| 304 | 
             
            				}
         | 
|  | |
| 298 |  | 
| 299 | 
             
            					const blob = await response.blob();
         | 
| 300 | 
             
            					const name = attachment.src.split("/").pop() ?? "attachment";
         | 
| 301 | 
            +
            					loadedFiles.push(
         | 
| 302 | 
            +
            						new File([blob], name, { type: blob.type || "application/octet-stream" })
         | 
| 303 | 
            +
            					);
         | 
| 304 | 
             
            				} catch (err) {
         | 
| 305 | 
             
            					console.error("Error loading attachment:", err);
         | 
| 306 | 
             
            				}
         | 
    	
        src/lib/components/chat/MarkdownRenderer.svelte
    CHANGED
    
    | @@ -86,10 +86,6 @@ | |
| 86 | 
             
            		<!-- eslint-disable-next-line svelte/no-at-html-tags -->
         | 
| 87 | 
             
            		{@html token.html}
         | 
| 88 | 
             
            	{:else if token.type === "code"}
         | 
| 89 | 
            -
            		<CodeBlock
         | 
| 90 | 
            -
            			code={token.code}
         | 
| 91 | 
            -
            			rawCode={token.rawCode}
         | 
| 92 | 
            -
            			loading={loading && !token.isClosed}
         | 
| 93 | 
            -
            		/>
         | 
| 94 | 
             
            	{/if}
         | 
| 95 | 
             
            {/each}
         | 
|  | |
| 86 | 
             
            		<!-- eslint-disable-next-line svelte/no-at-html-tags -->
         | 
| 87 | 
             
            		{@html token.html}
         | 
| 88 | 
             
            	{:else if token.type === "code"}
         | 
| 89 | 
            +
            		<CodeBlock code={token.code} rawCode={token.rawCode} loading={loading && !token.isClosed} />
         | 
|  | |
|  | |
|  | |
|  | |
| 90 | 
             
            	{/if}
         | 
| 91 | 
             
            {/each}
         | 
    	
        src/lib/migrations/routines/06-trim-message-updates.ts
    CHANGED
    
    | @@ -12,7 +12,11 @@ import { logger } from "$lib/server/logger"; | |
| 12 | 
             
            function convertMessageUpdate(message: Message, update: unknown): MessageUpdate | null {
         | 
| 13 | 
             
            	try {
         | 
| 14 | 
             
            		// Trim legacy web search updates entirely
         | 
| 15 | 
            -
            		if ( | 
|  | |
|  | |
|  | |
|  | |
| 16 | 
             
            			return null;
         | 
| 17 | 
             
            		}
         | 
| 18 |  | 
|  | |
| 12 | 
             
            function convertMessageUpdate(message: Message, update: unknown): MessageUpdate | null {
         | 
| 13 | 
             
            	try {
         | 
| 14 | 
             
            		// Trim legacy web search updates entirely
         | 
| 15 | 
            +
            		if (
         | 
| 16 | 
            +
            			typeof update === "object" &&
         | 
| 17 | 
            +
            			update !== null &&
         | 
| 18 | 
            +
            			(update as { type: string }).type === "webSearch"
         | 
| 19 | 
            +
            		) {
         | 
| 20 | 
             
            			return null;
         | 
| 21 | 
             
            		}
         | 
| 22 |  | 
    	
        src/lib/server/api/routes/groups/misc.ts
    CHANGED
    
    | @@ -63,36 +63,6 @@ export const misc = new Elysia() | |
| 63 | 
             
            			isAdmin: locals.isAdmin,
         | 
| 64 | 
             
            		} satisfies FeatureFlags;
         | 
| 65 | 
             
            	})
         | 
| 66 | 
            -
            	.get("/spaces-config", async ({ query }) => {
         | 
| 67 | 
            -
            		if (config.COMMUNITY_TOOLS !== "true") {
         | 
| 68 | 
            -
            			throw new Error("Community tools are not enabled");
         | 
| 69 | 
            -
            		}
         | 
| 70 | 
            -
             | 
| 71 | 
            -
            		const space = query.space;
         | 
| 72 | 
            -
             | 
| 73 | 
            -
            		if (!space) {
         | 
| 74 | 
            -
            			throw new Error("Missing space");
         | 
| 75 | 
            -
            		}
         | 
| 76 | 
            -
             | 
| 77 | 
            -
            		// Extract namespace from space URL or use as-is if it's already in namespace format
         | 
| 78 | 
            -
            		let namespace = null;
         | 
| 79 | 
            -
            		if (space.startsWith("https://huggingface.co/spaces/")) {
         | 
| 80 | 
            -
            			namespace = space.split("/").slice(-2).join("/");
         | 
| 81 | 
            -
            		} else if (space.match(/^[^/]+\/[^/]+$/)) {
         | 
| 82 | 
            -
            			namespace = space;
         | 
| 83 | 
            -
            		}
         | 
| 84 | 
            -
             | 
| 85 | 
            -
            		if (!namespace) {
         | 
| 86 | 
            -
            			throw new Error("Invalid space name. Specify a namespace or a full URL on huggingface.co.");
         | 
| 87 | 
            -
            		}
         | 
| 88 | 
            -
             | 
| 89 | 
            -
            		try {
         | 
| 90 | 
            -
            			const api = await (await Client.connect(namespace)).view_api();
         | 
| 91 | 
            -
            			return api as ApiReturnType;
         | 
| 92 | 
            -
            		} catch (e) {
         | 
| 93 | 
            -
            			throw new Error("Error fetching space API. Is the name correct?");
         | 
| 94 | 
            -
            		}
         | 
| 95 | 
            -
            	})
         | 
| 96 | 
             
            	.get("/export", async ({ locals }) => {
         | 
| 97 | 
             
            		if (!locals.user) {
         | 
| 98 | 
             
            			throw new Error("Not logged in");
         | 
|  | |
| 63 | 
             
            			isAdmin: locals.isAdmin,
         | 
| 64 | 
             
            		} satisfies FeatureFlags;
         | 
| 65 | 
             
            	})
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 66 | 
             
            	.get("/export", async ({ locals }) => {
         | 
| 67 | 
             
            		if (!locals.user) {
         | 
| 68 | 
             
            			throw new Error("Not logged in");
         | 
    	
        src/lib/server/endpoints/openai/endpointOai.ts
    CHANGED
    
    | @@ -123,7 +123,7 @@ export async function endpointOai( | |
| 123 | 
             
            				stop: parameters?.stop,
         | 
| 124 | 
             
            				temperature: parameters?.temperature,
         | 
| 125 | 
             
            				top_p: parameters?.top_p,
         | 
| 126 | 
            -
             | 
| 127 | 
             
            				presence_penalty: parameters?.presence_penalty,
         | 
| 128 | 
             
            			};
         | 
| 129 |  | 
| @@ -173,7 +173,7 @@ export async function endpointOai( | |
| 173 | 
             
            				stop: parameters?.stop,
         | 
| 174 | 
             
            				temperature: parameters?.temperature,
         | 
| 175 | 
             
            				top_p: parameters?.top_p,
         | 
| 176 | 
            -
             | 
| 177 | 
             
            				presence_penalty: parameters?.presence_penalty,
         | 
| 178 | 
             
            			};
         | 
| 179 |  | 
|  | |
| 123 | 
             
            				stop: parameters?.stop,
         | 
| 124 | 
             
            				temperature: parameters?.temperature,
         | 
| 125 | 
             
            				top_p: parameters?.top_p,
         | 
| 126 | 
            +
            				frequency_penalty: parameters?.frequency_penalty,
         | 
| 127 | 
             
            				presence_penalty: parameters?.presence_penalty,
         | 
| 128 | 
             
            			};
         | 
| 129 |  | 
|  | |
| 173 | 
             
            				stop: parameters?.stop,
         | 
| 174 | 
             
            				temperature: parameters?.temperature,
         | 
| 175 | 
             
            				top_p: parameters?.top_p,
         | 
| 176 | 
            +
            				frequency_penalty: parameters?.frequency_penalty,
         | 
| 177 | 
             
            				presence_penalty: parameters?.presence_penalty,
         | 
| 178 | 
             
            			};
         | 
| 179 |  | 
    	
        src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts
    CHANGED
    
    | @@ -16,9 +16,10 @@ export async function* openAIChatToTextGenerationStream( | |
| 16 | 
             
            	let thinkOpen = false;
         | 
| 17 |  | 
| 18 | 
             
            	for await (const completion of completionStream) {
         | 
|  | |
| 19 | 
             
            		// Check if this chunk contains router metadata (first chunk from llm-router)
         | 
| 20 | 
            -
            		if (!metadataYielded &&  | 
| 21 | 
            -
            			const metadata =  | 
| 22 | 
             
            			yield {
         | 
| 23 | 
             
            				token: {
         | 
| 24 | 
             
            					id: tokenId++,
         | 
| @@ -44,8 +45,11 @@ export async function* openAIChatToTextGenerationStream( | |
| 44 | 
             
            			}
         | 
| 45 | 
             
            		}
         | 
| 46 | 
             
            		const { choices } = completion;
         | 
| 47 | 
            -
            		const delta:  | 
| 48 | 
            -
             | 
|  | |
|  | |
|  | |
| 49 | 
             
            		const reasoning: string =
         | 
| 50 | 
             
            			typeof delta?.reasoning === "string"
         | 
| 51 | 
             
            				? (delta.reasoning as string)
         | 
| @@ -158,7 +162,10 @@ export async function* openAIChatToTextGenerationSingle( | |
| 158 | 
             
            	completion: OpenAI.Chat.Completions.ChatCompletion,
         | 
| 159 | 
             
            	getRouterMetadata?: () => { route?: string; model?: string }
         | 
| 160 | 
             
            ) {
         | 
| 161 | 
            -
            	const message:  | 
|  | |
|  | |
|  | |
| 162 | 
             
            	let content: string = message?.content || "";
         | 
| 163 | 
             
            	// Provider-dependent reasoning shapes (non-streaming)
         | 
| 164 | 
             
            	const r: string =
         | 
|  | |
| 16 | 
             
            	let thinkOpen = false;
         | 
| 17 |  | 
| 18 | 
             
            	for await (const completion of completionStream) {
         | 
| 19 | 
            +
            		const retyped = completion as { "x-router-metadata"?: { route: string; model: string } };
         | 
| 20 | 
             
            		// Check if this chunk contains router metadata (first chunk from llm-router)
         | 
| 21 | 
            +
            		if (!metadataYielded && retyped["x-router-metadata"]) {
         | 
| 22 | 
            +
            			const metadata = retyped["x-router-metadata"];
         | 
| 23 | 
             
            			yield {
         | 
| 24 | 
             
            				token: {
         | 
| 25 | 
             
            					id: tokenId++,
         | 
|  | |
| 45 | 
             
            			}
         | 
| 46 | 
             
            		}
         | 
| 47 | 
             
            		const { choices } = completion;
         | 
| 48 | 
            +
            		const delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
         | 
| 49 | 
            +
            			reasoning?: string;
         | 
| 50 | 
            +
            			reasoning_content?: string;
         | 
| 51 | 
            +
            		} = choices?.[0]?.delta ?? {};
         | 
| 52 | 
            +
            		const content: string = delta.content ?? "";
         | 
| 53 | 
             
            		const reasoning: string =
         | 
| 54 | 
             
            			typeof delta?.reasoning === "string"
         | 
| 55 | 
             
            				? (delta.reasoning as string)
         | 
|  | |
| 162 | 
             
            	completion: OpenAI.Chat.Completions.ChatCompletion,
         | 
| 163 | 
             
            	getRouterMetadata?: () => { route?: string; model?: string }
         | 
| 164 | 
             
            ) {
         | 
| 165 | 
            +
            	const message: NonNullable<OpenAI.Chat.Completions.ChatCompletion.Choice>["message"] & {
         | 
| 166 | 
            +
            		reasoning?: string;
         | 
| 167 | 
            +
            		reasoning_content?: string;
         | 
| 168 | 
            +
            	} = completion.choices?.[0]?.message ?? {};
         | 
| 169 | 
             
            	let content: string = message?.content || "";
         | 
| 170 | 
             
            	// Provider-dependent reasoning shapes (non-streaming)
         | 
| 171 | 
             
            	const r: string =
         | 
    	
        src/lib/server/models.ts
    CHANGED
    
    | @@ -64,7 +64,7 @@ const modelConfig = z.object({ | |
| 64 | 
             
            			stop: z.array(z.string()).optional(),
         | 
| 65 | 
             
            			top_p: z.number().positive().optional(),
         | 
| 66 | 
             
            			top_k: z.number().positive().optional(),
         | 
| 67 | 
            -
             | 
| 68 | 
             
            			presence_penalty: z.number().min(-2).max(2).optional(),
         | 
| 69 | 
             
            		})
         | 
| 70 | 
             
            		.passthrough()
         | 
| @@ -220,6 +220,8 @@ if (modelOverrides.length) { | |
| 220 | 
             
            		if (!override) return model;
         | 
| 221 |  | 
| 222 | 
             
            		const { id, name, ...rest } = override;
         | 
|  | |
|  | |
| 223 |  | 
| 224 | 
             
            		return {
         | 
| 225 | 
             
            			...model,
         | 
| @@ -291,7 +293,7 @@ const routerAliasId = (config.PUBLIC_LLM_ROUTER_ALIAS_ID || "omni").trim() || "o | |
| 291 | 
             
            const routerMultimodalEnabled =
         | 
| 292 | 
             
            	(config.LLM_ROUTER_ENABLE_MULTIMODAL || "").toLowerCase() === "true";
         | 
| 293 |  | 
| 294 | 
            -
            let decorated = builtModels as  | 
| 295 |  | 
| 296 | 
             
            if (archBase) {
         | 
| 297 | 
             
            	// Build a minimal model config for the alias
         | 
| @@ -304,12 +306,12 @@ if (archBase) { | |
| 304 | 
             
            		endpoints: [
         | 
| 305 | 
             
            			{
         | 
| 306 | 
             
            				type: "openai" as const,
         | 
| 307 | 
            -
            				baseURL: openaiBaseUrl | 
| 308 | 
             
            			},
         | 
| 309 | 
             
            		],
         | 
| 310 | 
             
            		// Keep the alias visible
         | 
| 311 | 
             
            		unlisted: false,
         | 
| 312 | 
            -
            	} as  | 
| 313 |  | 
| 314 | 
             
            	if (routerMultimodalEnabled) {
         | 
| 315 | 
             
            		aliasRaw.multimodal = true;
         | 
| @@ -318,13 +320,12 @@ if (archBase) { | |
| 318 |  | 
| 319 | 
             
            	const aliasBase = await processModel(aliasRaw);
         | 
| 320 | 
             
            	// Create a self-referential ProcessedModel for the router endpoint
         | 
| 321 | 
            -
            	 | 
| 322 | 
            -
            	aliasModel = {
         | 
| 323 | 
             
            		...aliasBase,
         | 
| 324 | 
             
            		isRouter: true,
         | 
| 325 | 
             
            		// getEndpoint uses the router wrapper regardless of the endpoints array
         | 
| 326 | 
             
            		getEndpoint: async (): Promise<Endpoint> => makeRouterEndpoint(aliasModel),
         | 
| 327 | 
            -
            	};
         | 
| 328 |  | 
| 329 | 
             
            	// Put alias first
         | 
| 330 | 
             
            	decorated = [aliasModel, ...decorated];
         | 
|  | |
| 64 | 
             
            			stop: z.array(z.string()).optional(),
         | 
| 65 | 
             
            			top_p: z.number().positive().optional(),
         | 
| 66 | 
             
            			top_k: z.number().positive().optional(),
         | 
| 67 | 
            +
            			frequency_penalty: z.number().min(-2).max(2).optional(),
         | 
| 68 | 
             
            			presence_penalty: z.number().min(-2).max(2).optional(),
         | 
| 69 | 
             
            		})
         | 
| 70 | 
             
            		.passthrough()
         | 
|  | |
| 220 | 
             
            		if (!override) return model;
         | 
| 221 |  | 
| 222 | 
             
            		const { id, name, ...rest } = override;
         | 
| 223 | 
            +
            		void id;
         | 
| 224 | 
            +
            		void name;
         | 
| 225 |  | 
| 226 | 
             
            		return {
         | 
| 227 | 
             
            			...model,
         | 
|  | |
| 293 | 
             
            const routerMultimodalEnabled =
         | 
| 294 | 
             
            	(config.LLM_ROUTER_ENABLE_MULTIMODAL || "").toLowerCase() === "true";
         | 
| 295 |  | 
| 296 | 
            +
            let decorated = builtModels as ProcessedModel[];
         | 
| 297 |  | 
| 298 | 
             
            if (archBase) {
         | 
| 299 | 
             
            	// Build a minimal model config for the alias
         | 
|  | |
| 306 | 
             
            		endpoints: [
         | 
| 307 | 
             
            			{
         | 
| 308 | 
             
            				type: "openai" as const,
         | 
| 309 | 
            +
            				baseURL: openaiBaseUrl,
         | 
| 310 | 
             
            			},
         | 
| 311 | 
             
            		],
         | 
| 312 | 
             
            		// Keep the alias visible
         | 
| 313 | 
             
            		unlisted: false,
         | 
| 314 | 
            +
            	} as ProcessedModel;
         | 
| 315 |  | 
| 316 | 
             
            	if (routerMultimodalEnabled) {
         | 
| 317 | 
             
            		aliasRaw.multimodal = true;
         | 
|  | |
| 320 |  | 
| 321 | 
             
            	const aliasBase = await processModel(aliasRaw);
         | 
| 322 | 
             
            	// Create a self-referential ProcessedModel for the router endpoint
         | 
| 323 | 
            +
            	const aliasModel: ProcessedModel = {
         | 
|  | |
| 324 | 
             
            		...aliasBase,
         | 
| 325 | 
             
            		isRouter: true,
         | 
| 326 | 
             
            		// getEndpoint uses the router wrapper regardless of the endpoints array
         | 
| 327 | 
             
            		getEndpoint: async (): Promise<Endpoint> => makeRouterEndpoint(aliasModel),
         | 
| 328 | 
            +
            	} as ProcessedModel;
         | 
| 329 |  | 
| 330 | 
             
            	// Put alias first
         | 
| 331 | 
             
            	decorated = [aliasModel, ...decorated];
         | 
    	
        src/lib/server/router/arch.ts
    CHANGED
    
    | @@ -106,7 +106,7 @@ export async function archSelectRoute( | |
| 106 | 
             
            		});
         | 
| 107 | 
             
            		clearTimeout(to);
         | 
| 108 | 
             
            		if (!resp.ok) throw new Error(`arch-router ${resp.status}`);
         | 
| 109 | 
            -
            		const data:  | 
| 110 | 
             
            		const text = (data?.choices?.[0]?.message?.content ?? "").toString().trim();
         | 
| 111 | 
             
            		const raw = parseRouteName(text);
         | 
| 112 |  | 
| @@ -114,7 +114,7 @@ export async function archSelectRoute( | |
| 114 | 
             
            		const chosen = raw === "other" ? other : raw || "casual_conversation";
         | 
| 115 | 
             
            		const exists = routes.some((r) => r.name === chosen);
         | 
| 116 | 
             
            		return { routeName: exists ? chosen : "casual_conversation" };
         | 
| 117 | 
            -
            	} catch (e | 
| 118 | 
             
            		clearTimeout(to);
         | 
| 119 | 
             
            		logger.warn({ err: String(e), traceId }, "arch router selection failed");
         | 
| 120 | 
             
            		return { routeName: "arch_router_failure" };
         | 
|  | |
| 106 | 
             
            		});
         | 
| 107 | 
             
            		clearTimeout(to);
         | 
| 108 | 
             
            		if (!resp.ok) throw new Error(`arch-router ${resp.status}`);
         | 
| 109 | 
            +
            		const data: { choices: { message: { content: string } }[] } = await resp.json();
         | 
| 110 | 
             
            		const text = (data?.choices?.[0]?.message?.content ?? "").toString().trim();
         | 
| 111 | 
             
            		const raw = parseRouteName(text);
         | 
| 112 |  | 
|  | |
| 114 | 
             
            		const chosen = raw === "other" ? other : raw || "casual_conversation";
         | 
| 115 | 
             
            		const exists = routes.some((r) => r.name === chosen);
         | 
| 116 | 
             
            		return { routeName: exists ? chosen : "casual_conversation" };
         | 
| 117 | 
            +
            	} catch (e) {
         | 
| 118 | 
             
            		clearTimeout(to);
         | 
| 119 | 
             
            		logger.warn({ err: String(e), traceId }, "arch router selection failed");
         | 
| 120 | 
             
            		return { routeName: "arch_router_failure" };
         | 
    	
        src/lib/server/router/endpoint.ts
    CHANGED
    
    | @@ -1,4 +1,9 @@ | |
| 1 | 
            -
            import type { | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 2 | 
             
            import endpoints from "../endpoints/endpoints";
         | 
| 3 | 
             
            import type { ProcessedModel } from "../models";
         | 
| 4 | 
             
            import { config } from "$lib/server/config";
         | 
| @@ -17,6 +22,7 @@ function stripReasoningBlocks(text: string): string { | |
| 17 |  | 
| 18 | 
             
            function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
         | 
| 19 | 
             
            	const { reasoning: _reasoning, ...rest } = message;
         | 
|  | |
| 20 | 
             
            	const content =
         | 
| 21 | 
             
            		typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
         | 
| 22 | 
             
            	return {
         | 
| @@ -47,7 +53,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E | |
| 47 | 
             
            			let modelForCall: ProcessedModel | undefined;
         | 
| 48 | 
             
            			try {
         | 
| 49 | 
             
            				const mod = await import("../models");
         | 
| 50 | 
            -
            				const all = (mod as  | 
| 51 | 
             
            				modelForCall = all?.find((m) => m.id === candidateModelId || m.name === candidateModelId);
         | 
| 52 | 
             
            			} catch (e) {
         | 
| 53 | 
             
            				logger.warn({ err: String(e) }, "[router] failed to load models for candidate lookup");
         | 
| @@ -75,7 +81,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E | |
| 75 |  | 
| 76 | 
             
            		// Yield router metadata for immediate UI display, using the actual candidate
         | 
| 77 | 
             
            		async function* metadataThenStream(
         | 
| 78 | 
            -
            			gen: AsyncGenerator< | 
| 79 | 
             
            			actualModel: string,
         | 
| 80 | 
             
            			selectedRoute: string
         | 
| 81 | 
             
            		) {
         | 
| @@ -84,14 +90,14 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E | |
| 84 | 
             
            				generated_text: null,
         | 
| 85 | 
             
            				details: null,
         | 
| 86 | 
             
            				routerMetadata: { route: selectedRoute, model: actualModel },
         | 
| 87 | 
            -
            			} | 
| 88 | 
             
            			for await (const ev of gen) yield ev;
         | 
| 89 | 
             
            		}
         | 
| 90 |  | 
| 91 | 
             
            		async function findFirstMultimodalCandidateId(): Promise<string | undefined> {
         | 
| 92 | 
             
            			try {
         | 
| 93 | 
             
            				const mod = await import("../models");
         | 
| 94 | 
            -
            				const all = (mod as  | 
| 95 | 
             
            				const first = all?.find((m) => !m.isRouter && m.multimodal);
         | 
| 96 | 
             
            				return first?.id ?? first?.name;
         | 
| 97 | 
             
            			} catch (e) {
         | 
| @@ -132,7 +138,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E | |
| 132 | 
             
            		const fallbackModel = config.LLM_ROUTER_FALLBACK_MODEL || routerModel.id;
         | 
| 133 | 
             
            		const { candidates } = resolveRouteModels(routeName, routes, fallbackModel);
         | 
| 134 |  | 
| 135 | 
            -
            		let lastErr:  | 
| 136 | 
             
            		for (const candidate of candidates) {
         | 
| 137 | 
             
            			try {
         | 
| 138 | 
             
            				logger.info({ route: routeName, model: candidate }, "[router] trying candidate");
         | 
|  | |
| 1 | 
            +
            import type {
         | 
| 2 | 
            +
            	Endpoint,
         | 
| 3 | 
            +
            	EndpointParameters,
         | 
| 4 | 
            +
            	EndpointMessage,
         | 
| 5 | 
            +
            	TextGenerationStreamOutputSimplified,
         | 
| 6 | 
            +
            } from "../endpoints/endpoints";
         | 
| 7 | 
             
            import endpoints from "../endpoints/endpoints";
         | 
| 8 | 
             
            import type { ProcessedModel } from "../models";
         | 
| 9 | 
             
            import { config } from "$lib/server/config";
         | 
|  | |
| 22 |  | 
| 23 | 
             
            function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
         | 
| 24 | 
             
            	const { reasoning: _reasoning, ...rest } = message;
         | 
| 25 | 
            +
            	void _reasoning;
         | 
| 26 | 
             
            	const content =
         | 
| 27 | 
             
            		typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
         | 
| 28 | 
             
            	return {
         | 
|  | |
| 53 | 
             
            			let modelForCall: ProcessedModel | undefined;
         | 
| 54 | 
             
            			try {
         | 
| 55 | 
             
            				const mod = await import("../models");
         | 
| 56 | 
            +
            				const all = (mod as { models: ProcessedModel[] }).models;
         | 
| 57 | 
             
            				modelForCall = all?.find((m) => m.id === candidateModelId || m.name === candidateModelId);
         | 
| 58 | 
             
            			} catch (e) {
         | 
| 59 | 
             
            				logger.warn({ err: String(e) }, "[router] failed to load models for candidate lookup");
         | 
|  | |
| 81 |  | 
| 82 | 
             
            		// Yield router metadata for immediate UI display, using the actual candidate
         | 
| 83 | 
             
            		async function* metadataThenStream(
         | 
| 84 | 
            +
            			gen: AsyncGenerator<TextGenerationStreamOutputSimplified>,
         | 
| 85 | 
             
            			actualModel: string,
         | 
| 86 | 
             
            			selectedRoute: string
         | 
| 87 | 
             
            		) {
         | 
|  | |
| 90 | 
             
            				generated_text: null,
         | 
| 91 | 
             
            				details: null,
         | 
| 92 | 
             
            				routerMetadata: { route: selectedRoute, model: actualModel },
         | 
| 93 | 
            +
            			};
         | 
| 94 | 
             
            			for await (const ev of gen) yield ev;
         | 
| 95 | 
             
            		}
         | 
| 96 |  | 
| 97 | 
             
            		async function findFirstMultimodalCandidateId(): Promise<string | undefined> {
         | 
| 98 | 
             
            			try {
         | 
| 99 | 
             
            				const mod = await import("../models");
         | 
| 100 | 
            +
            				const all = (mod as { models: ProcessedModel[] }).models;
         | 
| 101 | 
             
            				const first = all?.find((m) => !m.isRouter && m.multimodal);
         | 
| 102 | 
             
            				return first?.id ?? first?.name;
         | 
| 103 | 
             
            			} catch (e) {
         | 
|  | |
| 138 | 
             
            		const fallbackModel = config.LLM_ROUTER_FALLBACK_MODEL || routerModel.id;
         | 
| 139 | 
             
            		const { candidates } = resolveRouteModels(routeName, routes, fallbackModel);
         | 
| 140 |  | 
| 141 | 
            +
            		let lastErr: unknown = undefined;
         | 
| 142 | 
             
            		for (const candidate of candidates) {
         | 
| 143 | 
             
            			try {
         | 
| 144 | 
             
            				logger.info({ route: routeName, model: candidate }, "[router] trying candidate");
         | 
    	
        src/lib/server/textGeneration/reasoning.ts
    CHANGED
    
    | @@ -21,9 +21,9 @@ export async function generateSummaryOfReasoning( | |
| 21 | 
             
            				preprompt: `You are tasked with summarizing the latest reasoning steps. Never describe results of the reasoning, only the process. Remain vague in your summary.
         | 
| 22 | 
             
                        The text might be incomplete, try your best to summarize it in one very short sentence, starting with a gerund and ending with three points. 
         | 
| 23 | 
             
                        Example: "Thinking about life...", "Summarizing the results...", "Processing the input..."`,
         | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
             
            				modelId,
         | 
| 28 | 
             
            			})
         | 
| 29 | 
             
            		);
         | 
|  | |
| 21 | 
             
            				preprompt: `You are tasked with summarizing the latest reasoning steps. Never describe results of the reasoning, only the process. Remain vague in your summary.
         | 
| 22 | 
             
                        The text might be incomplete, try your best to summarize it in one very short sentence, starting with a gerund and ending with three points. 
         | 
| 23 | 
             
                        Example: "Thinking about life...", "Summarizing the results...", "Processing the input..."`,
         | 
| 24 | 
            +
            				generateSettings: {
         | 
| 25 | 
            +
            					max_tokens: 50,
         | 
| 26 | 
            +
            				},
         | 
| 27 | 
             
            				modelId,
         | 
| 28 | 
             
            			})
         | 
| 29 | 
             
            		);
         | 
    	
        src/lib/server/textGeneration/title.ts
    CHANGED
    
    | @@ -44,9 +44,9 @@ Do not answer the question. | |
| 44 | 
             
            Do not include the word prompt into your response.
         | 
| 45 | 
             
            Do not include quotes, emojis, hashtags or trailing punctuation.
         | 
| 46 | 
             
            Return ONLY the title text.`,
         | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
             
            			modelId,
         | 
| 51 | 
             
            		})
         | 
| 52 | 
             
            	)
         | 
|  | |
| 44 | 
             
            Do not include the word prompt into your response.
         | 
| 45 | 
             
            Do not include quotes, emojis, hashtags or trailing punctuation.
         | 
| 46 | 
             
            Return ONLY the title text.`,
         | 
| 47 | 
            +
            			generateSettings: {
         | 
| 48 | 
            +
            				max_tokens: 30,
         | 
| 49 | 
            +
            			},
         | 
| 50 | 
             
            			modelId,
         | 
| 51 | 
             
            		})
         | 
| 52 | 
             
            	)
         | 
    	
        src/lib/utils/marked.ts
    CHANGED
    
    | @@ -174,6 +174,7 @@ function createMarkedInstance(sources: SimpleSource[]): Marked { | |
| 174 | 
             
            }
         | 
| 175 | 
             
            function isFencedBlockClosed(raw?: string): boolean {
         | 
| 176 | 
             
            	if (!raw) return true;
         | 
|  | |
| 177 | 
             
            	const trimmed = raw.replace(/[\s\u0000]+$/, "");
         | 
| 178 | 
             
            	const openingFenceMatch = trimmed.match(/^([`~]{3,})/);
         | 
| 179 | 
             
            	if (!openingFenceMatch) {
         | 
| @@ -203,14 +204,14 @@ export async function processTokens(content: string, sources: SimpleSource[]): P | |
| 203 |  | 
| 204 | 
             
            	const processedTokens = await Promise.all(
         | 
| 205 | 
             
            		tokens.map(async (token) => {
         | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
             
            			} else {
         | 
| 215 | 
             
            				return {
         | 
| 216 | 
             
            					type: "text" as const,
         | 
| @@ -227,14 +228,14 @@ export function processTokensSync(content: string, sources: SimpleSource[]): Tok | |
| 227 | 
             
            	const marked = createMarkedInstance(sources);
         | 
| 228 | 
             
            	const tokens = marked.lexer(content);
         | 
| 229 | 
             
            	return tokens.map((token) => {
         | 
| 230 | 
            -
             | 
| 231 | 
            -
             | 
| 232 | 
            -
             | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
             
            		}
         | 
| 239 | 
             
            		return { type: "text" as const, html: marked.parse(token.raw) };
         | 
| 240 | 
             
            	});
         | 
|  | |
| 174 | 
             
            }
         | 
| 175 | 
             
            function isFencedBlockClosed(raw?: string): boolean {
         | 
| 176 | 
             
            	if (!raw) return true;
         | 
| 177 | 
            +
            	/* eslint-disable-next-line no-control-regex */
         | 
| 178 | 
             
            	const trimmed = raw.replace(/[\s\u0000]+$/, "");
         | 
| 179 | 
             
            	const openingFenceMatch = trimmed.match(/^([`~]{3,})/);
         | 
| 180 | 
             
            	if (!openingFenceMatch) {
         | 
|  | |
| 204 |  | 
| 205 | 
             
            	const processedTokens = await Promise.all(
         | 
| 206 | 
             
            		tokens.map(async (token) => {
         | 
| 207 | 
            +
            			if (token.type === "code") {
         | 
| 208 | 
            +
            				return {
         | 
| 209 | 
            +
            					type: "code" as const,
         | 
| 210 | 
            +
            					lang: token.lang,
         | 
| 211 | 
            +
            					code: hljs.highlightAuto(token.text, hljs.getLanguage(token.lang)?.aliases).value,
         | 
| 212 | 
            +
            					rawCode: token.text,
         | 
| 213 | 
            +
            					isClosed: isFencedBlockClosed(token.raw ?? ""),
         | 
| 214 | 
            +
            				};
         | 
| 215 | 
             
            			} else {
         | 
| 216 | 
             
            				return {
         | 
| 217 | 
             
            					type: "text" as const,
         | 
|  | |
| 228 | 
             
            	const marked = createMarkedInstance(sources);
         | 
| 229 | 
             
            	const tokens = marked.lexer(content);
         | 
| 230 | 
             
            	return tokens.map((token) => {
         | 
| 231 | 
            +
            		if (token.type === "code") {
         | 
| 232 | 
            +
            			return {
         | 
| 233 | 
            +
            				type: "code" as const,
         | 
| 234 | 
            +
            				lang: token.lang,
         | 
| 235 | 
            +
            				code: hljs.highlightAuto(token.text, hljs.getLanguage(token.lang)?.aliases).value,
         | 
| 236 | 
            +
            				rawCode: token.text,
         | 
| 237 | 
            +
            				isClosed: isFencedBlockClosed(token.raw ?? ""),
         | 
| 238 | 
            +
            			};
         | 
| 239 | 
             
            		}
         | 
| 240 | 
             
            		return { type: "text" as const, html: marked.parse(token.raw) };
         | 
| 241 | 
             
            	});
         | 
    	
        src/styles/highlight-js.css
    CHANGED
    
    | @@ -20,23 +20,23 @@ hue-6-2: #c18401 | |
| 20 | 
             
            */
         | 
| 21 |  | 
| 22 | 
             
            .hljs {
         | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
             
            }
         | 
| 29 |  | 
| 30 | 
             
            .hljs-comment,
         | 
| 31 | 
             
            .hljs-quote {
         | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
             
            }
         | 
| 35 |  | 
| 36 | 
             
            .hljs-doctag,
         | 
| 37 | 
             
            .hljs-keyword,
         | 
| 38 | 
             
            .hljs-formula {
         | 
| 39 | 
            -
             | 
| 40 | 
             
            }
         | 
| 41 |  | 
| 42 | 
             
            .hljs-section,
         | 
| @@ -44,11 +44,11 @@ hue-6-2: #c18401 | |
| 44 | 
             
            .hljs-selector-tag,
         | 
| 45 | 
             
            .hljs-deletion,
         | 
| 46 | 
             
            .hljs-subst {
         | 
| 47 | 
            -
             | 
| 48 | 
             
            }
         | 
| 49 |  | 
| 50 | 
             
            .hljs-literal {
         | 
| 51 | 
            -
             | 
| 52 | 
             
            }
         | 
| 53 |  | 
| 54 | 
             
            .hljs-string,
         | 
| @@ -56,12 +56,12 @@ hue-6-2: #c18401 | |
| 56 | 
             
            .hljs-addition,
         | 
| 57 | 
             
            .hljs-attribute,
         | 
| 58 | 
             
            .hljs-meta-string {
         | 
| 59 | 
            -
             | 
| 60 | 
             
            }
         | 
| 61 |  | 
| 62 | 
             
            .hljs-built_in,
         | 
| 63 | 
             
            .hljs-class .hljs-title {
         | 
| 64 | 
            -
             | 
| 65 | 
             
            }
         | 
| 66 |  | 
| 67 | 
             
            .hljs-attr,
         | 
| @@ -72,7 +72,7 @@ hue-6-2: #c18401 | |
| 72 | 
             
            .hljs-selector-attr,
         | 
| 73 | 
             
            .hljs-selector-pseudo,
         | 
| 74 | 
             
            .hljs-number {
         | 
| 75 | 
            -
             | 
| 76 | 
             
            }
         | 
| 77 |  | 
| 78 | 
             
            .hljs-symbol,
         | 
| @@ -81,19 +81,19 @@ hue-6-2: #c18401 | |
| 81 | 
             
            .hljs-meta,
         | 
| 82 | 
             
            .hljs-selector-id,
         | 
| 83 | 
             
            .hljs-title {
         | 
| 84 | 
            -
             | 
| 85 | 
             
            }
         | 
| 86 |  | 
| 87 | 
             
            .hljs-emphasis {
         | 
| 88 | 
            -
             | 
| 89 | 
             
            }
         | 
| 90 |  | 
| 91 | 
             
            .hljs-strong {
         | 
| 92 | 
            -
             | 
| 93 | 
             
            }
         | 
| 94 |  | 
| 95 | 
             
            .hljs-link {
         | 
| 96 | 
            -
             | 
| 97 | 
             
            }
         | 
| 98 |  | 
| 99 | 
             
            /* Atom One Dark (v9.16.2) scoped to .dark */
         | 
| @@ -118,23 +118,23 @@ hue-6-2: #e6c07b | |
| 118 | 
             
            */
         | 
| 119 |  | 
| 120 | 
             
            .dark .hljs {
         | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
             
            }
         | 
| 127 |  | 
| 128 | 
             
            .dark .hljs-comment,
         | 
| 129 | 
             
            .dark .hljs-quote {
         | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
             
            }
         | 
| 133 |  | 
| 134 | 
             
            .dark .hljs-doctag,
         | 
| 135 | 
             
            .dark .hljs-keyword,
         | 
| 136 | 
             
            .dark .hljs-formula {
         | 
| 137 | 
            -
             | 
| 138 | 
             
            }
         | 
| 139 |  | 
| 140 | 
             
            .dark .hljs-section,
         | 
| @@ -142,11 +142,11 @@ hue-6-2: #e6c07b | |
| 142 | 
             
            .dark .hljs-selector-tag,
         | 
| 143 | 
             
            .dark .hljs-deletion,
         | 
| 144 | 
             
            .dark .hljs-subst {
         | 
| 145 | 
            -
             | 
| 146 | 
             
            }
         | 
| 147 |  | 
| 148 | 
             
            .dark .hljs-literal {
         | 
| 149 | 
            -
             | 
| 150 | 
             
            }
         | 
| 151 |  | 
| 152 | 
             
            .dark .hljs-string,
         | 
| @@ -154,12 +154,12 @@ hue-6-2: #e6c07b | |
| 154 | 
             
            .dark .hljs-addition,
         | 
| 155 | 
             
            .dark .hljs-attribute,
         | 
| 156 | 
             
            .dark .hljs-meta-string {
         | 
| 157 | 
            -
             | 
| 158 | 
             
            }
         | 
| 159 |  | 
| 160 | 
             
            .dark .hljs-built_in,
         | 
| 161 | 
             
            .dark .hljs-class .hljs-title {
         | 
| 162 | 
            -
             | 
| 163 | 
             
            }
         | 
| 164 |  | 
| 165 | 
             
            .dark .hljs-attr,
         | 
| @@ -170,7 +170,7 @@ hue-6-2: #e6c07b | |
| 170 | 
             
            .dark .hljs-selector-attr,
         | 
| 171 | 
             
            .dark .hljs-selector-pseudo,
         | 
| 172 | 
             
            .dark .hljs-number {
         | 
| 173 | 
            -
             | 
| 174 | 
             
            }
         | 
| 175 |  | 
| 176 | 
             
            .dark .hljs-symbol,
         | 
| @@ -179,17 +179,17 @@ hue-6-2: #e6c07b | |
| 179 | 
             
            .dark .hljs-meta,
         | 
| 180 | 
             
            .dark .hljs-selector-id,
         | 
| 181 | 
             
            .dark .hljs-title {
         | 
| 182 | 
            -
             | 
| 183 | 
             
            }
         | 
| 184 |  | 
| 185 | 
             
            .dark .hljs-emphasis {
         | 
| 186 | 
            -
             | 
| 187 | 
             
            }
         | 
| 188 |  | 
| 189 | 
             
            .dark .hljs-strong {
         | 
| 190 | 
            -
             | 
| 191 | 
             
            }
         | 
| 192 |  | 
| 193 | 
             
            .dark .hljs-link {
         | 
| 194 | 
            -
             | 
| 195 | 
             
            }
         | 
|  | |
| 20 | 
             
            */
         | 
| 21 |  | 
| 22 | 
             
            .hljs {
         | 
| 23 | 
            +
            	display: block;
         | 
| 24 | 
            +
            	overflow-x: auto;
         | 
| 25 | 
            +
            	padding: 0.5em;
         | 
| 26 | 
            +
            	color: #383a42;
         | 
| 27 | 
            +
            	background: #fafafa;
         | 
| 28 | 
             
            }
         | 
| 29 |  | 
| 30 | 
             
            .hljs-comment,
         | 
| 31 | 
             
            .hljs-quote {
         | 
| 32 | 
            +
            	color: #a0a1a7;
         | 
| 33 | 
            +
            	font-style: italic;
         | 
| 34 | 
             
            }
         | 
| 35 |  | 
| 36 | 
             
            .hljs-doctag,
         | 
| 37 | 
             
            .hljs-keyword,
         | 
| 38 | 
             
            .hljs-formula {
         | 
| 39 | 
            +
            	color: #a626a4;
         | 
| 40 | 
             
            }
         | 
| 41 |  | 
| 42 | 
             
            .hljs-section,
         | 
|  | |
| 44 | 
             
            .hljs-selector-tag,
         | 
| 45 | 
             
            .hljs-deletion,
         | 
| 46 | 
             
            .hljs-subst {
         | 
| 47 | 
            +
            	color: #e45649;
         | 
| 48 | 
             
            }
         | 
| 49 |  | 
| 50 | 
             
            .hljs-literal {
         | 
| 51 | 
            +
            	color: #0184bb;
         | 
| 52 | 
             
            }
         | 
| 53 |  | 
| 54 | 
             
            .hljs-string,
         | 
|  | |
| 56 | 
             
            .hljs-addition,
         | 
| 57 | 
             
            .hljs-attribute,
         | 
| 58 | 
             
            .hljs-meta-string {
         | 
| 59 | 
            +
            	color: #50a14f;
         | 
| 60 | 
             
            }
         | 
| 61 |  | 
| 62 | 
             
            .hljs-built_in,
         | 
| 63 | 
             
            .hljs-class .hljs-title {
         | 
| 64 | 
            +
            	color: #c18401;
         | 
| 65 | 
             
            }
         | 
| 66 |  | 
| 67 | 
             
            .hljs-attr,
         | 
|  | |
| 72 | 
             
            .hljs-selector-attr,
         | 
| 73 | 
             
            .hljs-selector-pseudo,
         | 
| 74 | 
             
            .hljs-number {
         | 
| 75 | 
            +
            	color: #986801;
         | 
| 76 | 
             
            }
         | 
| 77 |  | 
| 78 | 
             
            .hljs-symbol,
         | 
|  | |
| 81 | 
             
            .hljs-meta,
         | 
| 82 | 
             
            .hljs-selector-id,
         | 
| 83 | 
             
            .hljs-title {
         | 
| 84 | 
            +
            	color: #4078f2;
         | 
| 85 | 
             
            }
         | 
| 86 |  | 
| 87 | 
             
            .hljs-emphasis {
         | 
| 88 | 
            +
            	font-style: italic;
         | 
| 89 | 
             
            }
         | 
| 90 |  | 
| 91 | 
             
            .hljs-strong {
         | 
| 92 | 
            +
            	font-weight: bold;
         | 
| 93 | 
             
            }
         | 
| 94 |  | 
| 95 | 
             
            .hljs-link {
         | 
| 96 | 
            +
            	text-decoration: underline;
         | 
| 97 | 
             
            }
         | 
| 98 |  | 
| 99 | 
             
            /* Atom One Dark (v9.16.2) scoped to .dark */
         | 
|  | |
| 118 | 
             
            */
         | 
| 119 |  | 
| 120 | 
             
            .dark .hljs {
         | 
| 121 | 
            +
            	display: block;
         | 
| 122 | 
            +
            	overflow-x: auto;
         | 
| 123 | 
            +
            	padding: 0.5em;
         | 
| 124 | 
            +
            	color: #abb2bf;
         | 
| 125 | 
            +
            	background: #282c34;
         | 
| 126 | 
             
            }
         | 
| 127 |  | 
| 128 | 
             
            .dark .hljs-comment,
         | 
| 129 | 
             
            .dark .hljs-quote {
         | 
| 130 | 
            +
            	color: #5c6370;
         | 
| 131 | 
            +
            	font-style: italic;
         | 
| 132 | 
             
            }
         | 
| 133 |  | 
| 134 | 
             
            .dark .hljs-doctag,
         | 
| 135 | 
             
            .dark .hljs-keyword,
         | 
| 136 | 
             
            .dark .hljs-formula {
         | 
| 137 | 
            +
            	color: #c678dd;
         | 
| 138 | 
             
            }
         | 
| 139 |  | 
| 140 | 
             
            .dark .hljs-section,
         | 
|  | |
| 142 | 
             
            .dark .hljs-selector-tag,
         | 
| 143 | 
             
            .dark .hljs-deletion,
         | 
| 144 | 
             
            .dark .hljs-subst {
         | 
| 145 | 
            +
            	color: #e06c75;
         | 
| 146 | 
             
            }
         | 
| 147 |  | 
| 148 | 
             
            .dark .hljs-literal {
         | 
| 149 | 
            +
            	color: #56b6c2;
         | 
| 150 | 
             
            }
         | 
| 151 |  | 
| 152 | 
             
            .dark .hljs-string,
         | 
|  | |
| 154 | 
             
            .dark .hljs-addition,
         | 
| 155 | 
             
            .dark .hljs-attribute,
         | 
| 156 | 
             
            .dark .hljs-meta-string {
         | 
| 157 | 
            +
            	color: #98c379;
         | 
| 158 | 
             
            }
         | 
| 159 |  | 
| 160 | 
             
            .dark .hljs-built_in,
         | 
| 161 | 
             
            .dark .hljs-class .hljs-title {
         | 
| 162 | 
            +
            	color: #e6c07b;
         | 
| 163 | 
             
            }
         | 
| 164 |  | 
| 165 | 
             
            .dark .hljs-attr,
         | 
|  | |
| 170 | 
             
            .dark .hljs-selector-attr,
         | 
| 171 | 
             
            .dark .hljs-selector-pseudo,
         | 
| 172 | 
             
            .dark .hljs-number {
         | 
| 173 | 
            +
            	color: #d19a66;
         | 
| 174 | 
             
            }
         | 
| 175 |  | 
| 176 | 
             
            .dark .hljs-symbol,
         | 
|  | |
| 179 | 
             
            .dark .hljs-meta,
         | 
| 180 | 
             
            .dark .hljs-selector-id,
         | 
| 181 | 
             
            .dark .hljs-title {
         | 
| 182 | 
            +
            	color: #61aeee;
         | 
| 183 | 
             
            }
         | 
| 184 |  | 
| 185 | 
             
            .dark .hljs-emphasis {
         | 
| 186 | 
            +
            	font-style: italic;
         | 
| 187 | 
             
            }
         | 
| 188 |  | 
| 189 | 
             
            .dark .hljs-strong {
         | 
| 190 | 
            +
            	font-weight: bold;
         | 
| 191 | 
             
            }
         | 
| 192 |  | 
| 193 | 
             
            .dark .hljs-link {
         | 
| 194 | 
            +
            	text-decoration: underline;
         | 
| 195 | 
             
            }
         | 
 
			

