feat: conversations on the left, the transcript on the right
The transcript was duplicated across both panes, which was the actual complaint. The left column is now the list of conversations and the right pane is the transcript of the selected one, so the panes show different things. - the open conversation is marked with a check icon and an "Open" tag - each row carries its own transcript as its detail, so moving the selection previews that conversation - conversations are ordered most-recently-updated first, which keeps the one being chatted in at row zero, where Vicinae's selection reset lands - Return now adapts: with text in the search bar it sends, with an empty bar it opens the highlighted conversation (sending an empty prompt was a no-op anyway) - the search bar accessory dropdown is gone. The left column is the conversation switcher now, so the dropdown only duplicated it. Speaker icons in the transcript are not possible, so the labels are plain text. MdImage renders images block-level, centred, and capped at 200px tall, and all three candidate sources were tried in a live probe -- icon://person, a raw icon slug, and a file:// asset path -- and each one fell back to rendering its alt text as a caption. markdown.ts records the reason so nobody retries it blind. Verified on screen: one conversation row with an Open tag and a check icon, the full transcript on the right, and the action bar reading "Open Conversation" while the search bar is empty. typecheck, eslint, 109 tests and vici build all pass.
This commit is contained in:
+12
-8
@@ -15,14 +15,18 @@ import type { ChatTurn, ThreadMessage } from "./types";
|
||||
export const STREAMING_CURSOR = "▍";
|
||||
|
||||
/**
|
||||
* Role markers for the transcript.
|
||||
* Speaker labels.
|
||||
*
|
||||
* Emoji rather than icon images: Vicinae's markdown renderer is minimal and
|
||||
* there is no reliable way to inline an `icon://` reference into markdown, but
|
||||
* plain text always renders.
|
||||
* Plain text, not icons. Vicinae's markdown renderer draws images as
|
||||
* block-level, centred elements capped at 200px tall (`MdImage.qml`), and none
|
||||
* of the candidate sources resolve inside markdown — `icon://person`, a raw
|
||||
* icon slug, and a `file://` asset path were all tried and each one fell back
|
||||
* to rendering the alt text as a caption. An icon therefore cannot sit inline
|
||||
* beside a message the way a text label can. The dim/bright styling carries
|
||||
* most of the distinction regardless.
|
||||
*/
|
||||
export const USER_MARKER = "👤";
|
||||
export const ASSISTANT_MARKER = "✨";
|
||||
export const USER_LABEL = "You";
|
||||
export const ASSISTANT_LABEL = "Lam";
|
||||
|
||||
/** Prefixes every line so a multi-line message stays one continuous quote block. */
|
||||
export function quote(text: string): string {
|
||||
@@ -41,11 +45,11 @@ export function previewText(text: string, max: number): string {
|
||||
|
||||
function renderMessage(message: ThreadMessage): string {
|
||||
if (message.role === "user") {
|
||||
return `${USER_MARKER} **You**\n\n${quote(message.text)}`;
|
||||
return `**${USER_LABEL}**\n\n${quote(message.text)}`;
|
||||
}
|
||||
|
||||
const body = message.text.trim() || (message.isStreaming ? "_Thinking…_" : "_No response._");
|
||||
const sections = [`${ASSISTANT_MARKER} **Lam**`, "", message.isStreaming ? `${body} ${STREAMING_CURSOR}` : body];
|
||||
const sections = [`**${ASSISTANT_LABEL}**`, "", message.isStreaming ? `${body} ${STREAMING_CURSOR}` : body];
|
||||
|
||||
if (message.error) {
|
||||
sections.push("", `**Error** — ${message.error}`);
|
||||
|
||||
Reference in New Issue
Block a user