Skip to content
Merged

fix CI #1884

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
### Models ###
# Models are sourced exclusively from an OpenAI-compatible base URL.
# Example: https://router.huggingface.co/v1
OPENAI_BASE_URL=
OPENAI_BASE_URL=https://router.huggingface.co/v1

# Canonical auth token for any OpenAI-compatible provider
OPENAI_API_KEY=#your provider API key (works for HF router, OpenAI, LM Studio, etc.)
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
extraFileExtensions: [".svelte"],
},
rules: {
"no-empty": "off",
"require-yield": "off",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-non-null-assertion": "error",
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
cache: "npm"
- run: |
npm ci
npx playwright install
- name: "Tests"
run: |
npm run test
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ RUN touch /app/.env.local

USER root
RUN apt-get update
RUN apt-get install -y libgomp1
RUN apt-get install -y libgomp1 libcurl4

# ensure npm cache dir exists before adjusting ownership
RUN mkdir -p /home/user/.npm && chown -R 1000:1000 /home/user/.npm
Expand Down
1 change: 0 additions & 1 deletion PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Security and routing facts

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.


## Technical details

[![chat-ui](https://img.shields.io/github/stars/huggingface/chat-ui)](https://github.com/huggingface/chat-ui)
Expand Down
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"js-yaml": "^4.1.0",
"minimist": "^1.2.8",
"mongodb-memory-server": "^10.1.2",
"playwright": "^1.55.1",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.11",
Expand Down
1 change: 0 additions & 1 deletion src/lib/components/NavMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@

let {
conversations = $bindable(),
canLogin,
user,
p = $bindable(0),
ondeleteConversation,
Expand Down
1 change: 0 additions & 1 deletion src/lib/components/WelcomeModal.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import Modal from "$lib/components/Modal.svelte";
import Logo from "$lib/components/icons/Logo.svelte";
import IconOmni from "$lib/components/icons/IconOmni.svelte";
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";

Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/chat/ChatWindow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@

const blob = await response.blob();
const name = attachment.src.split("/").pop() ?? "attachment";
loadedFiles.push(new File([blob], name, { type: blob.type || "application/octet-stream" }));
loadedFiles.push(
new File([blob], name, { type: blob.type || "application/octet-stream" })
);
} catch (err) {
console.error("Error loading attachment:", err);
}
Expand Down
6 changes: 1 addition & 5 deletions src/lib/components/chat/MarkdownRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html token.html}
{:else if token.type === "code"}
<CodeBlock
code={token.code}
rawCode={token.rawCode}
loading={loading && !token.isClosed}
/>
<CodeBlock code={token.code} rawCode={token.rawCode} loading={loading && !token.isClosed} />
{/if}
{/each}
6 changes: 5 additions & 1 deletion src/lib/migrations/routines/06-trim-message-updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { logger } from "$lib/server/logger";
function convertMessageUpdate(message: Message, update: unknown): MessageUpdate | null {
try {
// Trim legacy web search updates entirely
if (typeof update === "object" && update !== null && (update as any).type === "webSearch") {
if (
typeof update === "object" &&
update !== null &&
(update as { type: string }).type === "webSearch"
) {
return null;
}

Expand Down
30 changes: 0 additions & 30 deletions src/lib/server/api/routes/groups/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,36 +63,6 @@ export const misc = new Elysia()
isAdmin: locals.isAdmin,
} satisfies FeatureFlags;
})
.get("/spaces-config", async ({ query }) => {
if (config.COMMUNITY_TOOLS !== "true") {
throw new Error("Community tools are not enabled");
}

const space = query.space;

if (!space) {
throw new Error("Missing space");
}

// Extract namespace from space URL or use as-is if it's already in namespace format
let namespace = null;
if (space.startsWith("https://huggingface.co/spaces/")) {
namespace = space.split("/").slice(-2).join("/");
} else if (space.match(/^[^/]+\/[^/]+$/)) {
namespace = space;
}

if (!namespace) {
throw new Error("Invalid space name. Specify a namespace or a full URL on huggingface.co.");
}

try {
const api = await (await Client.connect(namespace)).view_api();
return api as ApiReturnType;
} catch (e) {
throw new Error("Error fetching space API. Is the name correct?");
}
})
.get("/export", async ({ locals }) => {
if (!locals.user) {
throw new Error("Not logged in");
Expand Down
4 changes: 2 additions & 2 deletions src/lib/server/endpoints/openai/endpointOai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export async function endpointOai(
stop: parameters?.stop,
temperature: parameters?.temperature,
top_p: parameters?.top_p,
frequency_penalty: parameters?.frequency_penalty,
frequency_penalty: parameters?.frequency_penalty,
presence_penalty: parameters?.presence_penalty,
};

Expand Down Expand Up @@ -173,7 +173,7 @@ export async function endpointOai(
stop: parameters?.stop,
temperature: parameters?.temperature,
top_p: parameters?.top_p,
frequency_penalty: parameters?.frequency_penalty,
frequency_penalty: parameters?.frequency_penalty,
presence_penalty: parameters?.presence_penalty,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export async function* openAIChatToTextGenerationStream(
let thinkOpen = false;

for await (const completion of completionStream) {
const retyped = completion as { "x-router-metadata"?: { route: string; model: string } };
// Check if this chunk contains router metadata (first chunk from llm-router)
if (!metadataYielded && (completion as any)["x-router-metadata"]) {
const metadata = (completion as any)["x-router-metadata"];
if (!metadataYielded && retyped["x-router-metadata"]) {
const metadata = retyped["x-router-metadata"];
yield {
token: {
id: tokenId++,
Expand All @@ -44,8 +45,11 @@ export async function* openAIChatToTextGenerationStream(
}
}
const { choices } = completion;
const delta: any = choices?.[0]?.delta ?? {};
const content: string = (delta?.content as string) ?? "";
const delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
reasoning?: string;
reasoning_content?: string;
} = choices?.[0]?.delta ?? {};
const content: string = delta.content ?? "";
const reasoning: string =
typeof delta?.reasoning === "string"
? (delta.reasoning as string)
Expand Down Expand Up @@ -158,7 +162,10 @@ export async function* openAIChatToTextGenerationSingle(
completion: OpenAI.Chat.Completions.ChatCompletion,
getRouterMetadata?: () => { route?: string; model?: string }
) {
const message: any = completion.choices?.[0]?.message ?? {};
const message: NonNullable<OpenAI.Chat.Completions.ChatCompletion.Choice>["message"] & {
reasoning?: string;
reasoning_content?: string;
} = completion.choices?.[0]?.message ?? {};
let content: string = message?.content || "";
// Provider-dependent reasoning shapes (non-streaming)
const r: string =
Expand Down
15 changes: 8 additions & 7 deletions src/lib/server/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const modelConfig = z.object({
stop: z.array(z.string()).optional(),
top_p: z.number().positive().optional(),
top_k: z.number().positive().optional(),
frequency_penalty: z.number().min(-2).max(2).optional(),
frequency_penalty: z.number().min(-2).max(2).optional(),
presence_penalty: z.number().min(-2).max(2).optional(),
})
.passthrough()
Expand Down Expand Up @@ -220,6 +220,8 @@ if (modelOverrides.length) {
if (!override) return model;

const { id, name, ...rest } = override;
void id;
void name;

return {
...model,
Expand Down Expand Up @@ -291,7 +293,7 @@ const routerAliasId = (config.PUBLIC_LLM_ROUTER_ALIAS_ID || "omni").trim() || "o
const routerMultimodalEnabled =
(config.LLM_ROUTER_ENABLE_MULTIMODAL || "").toLowerCase() === "true";

let decorated = builtModels as any[];
let decorated = builtModels as ProcessedModel[];

if (archBase) {
// Build a minimal model config for the alias
Expand All @@ -304,12 +306,12 @@ if (archBase) {
endpoints: [
{
type: "openai" as const,
baseURL: openaiBaseUrl!,
baseURL: openaiBaseUrl,
},
],
// Keep the alias visible
unlisted: false,
} as any;
} as ProcessedModel;

if (routerMultimodalEnabled) {
aliasRaw.multimodal = true;
Expand All @@ -318,13 +320,12 @@ if (archBase) {

const aliasBase = await processModel(aliasRaw);
// Create a self-referential ProcessedModel for the router endpoint
let aliasModel: any = {};
aliasModel = {
const aliasModel: ProcessedModel = {
...aliasBase,
isRouter: true,
// getEndpoint uses the router wrapper regardless of the endpoints array
getEndpoint: async (): Promise<Endpoint> => makeRouterEndpoint(aliasModel),
};
} as ProcessedModel;

// Put alias first
decorated = [aliasModel, ...decorated];
Expand Down
4 changes: 2 additions & 2 deletions src/lib/server/router/arch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ export async function archSelectRoute(
});
clearTimeout(to);
if (!resp.ok) throw new Error(`arch-router ${resp.status}`);
const data: any = await resp.json();
const data: { choices: { message: { content: string } }[] } = await resp.json();
const text = (data?.choices?.[0]?.message?.content ?? "").toString().trim();
const raw = parseRouteName(text);

const other = config.LLM_ROUTER_OTHER_ROUTE || "casual_conversation";
const chosen = raw === "other" ? other : raw || "casual_conversation";
const exists = routes.some((r) => r.name === chosen);
return { routeName: exists ? chosen : "casual_conversation" };
} catch (e: any) {
} catch (e) {
clearTimeout(to);
logger.warn({ err: String(e), traceId }, "arch router selection failed");
return { routeName: "arch_router_failure" };
Expand Down
18 changes: 12 additions & 6 deletions src/lib/server/router/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Endpoint, EndpointParameters, EndpointMessage } from "../endpoints/endpoints";
import type {
Endpoint,
EndpointParameters,
EndpointMessage,
TextGenerationStreamOutputSimplified,
} from "../endpoints/endpoints";
import endpoints from "../endpoints/endpoints";
import type { ProcessedModel } from "../models";
import { config } from "$lib/server/config";
Expand All @@ -17,6 +22,7 @@ function stripReasoningBlocks(text: string): string {

function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
const { reasoning: _reasoning, ...rest } = message;
void _reasoning;
const content =
typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
return {
Expand Down Expand Up @@ -47,7 +53,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E
let modelForCall: ProcessedModel | undefined;
try {
const mod = await import("../models");
const all = (mod as any).models as ProcessedModel[];
const all = (mod as { models: ProcessedModel[] }).models;
modelForCall = all?.find((m) => m.id === candidateModelId || m.name === candidateModelId);
} catch (e) {
logger.warn({ err: String(e) }, "[router] failed to load models for candidate lookup");
Expand Down Expand Up @@ -75,7 +81,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E

// Yield router metadata for immediate UI display, using the actual candidate
async function* metadataThenStream(
gen: AsyncGenerator<any>,
gen: AsyncGenerator<TextGenerationStreamOutputSimplified>,
actualModel: string,
selectedRoute: string
) {
Expand All @@ -84,14 +90,14 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E
generated_text: null,
details: null,
routerMetadata: { route: selectedRoute, model: actualModel },
} as any;
};
for await (const ev of gen) yield ev;
}

async function findFirstMultimodalCandidateId(): Promise<string | undefined> {
try {
const mod = await import("../models");
const all = (mod as any).models as ProcessedModel[];
const all = (mod as { models: ProcessedModel[] }).models;
const first = all?.find((m) => !m.isRouter && m.multimodal);
return first?.id ?? first?.name;
} catch (e) {
Expand Down Expand Up @@ -132,7 +138,7 @@ export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<E
const fallbackModel = config.LLM_ROUTER_FALLBACK_MODEL || routerModel.id;
const { candidates } = resolveRouteModels(routeName, routes, fallbackModel);

let lastErr: any = undefined;
let lastErr: unknown = undefined;
for (const candidate of candidates) {
try {
logger.info({ route: routeName, model: candidate }, "[router] trying candidate");
Expand Down
6 changes: 3 additions & 3 deletions src/lib/server/textGeneration/reasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export async function generateSummaryOfReasoning(
preprompt: `You are tasked with summarizing the latest reasoning steps. Never describe results of the reasoning, only the process. Remain vague in your summary.
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.
Example: "Thinking about life...", "Summarizing the results...", "Processing the input..."`,
generateSettings: {
max_tokens: 50,
},
generateSettings: {
max_tokens: 50,
},
modelId,
})
);
Expand Down
Loading
Loading