Commit Graph
3881 Commits
Author SHA1 Message Date
Timothy Jaeryang Baek 1674e5a9ef refac 2026-08-11 12:50:50 -06:00
Timothy Jaeryang Baek f79b443c22 refac 2026-08-11 11:40:24 -06:00
G30 25455e943d fix(ui): resolve owner avatars against the API base url and add a fallback (#28272) 2026-08-11 11:05:35 -06:00
Timothy Jaeryang BaekandClassic298 c8f8fa451a a11y
Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com>
2026-08-11 01:25:20 -06:00
Timothy Jaeryang Baek e963d36e39 refac 2026-08-11 01:08:58 -06:00
Timothy Jaeryang Baek 9122c24ea2 refac 2026-08-11 00:06:21 -06:00
G30 d959e3e312 fix: scale the settings modal height with tall viewports instead of capping at 54rem (#27615) 2026-08-10 23:34:04 -06:00
Classic298 4eb0394511 fix: merged response receiving empty model responses after reload (#27673)
Clicking "Merged Response" in a multi-model chat often made the merging model answer with "It appears that the responses provided from the other models were empty".

The merge handler collected each model's answer via `history.messages[id].content`. Assistant messages are persisted by the backend with `output` only (`upsert_message_to_chat_by_id_and_message_id` writes `done`/`role`/`output`, never `content`), so `content` is only populated in the browser session that generated the responses, where the streaming handler mirrors it. Once the chat is reloaded from the database, every assistant message has `content: ''` and the merge request is sent with a list of empty strings, which is exactly what the merging model then reports. That is why the failure looks random: merging works right after generating, and fails after a refresh or when reopening the chat.

Read the responses through `getOutputText(message.output) || message.content`, the same fallback already used by every other read site (`ResponseMessage`, `Overview/Node`, `SearchModal`, `ChatItem`, `ChatMenu`, `Navbar/Menu`).

Fixes #26962
2026-08-10 23:31:21 -06:00
Classic298 fcc130c9bb fix: send stream_options.include_usage for backend-initiated chats (#27661)
Only the frontend added `stream_options: {include_usage: true}` to the completion payload, gated on the model's `usage` capability. Every backend-initiated run builds its own payload (automations, timers, subagents, channels) and omitted it, so those responses came back without token counts and never rendered the usage block, even with the capability enabled on the model.

Set it in `chat_completion` instead, the single handler all of those callers go through, and drop the two duplicate copies (the Anthropic-compat handler and the frontend). Capabilities are read from the resolved model before the custom-model fallback can rebind it, and the flag is applied after the model's `stream_response` override so a non-streaming model is unaffected.

Fixes #27653
2026-08-10 23:30:46 -06:00
G30 8e83d83324 fix: left-edge clipping of the terminal cloud icon and account profile avatar (#27691) 2026-08-10 23:28:17 -06:00
Solaris-star 2387ce63cb fix(chat): keep regenerate button visible in High Contrast mode (#27644)
Every action button on a response message gates its visibility on
'isLastMessage || ($settings?.highContrastMode ?? false)' so that buttons
stay visible (not hover-only) when High Contrast mode is on. The two
Regenerate buttons — the one inside RegenerateMenu and the fallback in the
{:else} branch — were missed and still gated on isLastMessage alone, so on
any non-last message the Regenerate icon was invisible until mouse-over even
with High Contrast enabled (#27638).

Add the same highContrastMode clause to both buttons, matching the other
action buttons in this file.
2026-08-10 23:27:42 -06:00
Classic298 be9af1653e fix: anchor chat input expand button to the input row (#27676)
The "expand input" button was positioned with `fixed top-0 right-0`. That only kept it near the composer by accident: `#message-input-container` sets `backdrop-blur-sm`, and a backdrop-filter makes an element the containing block for fixed descendants, so the button resolved to the top-right corner of the entire composer instead of the text area it belongs to.

That corner is already taken. The `@`-tagged model chip renders as the first row of the same container with its dismiss button at the right end, so with a multi-line prompt and a tagged model the two controls are drawn on top of each other. The attached-files row has the same problem: the button paints over the first thumbnail and its remove button.

Anchor the button to the wrapper that holds the text area instead, using `relative`/`absolute`, so it always sits at the top-right of the input row and below whatever rows precede it. With no chip and no files the position is unchanged. As a side effect the button is no longer a child of the `overflow-auto` scroller, so it can no longer be clipped or scrolled out of view on long prompts.

Fixes #26736
2026-08-10 23:26:53 -06:00
G30andNameless-Monster-Nerd b91bb67b55 fix: hide chat fork actions when chat import permission is disabled (#27711)
Co-authored-by: Nameless-Monster-Nerd <Nameless-Monster-Nerd@users.noreply.github.com>
2026-08-10 23:24:59 -06:00
G30 a3d33b4cf3 fix: hide chat delete actions when the chat delete permission is disabled (#27714)
The chat delete endpoints gate on chat.delete for non-admins, but none of
the UI affordances that reach them were conditioned on it, so a user
without the permission is offered controls that always fail with
"Access prohibited".

Gates every entry point on the same condition the backend enforces,
matching the existing chat.share / chat.export / chat.import gates in the
same components:

- Sidebar chat menu (ChatMenu) Delete item — also covers the Search
  modal's menu, which reuses ChatMenu
- Sidebar Shift+hover inline Delete button (ChatItem)
- Sidebar hidden #delete-chat-button keyboard-shortcut target (ChatItem)
- Chat navbar menu Delete item (Navbar/Menu)
- Search modal Shift-held inline Delete button (SearchModal)
- Settings -> Data Controls -> Delete All Chats
- Settings -> Archived Chats per-row Delete
2026-08-10 23:24:02 -06:00
G30 fe62934be7 fix: derive overview node spacing from rem so branches never overlap (#27995) 2026-08-10 23:23:31 -06:00
G30 38a03830c2 fix: show a placeholder when an image cannot be loaded (#27730)
A message referencing a file that no longer exists rendered a broken
image, because nothing anywhere noticed the failed load. What the user saw
depended on the caller's alt text: in a response the alt is the message
content, so the entire reply was rendered inside the image frame, and the
preview still opened full-screen on a dead image.

Image.svelte now tracks a failed load and renders an 'Image unavailable'
placeholder instead, suppressing the preview for a source that cannot be
shown. The failed state resets when the source changes, so a replaced or
corrected URL is retried. An onError callback lets a caller react without
changing behaviour for the eight existing call sites, which are untouched.

Responses now pass the file name (or 'Generated Image') as alt rather than
the message content, which was never a description of the image.
2026-08-10 22:27:13 -06:00
G30 a3e5d0b362 fix(chat): attribute shared chat messages to their author, not the viewer (#28274) 2026-08-10 21:40:54 -06:00
G30 148283f974 fix: release a queued message once an attached URL finishes (#28381)
A message sent while an attachment is still uploading is held in the chat
queue until the file settles. For files that release works, because the
message input calls onUpdate once the upload completes, which refreshes the
queued entry and asks the queue to run.

Attaching a URL takes a different path. uploadWeb sets the item to uploaded
but calls neither onUpdate nor processNextInQueue, and the queue is
otherwise only revisited when a generation finishes or when the chat is
mounted while idle. In a new chat with nothing generating, none of those
happen, so a message queued behind a URL waits with no event able to
release it until the chat is reloaded.

uploadWeb now asks the queue to run when it is done, the same way the file
path already does.
2026-08-10 20:24:35 -06:00
Classic298 eff5c4a2d9 feat: parse :::writing block metadata and use the subject as the block title (#28280)
Newer OpenAI chat models put metadata on the opening line of a colon fence block, like :::writing{variant="email" id="48173" subject="Short question" recipient="mail@example.com"}. The tokenizer matched that line and discarded it, so every block rendered under the same generic "Writing" heading no matter what it contained.

The opening line is now parsed into an attributes map on the token and the header uses it: the subject becomes the title, the recipient follows it and the full string is reachable on hover when the row is too narrow for it. Blocks without metadata render exactly as before, and the other fence types get the parsed attributes for free.

Attributes are read only from inside the {...} braces, not from the whole opening line. Scanning the whole line turned ordinary prose containing key="value" into metadata, and it backtracked quadratically: a 40k character opening line took 586ms to parse, and that runs again on every re-lex while the message streams. Anchored to the braces it is 0.0ms.

Nothing here turns the recipient into a link or a send action. That metadata is model output and can be steered by whatever is in the context, so a prefilled mail action is a separate decision rather than a side effect of parsing.
2026-08-10 19:44:06 -06:00
G30 8836dcb59f fix(ui): fall back to the default avatar when a profile image fails to load (#28270) 2026-08-10 19:18:32 -06:00
Timothy Jaeryang Baek 72a909fd2f refac 2026-08-10 19:16:21 -06:00
Timothy Jaeryang Baek 30d08a42f8 refac 2026-08-10 18:52:58 -06:00
Timothy Jaeryang Baek 407c40f72c refac 2026-08-10 18:52:34 -06:00
Timothy Jaeryang Baek b4d3b27caf refac 2026-08-10 18:52:27 -06:00
Timothy Jaeryang Baek d4461bd6f3 refac 2026-08-10 18:52:18 -06:00
Timothy Jaeryang Baek 8fbfd14a8b refac 2026-08-10 01:38:32 -06:00
Timothy Jaeryang Baek 048c063993 refac 2026-08-10 01:26:04 -06:00
Timothy Jaeryang Baek ff7467b4c5 refac 2026-08-10 01:14:53 -06:00
Timothy Jaeryang Baek 2dadc5435a refac 2026-08-09 13:22:46 -06:00
Timothy Jaeryang Baek 009999f363 refac 2026-08-08 15:47:10 -06:00
Timothy Jaeryang Baek c1c07cbe0f refac 2026-08-05 07:57:55 -05:00
Timothy Jaeryang Baek 6c4d0ace16 refac 2026-08-05 07:44:06 -05:00
Timothy Jaeryang Baek 29eeda9f9a refac 2026-08-05 07:12:28 -05:00
Timothy Jaeryang Baek 9c7ce154e7 refac 2026-08-05 07:04:30 -05:00
Timothy Jaeryang Baek cbb3aade2b refac 2026-08-05 06:41:30 -05:00
G30 b67804f2b6 fix: do not auto-open artifacts from the sidebar chat hover preview (#27773) 2026-07-31 17:58:02 -04:00
Timothy Jaeryang Baek 3becec6ccf refac 2026-07-31 17:35:34 -04:00
Timothy Jaeryang Baek 5b333d75c6 refac 2026-07-31 17:34:39 -04:00
Timothy Jaeryang Baek 810378c0b8 refac 2026-07-27 19:39:36 -04:00
Timothy Jaeryang Baek b6b16d5871 refac 2026-07-27 19:24:03 -04:00
Timothy Jaeryang Baek faeba7c17a refac 2026-07-27 05:28:33 -04:00
Timothy Jaeryang Baek c004b4ecb5 chore: format 2026-07-27 04:38:46 -04:00
Timothy Jaeryang Baek 2f2bf38e34 refac 2026-07-27 03:19:10 -04:00
Timothy Jaeryang Baek f59d86a10c refac 2026-07-27 03:18:12 -04:00
Timothy Jaeryang Baek 9707d3a5c2 refac 2026-07-27 03:09:25 -04:00
Timothy Jaeryang Baek f91ac068d0 refac 2026-07-27 02:12:40 -04:00
Timothy Jaeryang Baek 3492021361 refac 2026-07-27 02:04:44 -04:00
Classic298andmaxntv bc600d3f08 fix: escape KaTeX render-error fallback to prevent XSS via {@html} (#26718)
KatexRenderer rendered the raw math source through {@html} whenever renderToString threw. throwOnError only suppresses KaTeX ParseError, so a RangeError (maximum call stack size exceeded, reachable with deeply-nested brace input) escaped into the catch and re-exposed the unescaped source. Because the math tokenizer captures everything between the delimiters verbatim, that source can carry an HTML/JS payload which then executed in the viewer's browser on the application origin, a stored, cross-user XSS reachable through normal chat/channel/shared-chat rendering. Escape the fallback so the source is shown as text and is never injected as HTML. Valid math is unaffected, it still renders through the success path.

Co-authored-by: maxntv <maxntv@users.noreply.github.com>
2026-07-27 01:36:41 -04:00
Timothy Jaeryang Baek 067cf31f40 refac 2026-07-27 01:32:41 -04:00
Timothy Jaeryang Baek 75e54bf46b refac 2026-07-27 01:21:00 -04:00