Archived
Extract chat grouping model
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
import type { ChatLine, ChatPart } from "./components/shared";
|
||||||
|
|
||||||
|
export type ChatGroup =
|
||||||
|
| { kind: "message"; message: ChatLine; index: number }
|
||||||
|
| { kind: "group"; messages: ChatLine[]; startIndex: number };
|
||||||
|
|
||||||
|
export function groupChatMessages(messages: ChatLine[]): ChatGroup[] {
|
||||||
|
const groups: ChatGroup[] = [];
|
||||||
|
let eventMessages: ChatLine[] = [];
|
||||||
|
let eventStartIndex = 0;
|
||||||
|
|
||||||
|
const pushEvent = (message: ChatLine, index: number) => {
|
||||||
|
if (!eventMessages.length) eventStartIndex = index;
|
||||||
|
eventMessages.push(message);
|
||||||
|
};
|
||||||
|
const flushEvents = () => {
|
||||||
|
if (!eventMessages.length) return;
|
||||||
|
groups.push({ kind: "group", messages: eventMessages, startIndex: eventStartIndex });
|
||||||
|
eventMessages = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.forEach((message, index) => {
|
||||||
|
const readableParts = message.parts.filter((part) => isReadablePart(message, part));
|
||||||
|
const technicalParts = message.parts.filter((part) => !isReadablePart(message, part));
|
||||||
|
|
||||||
|
if (technicalParts.length) pushEvent({ role: message.role, parts: technicalParts }, index);
|
||||||
|
if (readableParts.length) {
|
||||||
|
flushEvents();
|
||||||
|
groups.push({ kind: "message", message: { role: message.role, parts: readableParts }, index });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
flushEvents();
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function summarizeChatGroup(messages: ChatLine[]): string {
|
||||||
|
const counts = messages.reduce<Record<string, number>>((acc, message) => {
|
||||||
|
acc[message.role] = (acc[message.role] ?? 0) + 1;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const details = Object.entries(counts).map(([role, count]) => `${count} ${role}`).join(" · ");
|
||||||
|
return `${messages.length} ${messages.length === 1 ? "event" : "events"}${details ? ` · ${details}` : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReadablePart(message: ChatLine, part: ChatPart): boolean {
|
||||||
|
return part.type === "text" && (message.role === "user" || message.role === "assistant" || message.role === "system" || message.role === "bash");
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { LitElement, html } from "lit";
|
import { LitElement, html } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators.js";
|
import { customElement, property, query, state } from "lit/decorators.js";
|
||||||
|
import { groupChatMessages, summarizeChatGroup } from "../chatGroups";
|
||||||
import type { ChatLine, ChatPart } from "./shared";
|
import type { ChatLine, ChatPart } from "./shared";
|
||||||
import { chatStyles } from "./shared";
|
import { chatStyles } from "./shared";
|
||||||
import "./FormattedText";
|
import "./FormattedText";
|
||||||
@@ -30,7 +31,7 @@ export class ChatView extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="chat" @scroll=${this.onScroll}>
|
<div class="chat" @scroll=${this.onScroll}>
|
||||||
${this.groupedMessages().map((group) => group.kind === "message"
|
${groupChatMessages(this.messages).map((group) => group.kind === "message"
|
||||||
? this.renderMessage(group.message, group.index)
|
? this.renderMessage(group.message, group.index)
|
||||||
: this.renderMessageGroup(group.messages, group.startIndex))}
|
: this.renderMessageGroup(group.messages, group.startIndex))}
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +52,7 @@ export class ChatView extends LitElement {
|
|||||||
<details class="msg event-group" data-index=${startIndex}>
|
<details class="msg event-group" data-index=${startIndex}>
|
||||||
<summary>
|
<summary>
|
||||||
<b class="label">events</b>
|
<b class="label">events</b>
|
||||||
<span>${this.groupSummary(messages)}</span>
|
<span>${summarizeChatGroup(messages)}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="group-body">
|
<div class="group-body">
|
||||||
${messages.map((message) => html`
|
${messages.map((message) => html`
|
||||||
@@ -65,48 +66,6 @@ export class ChatView extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private groupedMessages(): Array<{ kind: "message"; message: ChatLine; index: number } | { kind: "group"; messages: ChatLine[]; startIndex: number }> {
|
|
||||||
const groups: Array<{ kind: "message"; message: ChatLine; index: number } | { kind: "group"; messages: ChatLine[]; startIndex: number }> = [];
|
|
||||||
let eventMessages: ChatLine[] = [];
|
|
||||||
let eventStartIndex = 0;
|
|
||||||
|
|
||||||
const pushEvent = (message: ChatLine, index: number) => {
|
|
||||||
if (!eventMessages.length) eventStartIndex = index;
|
|
||||||
eventMessages.push(message);
|
|
||||||
};
|
|
||||||
const flushEvents = () => {
|
|
||||||
if (!eventMessages.length) return;
|
|
||||||
groups.push({ kind: "group", messages: eventMessages, startIndex: eventStartIndex });
|
|
||||||
eventMessages = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.messages.forEach((message, index) => {
|
|
||||||
const readableParts = message.parts.filter((part) => this.isReadablePart(message, part));
|
|
||||||
const technicalParts = message.parts.filter((part) => !this.isReadablePart(message, part));
|
|
||||||
|
|
||||||
if (technicalParts.length) pushEvent({ role: message.role, parts: technicalParts }, index);
|
|
||||||
if (readableParts.length) {
|
|
||||||
flushEvents();
|
|
||||||
groups.push({ kind: "message", message: { role: message.role, parts: readableParts }, index });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flushEvents();
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isReadablePart(message: ChatLine, part: ChatPart): boolean {
|
|
||||||
return part.type === "text" && (message.role === "user" || message.role === "assistant" || message.role === "system" || message.role === "bash");
|
|
||||||
}
|
|
||||||
|
|
||||||
private groupSummary(messages: ChatLine[]): string {
|
|
||||||
const counts = messages.reduce<Record<string, number>>((acc, message) => {
|
|
||||||
acc[message.role] = (acc[message.role] ?? 0) + 1;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
const details = Object.entries(counts).map(([role, count]) => `${count} ${role}`).join(" · ");
|
|
||||||
return `${messages.length} ${messages.length === 1 ? "event" : "events"}${details ? ` · ${details}` : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderPart(part: ChatPart) {
|
private renderPart(part: ChatPart) {
|
||||||
if (part.type === "text") return html`<formatted-text class="part" .text=${part.text}></formatted-text>`;
|
if (part.type === "text") return html`<formatted-text class="part" .text=${part.text}></formatted-text>`;
|
||||||
if (part.type === "thinking") return html`<details class="part"><summary>thinking</summary><formatted-text .text=${part.text}></formatted-text></details>`;
|
if (part.type === "thinking") return html`<details class="part"><summary>thinking</summary><formatted-text .text=${part.text}></formatted-text></details>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user