mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-10-19 07:34:58 +00:00
Merge commit '666bbb0726' into dev
Some checks are pending
Build and Push Docker Images / image_version (push) Waiting to run
Build and Push Docker Images / build (./docker/sandbox, cpu, ./docker/sandbox/Dockerfile, sandbox, surfsense-sandbox, ubuntu-24.04-arm, linux/arm64, arm64, , false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./docker/sandbox, cpu, ./docker/sandbox/Dockerfile, sandbox, surfsense-sandbox, ubuntu-latest, linux/amd64, amd64, , false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / verify_digests (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (sandbox, surfsense-sandbox, , cpu) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Blocked by required conditions
Some checks are pending
Build and Push Docker Images / image_version (push) Waiting to run
Build and Push Docker Images / build (./docker/sandbox, cpu, ./docker/sandbox/Dockerfile, sandbox, surfsense-sandbox, ubuntu-24.04-arm, linux/arm64, arm64, , false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./docker/sandbox, cpu, ./docker/sandbox/Dockerfile, sandbox, surfsense-sandbox, ubuntu-latest, linux/amd64, amd64, , false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Blocked by required conditions
Build and Push Docker Images / verify_digests (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (sandbox, surfsense-sandbox, , cpu) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Blocked by required conditions
This commit is contained in:
commit
64cb73ca94
3 changed files with 85 additions and 2 deletions
|
|
@ -101,7 +101,7 @@ The list handed to the generator is `[system, *history within budget, user]`.
|
|||
- The runtime is scoped to the active thread; thread lists and workspaces stay application state. Sending from a new chat first creates a thread titled "New chat". The last open thread of each workspace is remembered in `localStorage`.
|
||||
- [`sse.ts`](../../surfsense_local/frontend/src/features/chat/sse.ts) parses the stream chunk-safely: frames split on blank lines, and a partial frame waits for the next read. This token stream is separate from the `/events` channel.
|
||||
- From send until the first token, the assistant turn shows a shimmering "Thinking" beside an orbiting-dots indicator ported from surfsense_web ([`thinking-indicator.tsx`](../../surfsense_local/frontend/src/features/chat/thinking-indicator.tsx)), so a model load or a long prompt is never a blank reply. It is one header from send to answer, whose state moves from waiting to thinking to done, so the indicator and shimmer never restart when the trace arrives. A thinking model's trace then streams open below it, above the answer, in a box 13rem tall that fades at its scrolled edges and follows the newest line until the reader scrolls up, and folds to "Thought for N seconds" when the answer starts, the indicator sliding away; the person's own open or close wins until the next reply, and holds the chat's scroll still while the trace slides, with assistant-ui's `useScrollLock` ([`reply-thinking.tsx`](../../surfsense_local/frontend/src/features/chat/reply-thinking.tsx)). The trace rides in the message's `metadata.custom`, beside the citations, not as a content part, so Copy takes the answer alone.
|
||||
- Sending is disabled without a usable model or while threads or messages load, and Stop aborts the request. A `409` "no chat model selected" opens model setup. An `error` frame attaches to its assistant turn: auth and not-found errors, and network errors from a remote endpoint, offer Model setup; a network error from the local runtime offers nothing, since no setting restarts it; the rest offer Retry, which resends the same text.
|
||||
- Sending is disabled without a usable model or while threads or messages load, and Stop aborts the request. A `409` "no chat model selected" opens model setup. An `error` frame attaches to its assistant turn: auth, not-found and model-cannot-run errors, and network errors from a remote endpoint, offer Model setup; a network error from the local runtime and a context-too-long error offer nothing, since no button fixes either; the rest offer Retry, which resends the same text.
|
||||
- Every turn sends the ids of the ready sources the user left included in the sources panel, so unticking a source takes it out of retrieval, and unticking all of them leaves the model with no context.
|
||||
|
||||
## Citation panel
|
||||
|
|
@ -128,7 +128,6 @@ The list handed to the generator is `[system, *history within budget, user]`.
|
|||
|
||||
- A stream that ends without an error but yields no text still renames the thread and stores an empty assistant turn; the discard guard is `failed and not parts`.
|
||||
- `budget.py` prices the question at 1,024 tokens and says `MessageText` enforces that, but `MessageText` has no length limit, so a long question can push a turn past the model's window.
|
||||
- `model_cannot_run` and `context_too_long` offer Retry in `chat-error-notice.tsx`, which cannot fix either.
|
||||
- No live region announces streamed text, and focus does not move to the conversation heading after a thread switch; the dashboard design asks for both.
|
||||
- No test covers a client disconnecting mid-reply. The assistant's text is written only when generation ends, inside the stream, so whether a disconnected reply is kept is unverified.
|
||||
- With no hits, the system message is the instructions alone, which still ask for `[n]` labels. In the chat eval, Qwen3 1.7B wrote a `[1]` in all three such answers, which chat drops, and one of them never said the sources did not cover the question.
|
||||
|
|
|
|||
|
|
@ -12,11 +12,16 @@ function actionFor(error: ChatTurnError): Action {
|
|||
switch (error.kind) {
|
||||
case "provider_auth":
|
||||
case "provider_not_found":
|
||||
case "model_cannot_run":
|
||||
return "model-setup"
|
||||
case "network":
|
||||
// A bad base URL is a Model setup fix; a local runtime that isn't
|
||||
// running isn't — there's no settings action that starts it.
|
||||
return error.provider === "llamacpp" ? "none" : "model-setup"
|
||||
case "context_too_long":
|
||||
// The notice text already says to start a new chat or pick a model
|
||||
// with a larger window — Retry would resend the same overlong turn.
|
||||
return "none"
|
||||
default:
|
||||
return "retry"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -891,6 +891,85 @@ describe("dashboard chat", () => {
|
|||
expect(screen.queryByRole("button", { name: "Model setup" })).toBeNull()
|
||||
})
|
||||
|
||||
it("offers no Retry for a context-too-long failure it cannot fix", async () => {
|
||||
const fetchMock = vi.fn(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const path = String(input)
|
||||
if (path === "/llm/providers") {
|
||||
return Response.json([
|
||||
{ name: "llamacpp", healthy: true, can_download: true },
|
||||
])
|
||||
}
|
||||
if (
|
||||
path === "/workspaces/1/documents?document_type=FILE&document_type=NOTE"
|
||||
) {
|
||||
return Response.json([])
|
||||
}
|
||||
if (path === "/workspaces/1/chat/threads" && !init?.method) {
|
||||
return Response.json([])
|
||||
}
|
||||
if (path === "/workspaces/1/chat/threads" && init?.method === "POST") {
|
||||
return Response.json(
|
||||
{
|
||||
id: 10,
|
||||
workspace_id: 1,
|
||||
title: "Too long",
|
||||
created_at: "2026-09-05T00:00:00Z",
|
||||
updated_at: "2026-09-05T00:00:00Z",
|
||||
},
|
||||
{ status: 201 }
|
||||
)
|
||||
}
|
||||
if (path === "/chat/threads/10/messages" && init?.method === "POST") {
|
||||
return new Response(
|
||||
'data: {"type":"accepted","user_message_id":100,"assistant_message_id":101,"user_created_at":"2026-09-05T00:00:00Z"}\n\ndata: {"type":"error","kind":"context_too_long","message":"context window exceeded","provider":"llamacpp"}\n\ndata: [DONE]\n\n',
|
||||
{ headers: { "Content-Type": "text/event-stream" } }
|
||||
)
|
||||
}
|
||||
if (path === "/chat/threads/10/messages") {
|
||||
return Response.json([])
|
||||
}
|
||||
return Response.json({ detail: "not found" }, { status: 404 })
|
||||
}
|
||||
)
|
||||
vi.stubGlobal("fetch", fetchMock)
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<DashboardPage
|
||||
initialProviderAvailable={true}
|
||||
selection={{
|
||||
model_type: "text_gen",
|
||||
provider: "llamacpp",
|
||||
connection_id: null,
|
||||
name: "llama3.2:1b",
|
||||
updated_at: "2026-09-05T00:00:00Z",
|
||||
}}
|
||||
initialWorkspaces={[workspace]}
|
||||
onModelSelected={vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
await screen.findByRole("textbox", { name: "Message" })
|
||||
await user.type(
|
||||
screen.getByRole("textbox", { name: "Message" }),
|
||||
"Too long"
|
||||
)
|
||||
await user.click(screen.getByRole("button", { name: "Send message" }))
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"This conversation is too long for the model’s context window. Start a new chat or pick a model with a larger window."
|
||||
)
|
||||
).toBeTruthy()
|
||||
// Retry resends the identical message to the identical model and fails
|
||||
// identically — it cannot fix an oversized context window.
|
||||
const retryButton = screen.queryByRole("button", { name: "Retry" })
|
||||
expect(retryButton, "context_too_long must not offer Retry").toBeNull()
|
||||
})
|
||||
|
||||
it("aborts the active stream when stop is pressed", async () => {
|
||||
const captured: { signal: AbortSignal | null } = { signal: null }
|
||||
const fetchMock = vi.fn(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue