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 |
}
|