Add full-featured AIChat component
AIChat (elements/ai/AIChat) is a complete LLM chat UI built on new hooks: - useAIChat: streaming conversation state (send, stop, regenerate, edit, feedback); `send` may return a string, async iterable, ReadableStream or fetch Response - useAIChatHistory: saved conversations with optional localStorage persistence and generated titles, shown in AIChatSidebar - useAIChatSpeech: read aloud via browser speech synthesis or a custom `speak` returning audio - useAIChatDictation: voice input via browser recognition or a custom `transcribe` Also adds a model picker, message editing, thumbs up/down, markdown export, scoped markdown styles in base.css, the twuiReadTextStream, twuiStripMarkdown and twuiGroupByRecency utils (with tests), and a playground page. AIPromptBlock is kept and marked deprecated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
export type TWUIRecencyGroup<T> = {
|
||||
label: string;
|
||||
items: T[];
|
||||
};
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* # Group by recency
|
||||
* Buckets items into "Today", "Yesterday", "Previous 7 days",
|
||||
* "Previous 30 days" and then by month, newest first - the grouping chat
|
||||
* apps use for conversation lists. Empty groups are left out.
|
||||
*/
|
||||
export default function twuiGroupByRecency<T>(
|
||||
items: T[],
|
||||
getTime: (item: T) => number,
|
||||
now: number = Date.now(),
|
||||
): TWUIRecencyGroup<T>[] {
|
||||
const startOfToday = new Date(now);
|
||||
startOfToday.setHours(0, 0, 0, 0);
|
||||
const today = startOfToday.getTime();
|
||||
|
||||
const groups = new Map<string, T[]>();
|
||||
|
||||
const label = (time: number) => {
|
||||
if (time >= today) return "Today";
|
||||
if (time >= today - DAY) return "Yesterday";
|
||||
if (time >= today - 7 * DAY) return "Previous 7 days";
|
||||
if (time >= today - 30 * DAY) return "Previous 30 days";
|
||||
return new Date(time).toLocaleDateString(undefined, {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
[...items]
|
||||
.sort((a, b) => getTime(b) - getTime(a))
|
||||
.forEach((item) => {
|
||||
const key = label(getTime(item));
|
||||
const list = groups.get(key);
|
||||
if (list) list.push(item);
|
||||
else groups.set(key, [item]);
|
||||
});
|
||||
|
||||
return Array.from(groups, ([label, items]) => ({ label, items }));
|
||||
}
|
||||
Reference in New Issue
Block a user