Chat bubbles, icons for chats, more vivid and distinct colour tags

- Bubbles: own messages right-aligned in their own colour, agent answers left.
- Chat icons (22, validated on the server), shown in list, top bar, project page.
- Colour tags: eight fixed hues (new: pink) with theme-matched lightness and the
  highest chroma sRGB allows; stronger background tint.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Sonnet 5.5 committed 2026-11-14 23:31:52 +01:00
1 parent f34a0c7a73
commit ce0d8afe69
13 files changed
+180 -37

No files matched your search

+35 -2
View File
@@ -1,9 +1,16 @@
import { useT, type Key } from "../i18n";
import { Icon } from "./Icon";
/** Colour tags of chats: names, drawn in the colours of the active theme (see `[data-color]` in styles.css). */
export const SESSION_COLORS = ["red", "orange", "yellow", "green", "aqua", "blue", "purple"] as const;
export const SESSION_COLORS = ["red", "orange", "yellow", "green", "aqua", "blue", "purple", "pink"] as const;
/** Row of colour dots; the selected one is ringed, a second click (or ✕) removes the tag. */
/** Icons a chat can get (must match SESSION_ICONS on the server). */
export const SESSION_ICONS = [
"robot", "brain", "terminal", "wrench", "gear", "chart", "shield", "image", "music", "cube", "file-text", "folder", "search", "sparkle", "clock", "lock",
"users", "toolbox", "plug", "scroll", "eye", "template",
] as const;
/** Row of colour dots; the selected one is ringed, a second click removes the tag. */
export function ColorSwatches({ value, onChange }: { value?: string; onChange: (color: string) => void }) {
const t = useT();
return (
@@ -27,3 +34,29 @@ export function ColorSwatches({ value, onChange }: { value?: string; onChange: (
</div>
);
}
/** Grid of icons for a chat (in the colour of the chat); a second click removes the icon. */
export function IconPicker({ value, color, onChange }: { value?: string; color?: string; onChange: (icon: string) => void }) {
const t = useT();
return (
<div className="icon-picker" role="radiogroup" aria-label={t("icon.label")} data-color={color}>
{SESSION_ICONS.map((i) => (
<button
key={i}
type="button"
role="radio"
aria-checked={value === i}
className={`icon-choice ${value === i ? "on" : ""}`}
title={i}
aria-label={i}
onClick={(e) => {
e.stopPropagation();
onChange(value === i ? "" : i);
}}
>
<Icon name={i} />
</button>
))}
</div>
);
}