Archived
Render skill invocations compactly
This commit is contained in:
@@ -47,5 +47,6 @@ export function summarizeChatGroup(messages: ChatLine[]): string {
|
|||||||
|
|
||||||
function isReadablePart(message: ChatLine, part: ChatPart): boolean {
|
function isReadablePart(message: ChatLine, part: ChatPart): boolean {
|
||||||
if (message.source === "compaction" || message.source === "branch_summary") return false;
|
if (message.source === "compaction" || message.source === "branch_summary") return false;
|
||||||
|
if (part.type === "skillInvocation") return true;
|
||||||
return part.type === "text" && (message.role === "user" || message.role === "assistant" || message.role === "system" || message.role === "bash");
|
return part.type === "text" && (message.role === "user" || message.role === "assistant" || message.role === "system" || message.role === "bash");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ function normalizeMessage(message: unknown): ChatLine[] {
|
|||||||
if (getString(message, "role") === "bashExecution") return [normalizeBashExecution(message)];
|
if (getString(message, "role") === "bashExecution") return [normalizeBashExecution(message)];
|
||||||
const role = normalizeRole(getString(message, "role"));
|
const role = normalizeRole(getString(message, "role"));
|
||||||
const parts = normalizeContent(getProperty(message, "content"), message);
|
const parts = normalizeContent(getProperty(message, "content"), message);
|
||||||
|
const skillLines = role === "user" ? normalizeSkillInvocation(parts) : undefined;
|
||||||
|
if (skillLines !== undefined) return skillLines;
|
||||||
const source = normalizeSource(message, parts);
|
const source = normalizeSource(message, parts);
|
||||||
if (role === "tool") return [{ role, parts, ...(source === undefined ? {} : { source }) }];
|
if (role === "tool") return [{ role, parts, ...(source === undefined ? {} : { source }) }];
|
||||||
|
|
||||||
@@ -31,6 +33,28 @@ function normalizeMessage(message: unknown): ChatLine[] {
|
|||||||
return visible.length > 0 ? [{ role, parts: visible, ...(source === undefined ? {} : { source }) }] : [];
|
return visible.length > 0 ? [{ role, parts: visible, ...(source === undefined ? {} : { source }) }] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSkillInvocation(parts: ChatPart[]): ChatLine[] | undefined {
|
||||||
|
if (parts.length !== 1 || parts[0]?.type !== "text") return undefined;
|
||||||
|
const skill = parseSkillBlock(parts[0].text);
|
||||||
|
if (skill === undefined) return undefined;
|
||||||
|
return [
|
||||||
|
{ role: "user", parts: [{ type: "skillInvocation", name: skill.name, location: skill.location, content: skill.content }] },
|
||||||
|
...(skill.userMessage === undefined ? [] : [{ role: "user" as const, parts: [{ type: "text" as const, text: skill.userMessage }] }]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSkillBlock(text: string): { name: string; location: string; content: string; userMessage?: string } | undefined {
|
||||||
|
const match = text.match(/^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/);
|
||||||
|
if (match === null) return undefined;
|
||||||
|
const userMessage = match[4]?.trim();
|
||||||
|
return {
|
||||||
|
name: match[1] ?? "skill",
|
||||||
|
location: match[2] ?? "",
|
||||||
|
content: match[3] ?? "",
|
||||||
|
...(userMessage === undefined || userMessage === "" ? {} : { userMessage }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeSource(message: unknown, _parts: ChatPart[]): ChatLine["source"] | undefined {
|
function normalizeSource(message: unknown, _parts: ChatPart[]): ChatLine["source"] | undefined {
|
||||||
const source = getString(message, "source");
|
const source = getString(message, "source");
|
||||||
if (source === "compaction" || source === "branch_summary") return source;
|
if (source === "compaction" || source === "branch_summary") return source;
|
||||||
|
|||||||
@@ -129,6 +129,13 @@ export class ChatView extends LitElement {
|
|||||||
if (part.type === "text" && message?.role === "bash") return html`<pre class="part shell-output">${part.text}</pre>`;
|
if (part.type === "text" && message?.role === "bash") return html`<pre class="part shell-output">${part.text}</pre>`;
|
||||||
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>`;
|
||||||
|
if (part.type === "skillInvocation") return html`
|
||||||
|
<details class="part skill-invocation">
|
||||||
|
<summary><b>[skill]</b> ${part.name}</summary>
|
||||||
|
<small>${part.location}</small>
|
||||||
|
<formatted-text .text=${part.content}></formatted-text>
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
if (part.type === "toolCall") return html`<div class="part tool-line">▶ ${part.toolName}<span class="summary">${part.summary}</span></div>`;
|
if (part.type === "toolCall") return html`<div class="part tool-line">▶ ${part.toolName}<span class="summary">${part.summary}</span></div>`;
|
||||||
if (part.type === "toolResult") return html`
|
if (part.type === "toolResult") return html`
|
||||||
<details class="part" ?open=${part.isError}>
|
<details class="part" ?open=${part.isError}>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { css } from "lit";
|
|||||||
export type ChatPart =
|
export type ChatPart =
|
||||||
| { type: "text"; text: string }
|
| { type: "text"; text: string }
|
||||||
| { type: "thinking"; text: string }
|
| { type: "thinking"; text: string }
|
||||||
|
| { type: "skillInvocation"; name: string; location: string; content: string }
|
||||||
| { type: "toolCall"; toolName: string; summary: string }
|
| { type: "toolCall"; toolName: string; summary: string }
|
||||||
| { type: "toolResult"; toolName: string; text: string; isError: boolean }
|
| { type: "toolResult"; toolName: string; text: string; isError: boolean }
|
||||||
| { type: "empty" };
|
| { type: "empty" };
|
||||||
@@ -75,6 +76,9 @@ export const chatStyles = css`
|
|||||||
.tool-line { color: #d29922; }
|
.tool-line { color: #d29922; }
|
||||||
.summary { color: #8b949e; margin-left: 6px; }
|
.summary { color: #8b949e; margin-left: 6px; }
|
||||||
.part:is(details) { border-top: 1px solid #30363d; padding-top: 8px; }
|
.part:is(details) { border-top: 1px solid #30363d; padding-top: 8px; }
|
||||||
|
.skill-invocation { border: 1px solid #30363d; border-radius: 8px; background: #161b22; padding: 8px 10px; }
|
||||||
|
.skill-invocation > summary { color: #d2a8ff; }
|
||||||
|
.skill-invocation > small { display: block; margin: 6px 0 8px; color: #8b949e; }
|
||||||
summary { cursor: pointer; color: #8b949e; }
|
summary { cursor: pointer; color: #8b949e; }
|
||||||
pre { margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
|
pre { margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
|
||||||
.shell-output { color: #e6edf3; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; }
|
.shell-output { color: #e6edf3; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; }
|
||||||
|
|||||||
Reference in New Issue
Block a user