@springbrand/message-panel 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/demo/camel-chat-showcase.d.ts +1 -0
- package/dist/demo/camel-chat-showcase.js +1007 -0
- package/dist/demo/fixtures.d.ts +32 -0
- package/dist/demo/fixtures.js +250 -0
- package/dist/demo/index.js +4 -0
- package/dist/demo/message-panel-chat-showcase.d.ts +1 -0
- package/dist/demo/message-panel-chat-showcase.js +114 -0
- package/dist/demo/message-panel-gallery.d.ts +1 -0
- package/dist/demo/message-panel-gallery.js +130 -0
- package/dist/src/adapters/ai-sdk.d.ts +4 -0
- package/dist/src/adapters/ai-sdk.js +25 -0
- package/dist/src/camel/camel-chat-messages.d.ts +44 -0
- package/dist/src/camel/camel-chat-messages.js +421 -0
- package/dist/src/camel/camel-model-select.d.ts +13 -0
- package/dist/src/camel/camel-model-select.js +7 -0
- package/dist/src/camel/camel-plan-bar.d.ts +5 -0
- package/dist/src/camel/camel-plan-bar.js +24 -0
- package/dist/src/camel/camel-prompt-input.d.ts +32 -0
- package/dist/src/camel/camel-prompt-input.js +182 -0
- package/dist/src/camel/camel-tool-presentation.d.ts +21 -0
- package/dist/src/camel/camel-tool-presentation.js +784 -0
- package/dist/src/camel/camel-turn.d.ts +57 -0
- package/dist/src/camel/camel-turn.js +203 -0
- package/dist/src/camel/index.js +7 -0
- package/dist/src/camel/turn-summary-bar.d.ts +8 -0
- package/dist/src/camel/turn-summary-bar.js +12 -0
- package/dist/src/chat-summary-panel.d.ts +22 -0
- package/dist/src/chat-summary-panel.js +26 -0
- package/dist/src/composer/chat-composer.d.ts +4 -0
- package/dist/src/composer/chat-composer.js +90 -0
- package/dist/src/composer/composer-attachments.d.ts +13 -0
- package/dist/src/composer/composer-attachments.js +47 -0
- package/dist/src/composer/composer-trigger-popover.d.ts +31 -0
- package/dist/src/composer/composer-trigger-popover.js +29 -0
- package/dist/src/composer/composer.d.ts +15 -0
- package/dist/src/composer/composer.js +180 -0
- package/dist/src/composer/drop-controller.d.ts +8 -0
- package/dist/src/composer/drop-controller.js +69 -0
- package/dist/src/composer/index.js +6 -0
- package/dist/src/composer/types.d.ts +62 -0
- package/dist/src/composer/types.js +1 -0
- package/dist/src/contracts.d.ts +94 -0
- package/dist/src/contracts.js +1 -0
- package/dist/src/conversation.d.ts +13 -0
- package/dist/src/conversation.js +18 -0
- package/dist/src/index.js +12 -0
- package/dist/src/internal/cn.d.ts +1 -0
- package/dist/src/internal/cn.js +3 -0
- package/dist/src/message-panel.d.ts +4 -0
- package/dist/src/message-panel.js +144 -0
- package/dist/src/message.d.ts +36 -0
- package/dist/src/message.js +83 -0
- package/dist/src/parts/index.d.ts +187 -0
- package/dist/src/parts/index.js +332 -0
- package/dist/src/parts/kind.d.ts +3 -0
- package/dist/src/parts/kind.js +69 -0
- package/dist/src/parts/plan.d.ts +10 -0
- package/dist/src/parts/plan.js +40 -0
- package/dist/src/parts/primitives.d.ts +70 -0
- package/dist/src/parts/primitives.js +153 -0
- package/dist/src/registry.d.ts +4 -0
- package/dist/src/registry.js +31 -0
- package/dist/src/status.d.ts +26 -0
- package/dist/src/status.js +74 -0
- package/package.json +17 -8
- package/demo/camel-chat-showcase.tsx +0 -1265
- package/demo/fixtures.ts +0 -292
- package/demo/message-panel-chat-showcase.tsx +0 -250
- package/demo/message-panel-gallery.tsx +0 -389
- package/src/adapters/ai-sdk.ts +0 -32
- package/src/camel/camel-chat-messages.tsx +0 -1420
- package/src/camel/camel-model-select.tsx +0 -88
- package/src/camel/camel-plan-bar.tsx +0 -115
- package/src/camel/camel-prompt-input.tsx +0 -540
- package/src/camel/camel-tool-presentation.tsx +0 -1270
- package/src/camel/camel-turn.ts +0 -315
- package/src/camel/turn-summary-bar.tsx +0 -81
- package/src/chat-summary-panel.tsx +0 -229
- package/src/composer/chat-composer.tsx +0 -216
- package/src/composer/composer-attachments.tsx +0 -138
- package/src/composer/composer-trigger-popover.tsx +0 -236
- package/src/composer/composer.tsx +0 -425
- package/src/composer/drop-controller.ts +0 -80
- package/src/composer/types.ts +0 -79
- package/src/contracts.ts +0 -139
- package/src/conversation.tsx +0 -76
- package/src/internal/cn.ts +0 -3
- package/src/message-panel.tsx +0 -368
- package/src/message.tsx +0 -254
- package/src/parts/index.tsx +0 -1149
- package/src/parts/kind.ts +0 -67
- package/src/parts/plan.ts +0 -63
- package/src/parts/primitives.tsx +0 -439
- package/src/registry.ts +0 -44
- package/src/status.tsx +0 -132
- /package/{demo/index.ts → dist/demo/index.d.ts} +0 -0
- /package/{src/camel/index.ts → dist/src/camel/index.d.ts} +0 -0
- /package/{src/composer/index.ts → dist/src/composer/index.d.ts} +0 -0
- /package/{src/index.ts → dist/src/index.d.ts} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
type UnknownRecord = Record<string, unknown>;
|
|
3
|
+
export type CamelPart = UIMessage["parts"][number];
|
|
4
|
+
export interface CamelMessageMetadata extends UnknownRecord {
|
|
5
|
+
authorDisplayName?: string;
|
|
6
|
+
completedAt?: number;
|
|
7
|
+
createdAt?: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
interruptedByUser?: boolean;
|
|
10
|
+
messageSource?: string;
|
|
11
|
+
turnDurationMs?: number;
|
|
12
|
+
turnStartedAt?: number;
|
|
13
|
+
turnStatus?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CamelPartRef {
|
|
16
|
+
messageId: string;
|
|
17
|
+
partIndex: number;
|
|
18
|
+
part: CamelPart;
|
|
19
|
+
}
|
|
20
|
+
export interface CamelAssistantTurn {
|
|
21
|
+
kind: "assistant";
|
|
22
|
+
key: string;
|
|
23
|
+
message: UIMessage;
|
|
24
|
+
messages: readonly [UIMessage];
|
|
25
|
+
actionMessageId: string;
|
|
26
|
+
parts: readonly CamelPartRef[];
|
|
27
|
+
traceParts: readonly CamelPartRef[];
|
|
28
|
+
finalParts: readonly CamelPartRef[];
|
|
29
|
+
terminalState?: {
|
|
30
|
+
kind: "interrupted" | "error";
|
|
31
|
+
title: string;
|
|
32
|
+
body?: string;
|
|
33
|
+
};
|
|
34
|
+
stepCount: number;
|
|
35
|
+
copyText: string;
|
|
36
|
+
replyText?: string;
|
|
37
|
+
durationMs?: number;
|
|
38
|
+
completedAt?: number;
|
|
39
|
+
}
|
|
40
|
+
export type CamelStandaloneKind = "user" | "notice" | "slash-command" | "local-command-output" | "compact-summary";
|
|
41
|
+
export interface CamelStandaloneMessage {
|
|
42
|
+
kind: "message";
|
|
43
|
+
key: string;
|
|
44
|
+
message: UIMessage;
|
|
45
|
+
displayKind: CamelStandaloneKind;
|
|
46
|
+
}
|
|
47
|
+
export type CamelMessageItem = CamelAssistantTurn | CamelStandaloneMessage;
|
|
48
|
+
export declare function camelMetadata(message: UIMessage): CamelMessageMetadata;
|
|
49
|
+
export declare const camelMessageMetadata: typeof camelMetadata;
|
|
50
|
+
export declare function camelMessageText(message: UIMessage): string;
|
|
51
|
+
export declare function camelCreatedAt(message: UIMessage): number | undefined;
|
|
52
|
+
export declare function camelCompletedAt(message: UIMessage): number | undefined;
|
|
53
|
+
export declare function formatCamelMessageTime(timestamp: number): string;
|
|
54
|
+
export declare function formatCamelTurnDuration(milliseconds: number): string;
|
|
55
|
+
export declare function projectCamelAssistantTurn(message: UIMessage, finalToolNames?: readonly string[], replyText?: string): CamelAssistantTurn;
|
|
56
|
+
export declare function projectCamelMessages(messages: readonly UIMessage[], finalToolNames?: readonly string[]): CamelMessageItem[];
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { resolvePartKind, toolNameOfPart } from "../parts/kind";
|
|
2
|
+
const USER_STOP_REASON = "Stopped by user";
|
|
3
|
+
const TRACE_PART_KINDS = new Set(["tool", "parallel", "subagents"]);
|
|
4
|
+
function recordOf(value) {
|
|
5
|
+
return value != null && typeof value === "object"
|
|
6
|
+
? value
|
|
7
|
+
: {};
|
|
8
|
+
}
|
|
9
|
+
function nonNegativeNumber(value) {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
11
|
+
? value
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
14
|
+
export function camelMetadata(message) {
|
|
15
|
+
return recordOf(message.metadata);
|
|
16
|
+
}
|
|
17
|
+
export const camelMessageMetadata = camelMetadata;
|
|
18
|
+
function isVisibleText(part) {
|
|
19
|
+
return part.type === "text" && part.text.trim().length > 0;
|
|
20
|
+
}
|
|
21
|
+
function isVisibleReasoning(part) {
|
|
22
|
+
return part.type === "reasoning" && part.text.trim().length > 0;
|
|
23
|
+
}
|
|
24
|
+
function standalonePartKind(part) {
|
|
25
|
+
switch (part.type) {
|
|
26
|
+
case "data-notice":
|
|
27
|
+
return "notice";
|
|
28
|
+
case "data-slash-command":
|
|
29
|
+
return "slash-command";
|
|
30
|
+
case "data-local-command-output":
|
|
31
|
+
return "local-command-output";
|
|
32
|
+
case "data-compact-summary":
|
|
33
|
+
return "compact-summary";
|
|
34
|
+
default:
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function standaloneKind(message) {
|
|
39
|
+
for (const part of message.parts) {
|
|
40
|
+
const kind = standalonePartKind(part);
|
|
41
|
+
if (kind !== null)
|
|
42
|
+
return kind;
|
|
43
|
+
}
|
|
44
|
+
return message.role === "user" ? "user" : null;
|
|
45
|
+
}
|
|
46
|
+
function isDirectUserMessage(message) {
|
|
47
|
+
return message.role === "user" && standaloneKind(message) === "user";
|
|
48
|
+
}
|
|
49
|
+
export function camelMessageText(message) {
|
|
50
|
+
return message.parts
|
|
51
|
+
.filter(isVisibleText)
|
|
52
|
+
.map((part) => part.text)
|
|
53
|
+
.join("\n");
|
|
54
|
+
}
|
|
55
|
+
export function camelCreatedAt(message) {
|
|
56
|
+
return nonNegativeNumber(camelMetadata(message).createdAt);
|
|
57
|
+
}
|
|
58
|
+
export function camelCompletedAt(message) {
|
|
59
|
+
return (nonNegativeNumber(camelMetadata(message).completedAt) ??
|
|
60
|
+
camelCreatedAt(message));
|
|
61
|
+
}
|
|
62
|
+
export function formatCamelMessageTime(timestamp) {
|
|
63
|
+
const date = new Date(timestamp);
|
|
64
|
+
const now = new Date();
|
|
65
|
+
const isToday = date.getFullYear() === now.getFullYear() &&
|
|
66
|
+
date.getMonth() === now.getMonth() &&
|
|
67
|
+
date.getDate() === now.getDate();
|
|
68
|
+
return date.toLocaleString([], {
|
|
69
|
+
...(isToday ? {} : { month: "short", day: "numeric" }),
|
|
70
|
+
hour: "numeric",
|
|
71
|
+
minute: "2-digit",
|
|
72
|
+
hour12: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function formatCamelTurnDuration(milliseconds) {
|
|
76
|
+
const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
|
|
77
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
78
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
79
|
+
const seconds = totalSeconds % 60;
|
|
80
|
+
return hours > 0
|
|
81
|
+
? `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`
|
|
82
|
+
: `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
83
|
+
}
|
|
84
|
+
function isTraceWork(part, finalToolNames) {
|
|
85
|
+
if (isVisibleReasoning(part))
|
|
86
|
+
return true;
|
|
87
|
+
const kind = resolvePartKind(part);
|
|
88
|
+
if (kind === "plan")
|
|
89
|
+
return false;
|
|
90
|
+
const toolName = toolNameOfPart(part);
|
|
91
|
+
if (toolName !== null && finalToolNames.includes(toolName))
|
|
92
|
+
return false;
|
|
93
|
+
return TRACE_PART_KINDS.has(kind ?? "");
|
|
94
|
+
}
|
|
95
|
+
function isTraceBoundary(part, finalToolNames) {
|
|
96
|
+
return part.type === "step-start" || isTraceWork(part, finalToolNames);
|
|
97
|
+
}
|
|
98
|
+
function terminalState(message) {
|
|
99
|
+
const metadata = camelMetadata(message);
|
|
100
|
+
if (metadata.interruptedByUser === true) {
|
|
101
|
+
return {
|
|
102
|
+
kind: "interrupted",
|
|
103
|
+
title: USER_STOP_REASON,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (metadata.turnStatus === "aborted" &&
|
|
107
|
+
metadata.error === USER_STOP_REASON)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (metadata.turnStatus === "error" || metadata.error) {
|
|
110
|
+
return {
|
|
111
|
+
kind: "error",
|
|
112
|
+
title: "Error",
|
|
113
|
+
body: metadata.error ?? "The response failed.",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
function turnDuration(message) {
|
|
119
|
+
const metadata = camelMetadata(message);
|
|
120
|
+
const explicit = nonNegativeNumber(metadata.turnDurationMs);
|
|
121
|
+
if (explicit !== undefined)
|
|
122
|
+
return explicit;
|
|
123
|
+
const startedAt = nonNegativeNumber(metadata.turnStartedAt);
|
|
124
|
+
const completedAt = nonNegativeNumber(metadata.completedAt);
|
|
125
|
+
return startedAt !== undefined && completedAt !== undefined
|
|
126
|
+
? Math.max(0, completedAt - startedAt)
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
export function projectCamelAssistantTurn(message, finalToolNames = [], replyText) {
|
|
130
|
+
const parts = message.parts.map((part, partIndex) => ({
|
|
131
|
+
messageId: message.id,
|
|
132
|
+
partIndex,
|
|
133
|
+
part,
|
|
134
|
+
}));
|
|
135
|
+
let lastTraceBoundary = -1;
|
|
136
|
+
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
137
|
+
if (isTraceBoundary(parts[index].part, finalToolNames)) {
|
|
138
|
+
lastTraceBoundary = index;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const traceParts = [];
|
|
143
|
+
const finalParts = [];
|
|
144
|
+
for (const partRef of parts) {
|
|
145
|
+
const { part, partIndex } = partRef;
|
|
146
|
+
if (part.type === "step-start" || resolvePartKind(part) === "plan") {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (isTraceWork(part, finalToolNames) ||
|
|
150
|
+
(isVisibleText(part) && partIndex < lastTraceBoundary)) {
|
|
151
|
+
traceParts.push(partRef);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const kind = resolvePartKind(part);
|
|
155
|
+
if (isVisibleText(part) || (kind !== null && kind !== "reasoning")) {
|
|
156
|
+
finalParts.push(partRef);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const hasFinalNarrative = finalParts.some(({ part }) => isVisibleText(part) || resolvePartKind(part) === "error");
|
|
160
|
+
const terminal = terminalState(message);
|
|
161
|
+
return {
|
|
162
|
+
kind: "assistant",
|
|
163
|
+
key: `assistant-${message.id}`,
|
|
164
|
+
message,
|
|
165
|
+
messages: [message],
|
|
166
|
+
actionMessageId: message.id,
|
|
167
|
+
parts,
|
|
168
|
+
traceParts,
|
|
169
|
+
finalParts,
|
|
170
|
+
terminalState: terminal?.kind === "interrupted" || !hasFinalNarrative
|
|
171
|
+
? terminal
|
|
172
|
+
: undefined,
|
|
173
|
+
stepCount: Math.max(1, parts.filter(({ part }) => part.type === "step-start").length),
|
|
174
|
+
copyText: camelMessageText(message),
|
|
175
|
+
replyText,
|
|
176
|
+
durationMs: turnDuration(message),
|
|
177
|
+
completedAt: camelCompletedAt(message),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export function projectCamelMessages(messages, finalToolNames = []) {
|
|
181
|
+
const items = [];
|
|
182
|
+
messages.forEach((message, index) => {
|
|
183
|
+
if (message.role === "system")
|
|
184
|
+
return;
|
|
185
|
+
const displayKind = standaloneKind(message);
|
|
186
|
+
if (message.role === "assistant" && displayKind === null) {
|
|
187
|
+
const reply = messages
|
|
188
|
+
.slice(index + 1)
|
|
189
|
+
.find(isDirectUserMessage);
|
|
190
|
+
items.push(projectCamelAssistantTurn(message, finalToolNames, reply ? camelMessageText(reply).trim() || undefined : undefined));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (displayKind !== null) {
|
|
194
|
+
items.push({
|
|
195
|
+
kind: "message",
|
|
196
|
+
key: `message-${message.id}`,
|
|
197
|
+
message,
|
|
198
|
+
displayKind,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return items;
|
|
203
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./camel-chat-messages";
|
|
2
|
+
export * from "./camel-model-select";
|
|
3
|
+
export * from "./camel-plan-bar";
|
|
4
|
+
export * from "./camel-turn";
|
|
5
|
+
export * from "./turn-summary-bar";
|
|
6
|
+
export * from "./camel-prompt-input";
|
|
7
|
+
export * from "./camel-tool-presentation";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface TurnSummaryBarProps {
|
|
3
|
+
durationMs?: number;
|
|
4
|
+
stepCount: number;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
showSeparator?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function TurnSummaryBar({ durationMs, stepCount, children, showSeparator, }: TurnSummaryBarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { cn } from "../internal/cn";
|
|
5
|
+
import { formatCamelTurnDuration } from "./camel-turn";
|
|
6
|
+
export function TurnSummaryBar({ durationMs, stepCount, children, showSeparator = true, }) {
|
|
7
|
+
const [expanded, setExpanded] = useState(false);
|
|
8
|
+
const stepLabel = `${stepCount} step${stepCount === 1 ? "" : "s"}`;
|
|
9
|
+
const showDuration = durationMs !== undefined && durationMs >= 1000;
|
|
10
|
+
const toggleLabel = expanded ? "hide work" : "show work";
|
|
11
|
+
return (_jsxs("div", { "data-camel-turn-summary": "", "data-state": expanded ? "open" : "closed", children: [_jsxs("button", { type: "button", "aria-expanded": expanded, "aria-label": `${expanded ? "Hide" : "Show"} work, ${stepLabel}${showDuration ? `, ${formatCamelTurnDuration(durationMs)}` : ""}`, className: cn("group/turn-summary flex w-full cursor-pointer items-center gap-1.5 rounded py-1 text-left", "font-mono text-xs text-muted-foreground/60 transition-colors duration-150", "hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring/50"), onClick: () => setExpanded((current) => !current), children: [showDuration && (_jsxs(_Fragment, { children: [_jsx("span", { children: "worked for" }), _jsx("span", { className: "text-muted-foreground/80", children: formatCamelTurnDuration(durationMs) }), _jsx("span", { className: "text-muted-foreground/30", children: "\u00B7" })] })), _jsx("span", { className: "text-muted-foreground/80", children: stepLabel }), _jsx("span", { className: "text-muted-foreground/30", children: "\u00B7" }), _jsx("span", { children: toggleLabel }), _jsx(ChevronRightIcon, { className: cn("size-3.5 text-muted-foreground/40 transition-transform duration-250", expanded && "rotate-90"), "aria-hidden": "true" })] }), _jsx("div", { "data-camel-turn-summary-content": "", "data-camel-collapsible-content": "", "data-state": expanded ? "open" : "closed", "aria-hidden": !expanded, inert: !expanded ? true : undefined, children: expanded && (_jsx("div", { "data-camel-collapsible-inner": "", children: _jsx("div", { className: "space-y-1 py-2", "data-camel-turn-trace": "", "data-state": "open", children: children }) })) }), showSeparator && _jsx("hr", { className: "my-2 border-t border-border/40" })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ChatSummarySource {
|
|
2
|
+
kind: "file" | "link";
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ChatSummaryPanelProps {
|
|
7
|
+
sessionTitle: string;
|
|
8
|
+
messageCount: number;
|
|
9
|
+
currentGoal?: string;
|
|
10
|
+
agentTitle: string;
|
|
11
|
+
agentValue?: string;
|
|
12
|
+
stateLabel: string;
|
|
13
|
+
stateTone: "danger" | "active" | "success";
|
|
14
|
+
activityCount: number;
|
|
15
|
+
taskCount: number;
|
|
16
|
+
runningTaskCount: number;
|
|
17
|
+
completedTaskCount: number;
|
|
18
|
+
sources: readonly ChatSummarySource[];
|
|
19
|
+
defaultExpanded?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function ChatSummaryPanel({ sessionTitle, messageCount, currentGoal, agentTitle, agentValue, stateLabel, stateTone, activityCount, taskCount, runningTaskCount, completedTaskCount, sources, defaultExpanded, className, }: ChatSummaryPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ActivityIcon, BotIcon, ChevronUpIcon, FileIcon, LinkIcon, ListIcon, MessageSquareIcon, WrenchIcon, } from "lucide-react";
|
|
4
|
+
import { cn } from "./internal/cn";
|
|
5
|
+
function SummaryRow({ icon: Icon, label, value, }) {
|
|
6
|
+
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-2 rounded-md py-1.5 text-left text-sm text-foreground", children: [_jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), value && (_jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: value }))] }));
|
|
7
|
+
}
|
|
8
|
+
export function ChatSummaryPanel({ sessionTitle, messageCount, currentGoal, agentTitle, agentValue, stateLabel, stateTone, activityCount, taskCount, runningTaskCount, completedTaskCount, sources, defaultExpanded = false, className, }) {
|
|
9
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
10
|
+
const stateColor = {
|
|
11
|
+
danger: "bg-destructive",
|
|
12
|
+
active: "bg-primary",
|
|
13
|
+
success: "bg-success",
|
|
14
|
+
}[stateTone];
|
|
15
|
+
if (!expanded) {
|
|
16
|
+
return (_jsxs("button", { type: "button", className: cn("inline-flex h-8 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-background/90 px-2.5 text-xs font-medium text-muted-foreground shadow-sm backdrop-blur transition-all outline-none", "hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50", "animate-in fade-in-0 zoom-in-95 slide-in-from-top-1 duration-150 motion-reduce:animate-none", className), onClick: () => setExpanded(true), "aria-expanded": false, "aria-label": "\u5C55\u5F00\u6458\u8981", title: "\u5C55\u5F00\u6458\u8981", "data-message-panel-summary": "", "data-state": "collapsed", children: [_jsx(ListIcon, { className: "size-3.5" }), "\u6458\u8981"] }));
|
|
17
|
+
}
|
|
18
|
+
return (_jsxs("aside", { className: cn("w-[19rem] min-w-0 max-w-full overflow-hidden rounded-2xl border border-border bg-popover shadow-card", "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200 motion-reduce:animate-none", className), "data-message-panel-summary": "", "data-state": "expanded", children: [_jsxs("header", { className: "flex h-10 items-center border-b border-border px-4", children: [_jsx("span", { className: "text-sm font-medium text-muted-foreground", children: "\u6458\u8981" }), _jsx("button", { type: "button", className: "group/collapse ml-auto inline-flex size-8 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4", onClick: () => setExpanded(false), "aria-expanded": true, "aria-label": "\u6536\u8D77\u6458\u8981", title: "\u6536\u8D77\u6458\u8981", children: _jsx(ChevronUpIcon, { className: "transition-transform duration-200 group-hover/collapse:-translate-y-0.5 motion-reduce:transition-none" }) })] }), _jsx("div", { className: "max-h-[min(31rem,calc(100dvh-8rem))] overflow-y-auto", children: _jsxs("div", { className: "px-4 py-3", children: [_jsxs("section", { children: [_jsx("p", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "\u4F1A\u8BDD" }), _jsx(SummaryRow, { icon: MessageSquareIcon, label: sessionTitle, value: `${messageCount} 条消息` }), currentGoal && (_jsx("p", { className: "mb-1 ml-6 line-clamp-2 text-xs leading-5 text-muted-foreground", children: currentGoal })), _jsx(SummaryRow, { icon: BotIcon, label: agentTitle, value: agentValue }), _jsxs("div", { className: "flex items-center gap-2 py-1.5 text-sm", children: [_jsx(ActivityIcon, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1", children: "\u72B6\u6001" }), _jsx("span", { className: cn("size-1.5 rounded-full transition-colors duration-200", stateTone === "active" &&
|
|
19
|
+
"animate-pulse motion-reduce:animate-none", stateColor) }), _jsx("span", { className: "text-xs text-muted-foreground", children: stateLabel })] }), _jsx(SummaryRow, { icon: WrenchIcon, label: `${activityCount} 次工具活动` })] }), _jsxs("section", { className: "mt-3 border-t border-border pt-3", children: [_jsx("p", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "\u5B50\u4EFB\u52A1" }), _jsx(SummaryRow, { icon: BotIcon, label: taskCount === 0 ? "暂无子任务" : `${runningTaskCount} 个运行中`, value: completedTaskCount > 0
|
|
20
|
+
? `${completedTaskCount} 已完成`
|
|
21
|
+
: undefined })] }), _jsxs("section", { className: "mt-3 border-t border-border pt-3", children: [_jsx("p", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "\u6765\u6E90" }), sources.length === 0 ? (_jsx("p", { className: "py-1.5 text-xs text-muted-foreground", children: "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u9644\u4EF6\u6216\u5F15\u7528\u6765\u6E90" })) : (_jsx("div", { className: "space-y-0.5", children: sources.map((source) => {
|
|
22
|
+
const Icon = source.kind === "file" ? FileIcon : LinkIcon;
|
|
23
|
+
const content = (_jsxs(_Fragment, { children: [_jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: source.label })] }));
|
|
24
|
+
return source.href ? (_jsx("a", { href: source.href, target: "_blank", rel: "noreferrer", className: "flex items-center gap-2 rounded-md py-1.5 text-sm text-foreground hover:bg-accent", children: content }, `${source.kind}:${source.href}`)) : (_jsx("div", { className: "flex items-center gap-2 py-1.5 text-sm text-foreground", children: content }, `${source.kind}:${source.label}`));
|
|
25
|
+
}) }))] })] }) })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PlusIcon, SparklesIcon, } from "lucide-react";
|
|
3
|
+
import { forwardRef, useEffect, useMemo, useState, } from "react";
|
|
4
|
+
import { cn } from "../internal/cn";
|
|
5
|
+
import { Composer } from "./composer";
|
|
6
|
+
const toolbarIcons = {
|
|
7
|
+
plus: PlusIcon,
|
|
8
|
+
sparkles: SparklesIcon,
|
|
9
|
+
};
|
|
10
|
+
function ToolbarAction({ action }) {
|
|
11
|
+
const Icon = action.icon ? toolbarIcons[action.icon] : undefined;
|
|
12
|
+
return (_jsxs("button", { type: "button", className: "sb-chat-composer__action", disabled: action.disabled, onClick: action.onSelect, children: [Icon && _jsx(Icon, { size: 16 }), _jsx("span", { children: action.label })] }));
|
|
13
|
+
}
|
|
14
|
+
function CommandMenu({ commands, active, label, onActiveChange, onPick, }) {
|
|
15
|
+
return (_jsxs("div", { className: "sb-command-menu", role: "listbox", "aria-label": label, onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "sb-command-menu__label", children: label }), _jsx("div", { className: "sb-command-menu__list", children: commands.map((command, index) => (_jsxs("button", { type: "button", role: "option", "aria-selected": index === active, onMouseEnter: () => onActiveChange(index), onClick: () => onPick(command), children: [_jsxs("code", { children: ["/", command.name] }), _jsx("span", { children: command.description })] }, command.name))) })] }));
|
|
16
|
+
}
|
|
17
|
+
function ChatComposerInner({ value, defaultValue = "", onValueChange, commands = [], toolbarActions = [], variant = "docked", commandMenuLabel = "命令(↑↓ 选择,回车填入)", onCommandPick, onSubmit, tools, textareaProps, className, ...props }, ref) {
|
|
18
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
19
|
+
const resolvedValue = value ?? internalValue;
|
|
20
|
+
const [dismissed, setDismissed] = useState(false);
|
|
21
|
+
const [active, setActive] = useState(0);
|
|
22
|
+
const query = resolvedValue.startsWith("/") && !resolvedValue.includes("\n")
|
|
23
|
+
? resolvedValue.slice(1).trim().toLowerCase()
|
|
24
|
+
: null;
|
|
25
|
+
const filtered = useMemo(() => {
|
|
26
|
+
if (query === null)
|
|
27
|
+
return [];
|
|
28
|
+
if (!query)
|
|
29
|
+
return [...commands];
|
|
30
|
+
return commands.filter((command) => command.name.toLowerCase().includes(query) ||
|
|
31
|
+
command.description.toLowerCase().includes(query));
|
|
32
|
+
}, [commands, query]);
|
|
33
|
+
const menuOpen = !dismissed && query !== null && filtered.length > 0;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setDismissed(false);
|
|
36
|
+
}, [resolvedValue]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (active >= filtered.length)
|
|
39
|
+
setActive(0);
|
|
40
|
+
}, [active, filtered.length]);
|
|
41
|
+
const changeValue = (next) => {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
setInternalValue(next);
|
|
44
|
+
onValueChange?.(next);
|
|
45
|
+
};
|
|
46
|
+
const pick = (command) => {
|
|
47
|
+
changeValue(command.prompt);
|
|
48
|
+
setDismissed(true);
|
|
49
|
+
onCommandPick?.(command);
|
|
50
|
+
};
|
|
51
|
+
const handleKeyDown = (event) => {
|
|
52
|
+
textareaProps?.onKeyDown?.(event);
|
|
53
|
+
if (event.defaultPrevented || !menuOpen)
|
|
54
|
+
return;
|
|
55
|
+
if (event.key === "Escape") {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
setDismissed(true);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
63
|
+
setActive((current) => (current + delta + filtered.length) % filtered.length);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if ((event.key === "Enter" &&
|
|
67
|
+
!event.shiftKey &&
|
|
68
|
+
!event.nativeEvent.isComposing) ||
|
|
69
|
+
event.key === "Tab") {
|
|
70
|
+
const command = filtered[active] ?? filtered[0];
|
|
71
|
+
if (command) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
pick(command);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return (_jsx("div", { ref: ref, className: cn("sb-chat-composer", className), "data-variant": variant, children: _jsxs("div", { className: "sb-chat-composer__inner", children: [menuOpen && (_jsx(CommandMenu, { commands: filtered, active: active, label: commandMenuLabel, onActiveChange: setActive, onPick: pick })), _jsx(Composer, { ...props, value: resolvedValue, onValueChange: changeValue, onSubmit: async (message) => {
|
|
78
|
+
await onSubmit(message);
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
setInternalValue("");
|
|
81
|
+
}, tools: _jsxs(_Fragment, { children: [tools, toolbarActions.map((action) => (_jsx(ToolbarAction, { action: action }, action.id)))] }), textareaProps: {
|
|
82
|
+
...textareaProps,
|
|
83
|
+
onKeyDown: handleKeyDown,
|
|
84
|
+
onBlur: (event) => {
|
|
85
|
+
textareaProps?.onBlur?.(event);
|
|
86
|
+
setDismissed(true);
|
|
87
|
+
},
|
|
88
|
+
} })] }) }));
|
|
89
|
+
}
|
|
90
|
+
export const ChatComposer = forwardRef(ChatComposerInner);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import type { ComposerAttachmentView } from "./types";
|
|
3
|
+
export declare const Attachments: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export interface AttachmentPreviewProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
attachment: ComposerAttachmentView;
|
|
6
|
+
actions?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare const AttachmentPreview: import("react").ForwardRefExoticComponent<AttachmentPreviewProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
9
|
+
export declare const AttachmentRemove: import("react").ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
export declare function AttachmentRetry(props: ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
|
|
11
|
+
export declare function AttachmentReorder({ direction, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
12
|
+
direction: "left" | "right";
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowLeftIcon, ArrowRightIcon, FileAudioIcon, FileCodeIcon, FileImageIcon, FileSpreadsheetIcon, FileTextIcon, Loader2Icon, RotateCcwIcon, XIcon, } from "lucide-react";
|
|
3
|
+
import { forwardRef, } from "react";
|
|
4
|
+
import { cn } from "../internal/cn";
|
|
5
|
+
function formatSize(bytes) {
|
|
6
|
+
if (!bytes)
|
|
7
|
+
return "";
|
|
8
|
+
if (bytes < 1024)
|
|
9
|
+
return `${bytes} B`;
|
|
10
|
+
if (bytes < 1024 * 1024)
|
|
11
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
12
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
13
|
+
}
|
|
14
|
+
function attachmentIcon(attachment) {
|
|
15
|
+
const value = `${attachment.mediaType ?? ""} ${attachment.name}`;
|
|
16
|
+
if (/image/i.test(value))
|
|
17
|
+
return FileImageIcon;
|
|
18
|
+
if (/audio/i.test(value))
|
|
19
|
+
return FileAudioIcon;
|
|
20
|
+
if (/csv|sheet|excel/i.test(value))
|
|
21
|
+
return FileSpreadsheetIcon;
|
|
22
|
+
if (/code|javascript|typescript|json|html|xml/i.test(value))
|
|
23
|
+
return FileCodeIcon;
|
|
24
|
+
return FileTextIcon;
|
|
25
|
+
}
|
|
26
|
+
export const Attachments = forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("sb-attachments", className), ...props })));
|
|
27
|
+
Attachments.displayName = "Attachments";
|
|
28
|
+
export const AttachmentPreview = forwardRef(({ attachment, actions, className, ...props }, ref) => {
|
|
29
|
+
const Icon = attachmentIcon(attachment);
|
|
30
|
+
const isImage = attachment.mediaType?.startsWith("image/") && Boolean(attachment.url);
|
|
31
|
+
const subtitle = attachment.status === "uploading"
|
|
32
|
+
? "上传中…"
|
|
33
|
+
: attachment.status === "error"
|
|
34
|
+
? "上传失败"
|
|
35
|
+
: [attachment.note, formatSize(attachment.size)].filter(Boolean).join(" · ");
|
|
36
|
+
return (_jsxs("div", { ref: ref, className: cn("sb-attachment", className), "data-status": attachment.status, ...props, children: [_jsx("span", { className: "sb-attachment__preview", children: attachment.status === "uploading" ? (_jsx(Loader2Icon, { size: 18, className: "sb-spin" })) : isImage ? (_jsx("img", { src: attachment.url, alt: "" })) : (_jsx(Icon, { size: 20 })) }), _jsxs("span", { className: "sb-attachment__text", children: [_jsx("strong", { title: attachment.name, children: attachment.name }), _jsx("small", { children: subtitle })] }), actions && _jsx("span", { className: "sb-attachment__actions", children: actions })] }));
|
|
37
|
+
});
|
|
38
|
+
AttachmentPreview.displayName = "AttachmentPreview";
|
|
39
|
+
export const AttachmentRemove = forwardRef(({ className, children, ...props }, ref) => (_jsx("button", { ref: ref, type: "button", className: cn("sb-attachment__button", className), "aria-label": "\u79FB\u9664\u9644\u4EF6", ...props, children: children ?? _jsx(XIcon, { size: 13 }) })));
|
|
40
|
+
AttachmentRemove.displayName = "AttachmentRemove";
|
|
41
|
+
export function AttachmentRetry(props) {
|
|
42
|
+
return (_jsx("button", { type: "button", className: cn("sb-attachment__button", props.className), "aria-label": "\u91CD\u8BD5\u4E0A\u4F20", ...props, children: _jsx(RotateCcwIcon, { size: 13 }) }));
|
|
43
|
+
}
|
|
44
|
+
export function AttachmentReorder({ direction, ...props }) {
|
|
45
|
+
const Icon = direction === "left" ? ArrowLeftIcon : ArrowRightIcon;
|
|
46
|
+
return (_jsx("button", { type: "button", className: cn("sb-attachment__button", props.className), "aria-label": direction === "left" ? "附件左移" : "附件右移", ...props, children: _jsx(Icon, { size: 13 }) }));
|
|
47
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComposerPrimitive, type Unstable_DirectiveFormatter, type Unstable_TriggerItem } from "@assistant-ui/react";
|
|
2
|
+
import { type ComponentPropsWithoutRef, type FC } from "react";
|
|
3
|
+
type IconComponent = FC<{
|
|
4
|
+
className?: string;
|
|
5
|
+
}>;
|
|
6
|
+
type DirectiveBehaviorProps = {
|
|
7
|
+
formatter?: Unstable_DirectiveFormatter;
|
|
8
|
+
onInserted?: (item: Unstable_TriggerItem) => void;
|
|
9
|
+
};
|
|
10
|
+
type ActionBehaviorProps = {
|
|
11
|
+
formatter?: Unstable_DirectiveFormatter;
|
|
12
|
+
onExecute: (item: Unstable_TriggerItem) => void;
|
|
13
|
+
removeOnExecute?: boolean;
|
|
14
|
+
};
|
|
15
|
+
type ComposerTriggerPopoverBaseProps = Omit<ComponentPropsWithoutRef<typeof ComposerPrimitive.Unstable_TriggerPopover>, "children"> & {
|
|
16
|
+
iconMap?: Record<string, IconComponent>;
|
|
17
|
+
fallbackIcon?: IconComponent;
|
|
18
|
+
backLabel?: string;
|
|
19
|
+
emptyCategoriesLabel?: string;
|
|
20
|
+
emptyItemsLabel?: string;
|
|
21
|
+
loadingLabel?: string;
|
|
22
|
+
};
|
|
23
|
+
export type ComposerTriggerPopoverProps = ComposerTriggerPopoverBaseProps & ({
|
|
24
|
+
directive: DirectiveBehaviorProps;
|
|
25
|
+
action?: never;
|
|
26
|
+
} | {
|
|
27
|
+
action: ActionBehaviorProps;
|
|
28
|
+
directive?: never;
|
|
29
|
+
});
|
|
30
|
+
export declare const ComposerTriggerPopover: FC<ComposerTriggerPopoverProps>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ComposerPrimitive, unstable_defaultDirectiveFormatter, unstable_useTriggerPopoverScopeContext, } from "@assistant-ui/react";
|
|
3
|
+
import { ChevronLeftIcon, ChevronRightIcon, TerminalIcon, } from "lucide-react";
|
|
4
|
+
import { memo, } from "react";
|
|
5
|
+
import { cn } from "../internal/cn";
|
|
6
|
+
function resolveIcon(iconKey, iconMap, fallback) {
|
|
7
|
+
return (iconKey && iconMap?.[iconKey]) || fallback;
|
|
8
|
+
}
|
|
9
|
+
function Categories({ iconMap, fallbackIcon, emptyLabel, }) {
|
|
10
|
+
return (_jsx(ComposerPrimitive.Unstable_TriggerPopoverCategories, { children: (categories) => (_jsxs("div", { className: "flex flex-col py-1", children: [categories.map((category) => {
|
|
11
|
+
const Icon = resolveIcon(category.id, iconMap, fallbackIcon);
|
|
12
|
+
return (_jsxs(ComposerPrimitive.Unstable_TriggerPopoverCategoryItem, { categoryId: category.id, className: "flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-sm outline-none transition-colors hover:bg-accent focus:bg-accent data-[highlighted]:bg-accent", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Icon, { className: "size-4 text-muted-foreground" }), category.label] }), _jsx(ChevronRightIcon, { className: "size-4 text-muted-foreground" })] }, category.id));
|
|
13
|
+
}), categories.length === 0 && (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyLabel }))] })) }));
|
|
14
|
+
}
|
|
15
|
+
function Items({ iconMap, fallbackIcon, backLabel, emptyLabel, loadingLabel, }) {
|
|
16
|
+
const { isLoading } = unstable_useTriggerPopoverScopeContext();
|
|
17
|
+
return (_jsx(ComposerPrimitive.Unstable_TriggerPopoverItems, { children: (items) => (_jsxs("div", { className: "flex flex-col", children: [_jsxs(ComposerPrimitive.Unstable_TriggerPopoverBack, { className: "flex cursor-pointer items-center gap-1.5 border-b px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground transition-colors hover:bg-accent", children: [_jsx(ChevronLeftIcon, { className: "size-3.5" }), backLabel] }), _jsxs("div", { className: "py-1", children: [items.map((item, index) => {
|
|
18
|
+
const iconKey = typeof item.metadata?.icon === "string"
|
|
19
|
+
? item.metadata.icon
|
|
20
|
+
: undefined;
|
|
21
|
+
const Icon = resolveIcon(iconKey, iconMap, fallbackIcon);
|
|
22
|
+
return (_jsxs(ComposerPrimitive.Unstable_TriggerPopoverItem, { item: item, index: index, className: "flex w-full cursor-pointer flex-col items-start gap-0.5 px-3 py-2 text-start outline-none transition-colors hover:bg-accent focus:bg-accent data-[highlighted]:bg-accent", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(Icon, { className: "size-3.5 text-primary" }), item.label] }), item.description && (_jsx("span", { className: "ms-5.5 text-xs leading-tight text-muted-foreground", children: item.description }))] }, item.id));
|
|
23
|
+
}), items.length === 0 && (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: isLoading ? loadingLabel : emptyLabel }))] })] })) }));
|
|
24
|
+
}
|
|
25
|
+
const ComposerTriggerPopoverImpl = ({ iconMap, fallbackIcon = TerminalIcon, backLabel = "Back", emptyCategoriesLabel = "No items available", emptyItemsLabel = "No matching items", loadingLabel = "Loading…", className, directive, action, ...props }) => (_jsxs(ComposerPrimitive.Unstable_TriggerPopover, { className: cn("absolute bottom-full start-0 z-50 mb-2 w-64 overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg", className), ...props, children: [directive ? (_jsx(ComposerPrimitive.Unstable_TriggerPopover.Directive, { formatter: directive.formatter ??
|
|
26
|
+
unstable_defaultDirectiveFormatter, onInserted: directive.onInserted })) : (_jsx(ComposerPrimitive.Unstable_TriggerPopover.Action, { formatter: action.formatter ?? unstable_defaultDirectiveFormatter, onExecute: action.onExecute, removeOnExecute: action.removeOnExecute })), _jsx(Categories, { iconMap: iconMap, fallbackIcon: fallbackIcon, emptyLabel: emptyCategoriesLabel }), _jsx(Items, { iconMap: iconMap, fallbackIcon: fallbackIcon, backLabel: backLabel, emptyLabel: emptyItemsLabel, loadingLabel: loadingLabel })] }));
|
|
27
|
+
ComposerTriggerPopoverImpl.displayName =
|
|
28
|
+
"ComposerTriggerPopover";
|
|
29
|
+
export const ComposerTriggerPopover = memo(ComposerTriggerPopoverImpl);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type HTMLAttributes } from "react";
|
|
2
|
+
import type { ComposerProps } from "./types";
|
|
3
|
+
export declare const ComposerHeader: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export declare const ComposerBody: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
5
|
+
export declare const ComposerFooter: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
6
|
+
export declare const ComposerTools: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export declare const ComposerSubmit: import("react").ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
8
|
+
status?: "idle" | "submitted" | "streaming";
|
|
9
|
+
} & import("react").RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
export declare function DropOverlay({ label, }: {
|
|
11
|
+
label?: string;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
13
|
+
export declare const Composer: <TAttachment>(props: ComposerProps<TAttachment> & {
|
|
14
|
+
ref?: React.ForwardedRef<HTMLDivElement>;
|
|
15
|
+
}) => React.ReactElement;
|