@flanksource/clicky-ui 0.2.16 → 0.2.18
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/chat.cjs +16 -0
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.js +17 -1
- package/dist/chat.js.map +1 -1
- package/dist/components/Combobox.cjs +19 -4
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +19 -4
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/FilterBar.cjs +45 -6
- package/dist/components/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar.d.ts +28 -0
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +45 -6
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/JsonSchemaForm.cjs +26 -3
- package/dist/components/JsonSchemaForm.cjs.map +1 -1
- package/dist/components/JsonSchemaForm.d.ts.map +1 -1
- package/dist/components/JsonSchemaForm.js +26 -3
- package/dist/components/JsonSchemaForm.js.map +1 -1
- package/dist/components/json-schema-form-preferences.cjs +4 -0
- package/dist/components/json-schema-form-preferences.cjs.map +1 -1
- package/dist/components/json-schema-form-preferences.d.ts +2 -0
- package/dist/components/json-schema-form-preferences.d.ts.map +1 -1
- package/dist/components/json-schema-form-preferences.js +4 -0
- package/dist/components/json-schema-form-preferences.js.map +1 -1
- package/dist/components/json-schema-form-render.cjs +1 -1
- package/dist/components/json-schema-form-render.cjs.map +1 -1
- package/dist/components/json-schema-form-render.d.ts.map +1 -1
- package/dist/components/json-schema-form-render.js +2 -2
- package/dist/components/json-schema-form-render.js.map +1 -1
- package/dist/components/json-schema-form-types.d.ts +5 -2
- package/dist/components/json-schema-form-types.d.ts.map +1 -1
- package/dist/components/json-schema-form-utils.cjs +13 -0
- package/dist/components/json-schema-form-utils.cjs.map +1 -1
- package/dist/components/json-schema-form-utils.d.ts +2 -0
- package/dist/components/json-schema-form-utils.d.ts.map +1 -1
- package/dist/components/json-schema-form-utils.js +13 -0
- package/dist/components/json-schema-form-utils.js.map +1 -1
- package/dist/components/loading.cjs +19 -6
- package/dist/components/loading.cjs.map +1 -1
- package/dist/components/loading.d.ts.map +1 -1
- package/dist/components/loading.js +19 -6
- package/dist/components/loading.js.map +1 -1
- package/dist/components.cjs +1 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +2 -1
- package/dist/data/Markdown.cjs +14 -40
- package/dist/data/Markdown.cjs.map +1 -1
- package/dist/data/Markdown.d.ts +3 -0
- package/dist/data/Markdown.d.ts.map +1 -1
- package/dist/data/Markdown.js +14 -40
- package/dist/data/Markdown.js.map +1 -1
- package/dist/data/TaskProgress.cjs +22 -0
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.js +22 -0
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/TaskSnapshot.cjs.map +1 -1
- package/dist/data/TaskSnapshot.d.ts +5 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -1
- package/dist/data/TaskSnapshot.js.map +1 -1
- package/dist/data/Tree.cjs +32 -1
- package/dist/data/Tree.cjs.map +1 -1
- package/dist/data/Tree.d.ts +9 -1
- package/dist/data/Tree.d.ts.map +1 -1
- package/dist/data/Tree.js +32 -1
- package/dist/data/Tree.js.map +1 -1
- package/dist/data/chat/Attachment.cjs +79 -0
- package/dist/data/chat/Attachment.cjs.map +1 -0
- package/dist/data/chat/Attachment.d.ts +18 -0
- package/dist/data/chat/Attachment.d.ts.map +1 -0
- package/dist/data/chat/Attachment.js +79 -0
- package/dist/data/chat/Attachment.js.map +1 -0
- package/dist/data/chat/Chat.cjs +71 -14
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts +30 -12
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.fixtures.d.ts +9 -0
- package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
- package/dist/data/chat/Chat.js +73 -16
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/Conversation.cjs +32 -14
- package/dist/data/chat/Conversation.cjs.map +1 -1
- package/dist/data/chat/Conversation.d.ts +5 -3
- package/dist/data/chat/Conversation.d.ts.map +1 -1
- package/dist/data/chat/Conversation.js +34 -16
- package/dist/data/chat/Conversation.js.map +1 -1
- package/dist/data/chat/Message.cjs +73 -14
- package/dist/data/chat/Message.cjs.map +1 -1
- package/dist/data/chat/Message.d.ts +11 -4
- package/dist/data/chat/Message.d.ts.map +1 -1
- package/dist/data/chat/Message.js +75 -16
- package/dist/data/chat/Message.js.map +1 -1
- package/dist/data/chat/MessageActions.cjs +54 -0
- package/dist/data/chat/MessageActions.cjs.map +1 -0
- package/dist/data/chat/MessageActions.d.ts +11 -0
- package/dist/data/chat/MessageActions.d.ts.map +1 -0
- package/dist/data/chat/MessageActions.js +54 -0
- package/dist/data/chat/MessageActions.js.map +1 -0
- package/dist/data/chat/ModelSelector.cjs +43 -0
- package/dist/data/chat/ModelSelector.cjs.map +1 -0
- package/dist/data/chat/ModelSelector.d.ts +22 -0
- package/dist/data/chat/ModelSelector.d.ts.map +1 -0
- package/dist/data/chat/ModelSelector.js +43 -0
- package/dist/data/chat/ModelSelector.js.map +1 -0
- package/dist/data/chat/PromptInput.cjs +28 -15
- package/dist/data/chat/PromptInput.cjs.map +1 -1
- package/dist/data/chat/PromptInput.d.ts +14 -8
- package/dist/data/chat/PromptInput.d.ts.map +1 -1
- package/dist/data/chat/PromptInput.js +28 -15
- package/dist/data/chat/PromptInput.js.map +1 -1
- package/dist/data/chat/Reasoning.cjs +37 -0
- package/dist/data/chat/Reasoning.cjs.map +1 -0
- package/dist/data/chat/Reasoning.d.ts +11 -0
- package/dist/data/chat/Reasoning.d.ts.map +1 -0
- package/dist/data/chat/Reasoning.js +37 -0
- package/dist/data/chat/Reasoning.js.map +1 -0
- package/dist/data/chat/Suggestion.cjs +20 -0
- package/dist/data/chat/Suggestion.cjs.map +1 -0
- package/dist/data/chat/Suggestion.d.ts +10 -0
- package/dist/data/chat/Suggestion.d.ts.map +1 -0
- package/dist/data/chat/Suggestion.js +20 -0
- package/dist/data/chat/Suggestion.js.map +1 -0
- package/dist/data/chat/ToolCall.cjs +26 -3
- package/dist/data/chat/ToolCall.cjs.map +1 -1
- package/dist/data/chat/ToolCall.d.ts +5 -1
- package/dist/data/chat/ToolCall.d.ts.map +1 -1
- package/dist/data/chat/ToolCall.js +26 -3
- package/dist/data/chat/ToolCall.js.map +1 -1
- package/dist/data/chat/index.d.ts +7 -2
- package/dist/data/chat/index.d.ts.map +1 -1
- package/dist/data/chat/types.cjs +16 -0
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +29 -2
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js +16 -0
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.cjs +8 -1
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +8 -1
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +6 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +5 -1
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +6 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/test-runner/context.cjs.map +1 -1
- package/dist/data/test-runner/context.d.ts +7 -0
- package/dist/data/test-runner/context.d.ts.map +1 -1
- package/dist/data/test-runner/context.js.map +1 -1
- package/dist/data/test-runner/index.d.ts +1 -0
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/routePath.cjs +39 -0
- package/dist/data/test-runner/routePath.cjs.map +1 -0
- package/dist/data/test-runner/routePath.d.ts +11 -0
- package/dist/data/test-runner/routePath.d.ts.map +1 -0
- package/dist/data/test-runner/routePath.js +39 -0
- package/dist/data/test-runner/routePath.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +4 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.js +4 -0
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-task-run.cjs +33 -20
- package/dist/hooks/use-task-run.cjs.map +1 -1
- package/dist/hooks/use-task-run.d.ts.map +1 -1
- package/dist/hooks/use-task-run.js +33 -20
- package/dist/hooks/use-task-run.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
package/dist/data/chat/Chat.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
3
3
|
import { useChat } from "@ai-sdk/react";
|
|
4
|
-
import { DefaultChatTransport } from "ai";
|
|
4
|
+
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { Conversation } from "./Conversation.js";
|
|
7
7
|
import { PromptInput } from "./PromptInput.js";
|
|
8
|
+
import { Suggestions } from "./Suggestion.js";
|
|
9
|
+
import { ModelSelector, EffortSelector } from "./ModelSelector.js";
|
|
10
|
+
const DEFAULT_EFFORTS = ["low", "medium", "high"];
|
|
8
11
|
function Chat({
|
|
9
12
|
api = "/api/chat",
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
models: modelsProp,
|
|
14
|
+
modelsApi = "/api/chat/models",
|
|
15
|
+
defaultModel,
|
|
16
|
+
reasoningEfforts = DEFAULT_EFFORTS,
|
|
17
|
+
defaultReasoningEffort = "",
|
|
18
|
+
onModelChange,
|
|
19
|
+
suggestions,
|
|
20
|
+
enableAttachments = false,
|
|
21
|
+
toolApproval,
|
|
22
|
+
threadId,
|
|
12
23
|
body,
|
|
13
24
|
transport,
|
|
14
25
|
initialMessages,
|
|
@@ -16,28 +27,74 @@ function Chat({
|
|
|
16
27
|
emptyState,
|
|
17
28
|
className
|
|
18
29
|
}) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const [models, setModels] = useState(modelsProp ?? []);
|
|
31
|
+
const [model, setModel] = useState(defaultModel);
|
|
32
|
+
const [effort, setEffort] = useState(defaultReasoningEffort);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (modelsProp || !modelsApi) return;
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
fetch(modelsApi).then((r) => r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`))).then((data) => {
|
|
37
|
+
if (cancelled) return;
|
|
38
|
+
setModels(data);
|
|
39
|
+
setModel((m) => {
|
|
40
|
+
var _a;
|
|
41
|
+
return m ?? ((_a = data.find((d) => d.configured !== false)) == null ? void 0 : _a.id);
|
|
42
|
+
});
|
|
43
|
+
}).catch((err) => console.warn("clicky-ui: failed to load chat models", err));
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
};
|
|
47
|
+
}, [modelsProp, modelsApi]);
|
|
48
|
+
const selectedModel = models.find((m) => m.id === model);
|
|
49
|
+
const showEffort = !selectedModel || selectedModel.reasoning;
|
|
50
|
+
const bodyRef = useRef({});
|
|
51
|
+
bodyRef.current = {
|
|
52
|
+
...body,
|
|
53
|
+
...model ? { model } : {},
|
|
54
|
+
...effort ? { reasoningEffort: effort } : {},
|
|
55
|
+
...toolApproval ? { toolApproval } : {},
|
|
56
|
+
...threadId ? { threadId } : {}
|
|
57
|
+
};
|
|
58
|
+
const resolvedTransport = useMemo(
|
|
59
|
+
() => transport ?? new DefaultChatTransport({ api, body: () => bodyRef.current }),
|
|
60
|
+
[transport, api]
|
|
61
|
+
);
|
|
62
|
+
const { messages, sendMessage, regenerate, addToolApprovalResponse, status, stop } = useChat({
|
|
29
63
|
transport: resolvedTransport,
|
|
64
|
+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
|
|
30
65
|
...initialMessages ? { messages: initialMessages } : {}
|
|
31
66
|
});
|
|
67
|
+
const onModelSelect = (id) => {
|
|
68
|
+
setModel(id);
|
|
69
|
+
onModelChange == null ? void 0 : onModelChange(id);
|
|
70
|
+
};
|
|
71
|
+
const toolbar = models.length > 0 || showEffort ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
72
|
+
/* @__PURE__ */ jsx(ModelSelector, { models, value: model, onChange: onModelSelect }),
|
|
73
|
+
showEffort && /* @__PURE__ */ jsx(EffortSelector, { efforts: reasoningEfforts, value: effort, onChange: setEffort })
|
|
74
|
+
] }) : void 0;
|
|
75
|
+
const empty = messages.length === 0 && (emptyState || (suggestions == null ? void 0 : suggestions.length)) ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
76
|
+
emptyState,
|
|
77
|
+
suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx(Suggestions, { suggestions, onSelect: (text) => void sendMessage({ text }) })
|
|
78
|
+
] }) : void 0;
|
|
32
79
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full flex-col", className), children: [
|
|
33
|
-
/* @__PURE__ */ jsx(
|
|
80
|
+
/* @__PURE__ */ jsx(
|
|
81
|
+
Conversation,
|
|
82
|
+
{
|
|
83
|
+
messages,
|
|
84
|
+
emptyState: empty,
|
|
85
|
+
onRegenerate: (messageId) => void regenerate({ messageId }),
|
|
86
|
+
onApprove: (id, approved, reason) => void addToolApprovalResponse(reason ? { id, approved, reason } : { id, approved })
|
|
87
|
+
}
|
|
88
|
+
),
|
|
34
89
|
/* @__PURE__ */ jsx("div", { className: "p-4 pt-0", children: /* @__PURE__ */ jsx(
|
|
35
90
|
PromptInput,
|
|
36
91
|
{
|
|
37
92
|
status,
|
|
38
93
|
onStop: () => void stop(),
|
|
39
94
|
placeholder,
|
|
40
|
-
|
|
95
|
+
enableAttachments,
|
|
96
|
+
toolbar,
|
|
97
|
+
onSubmit: (text, files) => void sendMessage({ text, files })
|
|
41
98
|
}
|
|
42
99
|
) })
|
|
43
100
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport {
|
|
1
|
+
{"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport {\n DefaultChatTransport,\n lastAssistantMessageIsCompleteWithApprovalResponses,\n type ChatTransport,\n type UIMessage,\n} from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { ModelSelector, EffortSelector } from \"./ModelSelector\";\nimport type { ChatModel, Suggestion } from \"./types\";\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Suggested prompts shown on the empty state. */\n suggestions?: Suggestion[];\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** \"manual\" forwards a tool-approval policy hint to the backend. */\n toolApproval?: \"auto\" | \"manual\";\n /** Thread id to persist this conversation under (forwarded in the body). */\n threadId?: string;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\nconst DEFAULT_EFFORTS = [\"low\", \"medium\", \"high\"];\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, approval policy and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n reasoningEfforts = DEFAULT_EFFORTS,\n defaultReasoningEffort = \"\",\n onModelChange,\n suggestions,\n enableAttachments = false,\n toolApproval,\n threadId,\n body,\n transport,\n initialMessages,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [model, setModel] = useState<string | undefined>(defaultModel);\n const [effort, setEffort] = useState(defaultReasoningEffort);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`))))\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n setModel((m) => m ?? data.find((d) => d.configured !== false)?.id);\n })\n .catch((err) => console.warn(\"clicky-ui: failed to load chat models\", err));\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n const selectedModel = models.find((m) => m.id === model);\n const showEffort = !selectedModel || selectedModel.reasoning;\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/policy selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(model ? { model } : {}),\n ...(effort ? { reasoningEffort: effort } : {}),\n ...(toolApproval ? { toolApproval } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () => transport ?? new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const { messages, sendMessage, regenerate, addToolApprovalResponse, status, stop } =\n useChat<UIMessage>({\n transport: resolvedTransport,\n sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n ...(initialMessages ? { messages: initialMessages } : {}),\n });\n\n const onModelSelect = (id: string) => {\n setModel(id);\n onModelChange?.(id);\n };\n\n const toolbar = models.length > 0 || showEffort ? (\n <div className=\"flex items-center gap-2\">\n <ModelSelector models={models} value={model} onChange={onModelSelect} />\n {showEffort && (\n <EffortSelector efforts={reasoningEfforts} value={effort} onChange={setEffort} />\n )}\n </div>\n ) : undefined;\n\n const empty = (messages.length === 0 && (emptyState || suggestions?.length)) ? (\n <div className=\"flex flex-col items-center gap-4\">\n {emptyState}\n {suggestions && suggestions.length > 0 && (\n <Suggestions suggestions={suggestions} onSelect={(text) => void sendMessage({ text })} />\n )}\n </div>\n ) : undefined;\n\n return (\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={(id, approved, reason) =>\n void addToolApprovalResponse(reason ? { id, approved, reason } : { id, approved })\n }\n />\n <div className=\"p-4 pt-0\">\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n toolbar={toolbar}\n onSubmit={(text, files) => void sendMessage({ text, files })}\n />\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,32 +3,50 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const utils = require("../../lib/utils.cjs");
|
|
6
|
+
const Icon = require("../Icon.cjs");
|
|
6
7
|
const Message = require("./Message.cjs");
|
|
7
|
-
|
|
8
|
+
const UiArrowDown = require("../../icons/components/UiArrowDown.cjs");
|
|
9
|
+
function Conversation({ messages, emptyState, className, ...actions }) {
|
|
8
10
|
const scrollRef = react.useRef(null);
|
|
9
|
-
const
|
|
11
|
+
const [pinned, setPinned] = react.useState(true);
|
|
10
12
|
const onScroll = () => {
|
|
11
13
|
const el = scrollRef.current;
|
|
12
14
|
if (!el) return;
|
|
13
15
|
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
14
|
-
|
|
16
|
+
setPinned(distance < 32);
|
|
15
17
|
};
|
|
16
18
|
react.useEffect(() => {
|
|
17
19
|
const el = scrollRef.current;
|
|
18
|
-
if (el &&
|
|
20
|
+
if (el && pinned) {
|
|
19
21
|
el.scrollTop = el.scrollHeight;
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
const scrollToBottom = () => {
|
|
25
|
+
const el = scrollRef.current;
|
|
26
|
+
if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
29
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
30
|
+
"div",
|
|
31
|
+
{
|
|
32
|
+
ref: scrollRef,
|
|
33
|
+
onScroll,
|
|
34
|
+
role: "log",
|
|
35
|
+
className: utils.cn("flex-1 overflow-y-auto", className),
|
|
36
|
+
children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(Message.Message, { message, ...actions }, message.id)) })
|
|
37
|
+
}
|
|
38
|
+
),
|
|
39
|
+
!pinned && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
40
|
+
"button",
|
|
41
|
+
{
|
|
42
|
+
type: "button",
|
|
43
|
+
"aria-label": "Scroll to latest",
|
|
44
|
+
onClick: scrollToBottom,
|
|
45
|
+
className: "absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground",
|
|
46
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowDown.UiArrowDown, className: "size-4" })
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
] });
|
|
32
50
|
}
|
|
33
51
|
exports.Conversation = Conversation;
|
|
34
52
|
//# sourceMappingURL=Conversation.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Conversation.cjs","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Message } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up. */\nexport function Conversation({ messages, emptyState, className }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const
|
|
1
|
+
{"version":3,"file":"Conversation.cjs","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiArrowDown } from \"../../icons\";\nimport { Message, type MessageActionHandlers } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = MessageActionHandlers & {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up — in which case a jump-to-bottom button\n * appears. */\nexport function Conversation({ messages, emptyState, className, ...actions }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [pinned, setPinned] = useState(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n setPinned(distance < 32);\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinned) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n const scrollToBottom = () => {\n const el = scrollRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior: \"smooth\" });\n };\n\n return (\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} {...actions} />\n ))}\n </div>\n )}\n </div>\n\n {!pinned && messages.length > 0 && (\n <button\n type=\"button\"\n aria-label=\"Scroll to latest\"\n onClick={scrollToBottom}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground\"\n >\n <Icon icon={UiArrowDown} className=\"size-4\" />\n </button>\n )}\n </div>\n );\n}\n"],"names":["useRef","useState","useEffect","jsxs","jsx","cn","Message","Icon","UiArrowDown"],"mappings":";;;;;;;;AAiBO,SAAS,aAAa,EAAE,UAAU,YAAY,WAAW,GAAG,WAA8B;AAC/F,QAAM,YAAYA,MAAAA,OAAuB,IAAI;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAS,IAAI;AAEzC,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,WAAW,EAAE;AAAA,EACzB;AAEAC,QAAAA,UAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,QAAQ;AAChB,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM;AAC3B,UAAM,KAAK,UAAU;AACrB,QAAI,OAAO,SAAS,EAAE,KAAK,GAAG,cAAc,UAAU,UAAU;AAAA,EAClE;AAEA,SACEC,2BAAAA,KAAC,OAAA,EAAI,WAAU,yCACb,UAAA;AAAA,IAAAC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,MAAK;AAAA,QACL,WAAWC,MAAAA,GAAG,0BAA0B,SAAS;AAAA,QAEhD,UAAA,SAAS,WAAW,KAAK,aACxBD,2BAAAA,IAAC,OAAA,EAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEAA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACbA,2BAAAA,IAACE,QAAAA,SAAA,EAAyB,SAAmB,GAAG,QAAA,GAAlC,QAAQ,EAAmC,CAC1D,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,IAIH,CAAC,UAAU,SAAS,SAAS,KAC5BF,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAU;AAAA,QAEV,UAAAA,2BAAAA,IAACG,WAAA,EAAK,MAAMC,YAAAA,aAAa,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9C,GAEJ;AAEJ;;"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { MessageActionHandlers } from './Message';
|
|
1
2
|
import { UIMessage } from './types';
|
|
2
|
-
export type ConversationProps = {
|
|
3
|
+
export type ConversationProps = MessageActionHandlers & {
|
|
3
4
|
messages: UIMessage[];
|
|
4
5
|
/** Shown when there are no messages yet. */
|
|
5
6
|
emptyState?: React.ReactNode;
|
|
6
7
|
className?: string;
|
|
7
8
|
};
|
|
8
9
|
/** A scrollable message log that sticks to the bottom as new content streams
|
|
9
|
-
* in, unless the user has scrolled up
|
|
10
|
-
|
|
10
|
+
* in, unless the user has scrolled up — in which case a jump-to-bottom button
|
|
11
|
+
* appears. */
|
|
12
|
+
export declare function Conversation({ messages, emptyState, className, ...actions }: ConversationProps): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
//# sourceMappingURL=Conversation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Conversation.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Conversation.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Conversation.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Conversation.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG;IACtD,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;eAEe;AACf,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,EAAE,iBAAiB,2CAwD9F"}
|
|
@@ -1,32 +1,50 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useEffect } from "react";
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useEffect } from "react";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Icon } from "../Icon.js";
|
|
4
5
|
import { Message } from "./Message.js";
|
|
5
|
-
|
|
6
|
+
import { UiArrowDown } from "../../icons/components/UiArrowDown.js";
|
|
7
|
+
function Conversation({ messages, emptyState, className, ...actions }) {
|
|
6
8
|
const scrollRef = useRef(null);
|
|
7
|
-
const
|
|
9
|
+
const [pinned, setPinned] = useState(true);
|
|
8
10
|
const onScroll = () => {
|
|
9
11
|
const el = scrollRef.current;
|
|
10
12
|
if (!el) return;
|
|
11
13
|
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
12
|
-
|
|
14
|
+
setPinned(distance < 32);
|
|
13
15
|
};
|
|
14
16
|
useEffect(() => {
|
|
15
17
|
const el = scrollRef.current;
|
|
16
|
-
if (el &&
|
|
18
|
+
if (el && pinned) {
|
|
17
19
|
el.scrollTop = el.scrollHeight;
|
|
18
20
|
}
|
|
19
21
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const scrollToBottom = () => {
|
|
23
|
+
const el = scrollRef.current;
|
|
24
|
+
if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
|
|
25
|
+
};
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"div",
|
|
29
|
+
{
|
|
30
|
+
ref: scrollRef,
|
|
31
|
+
onScroll,
|
|
32
|
+
role: "log",
|
|
33
|
+
className: cn("flex-1 overflow-y-auto", className),
|
|
34
|
+
children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsx(Message, { message, ...actions }, message.id)) })
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
!pinned && messages.length > 0 && /* @__PURE__ */ jsx(
|
|
38
|
+
"button",
|
|
39
|
+
{
|
|
40
|
+
type: "button",
|
|
41
|
+
"aria-label": "Scroll to latest",
|
|
42
|
+
onClick: scrollToBottom,
|
|
43
|
+
className: "absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground",
|
|
44
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: UiArrowDown, className: "size-4" })
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
] });
|
|
30
48
|
}
|
|
31
49
|
export {
|
|
32
50
|
Conversation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Conversation.js","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Message } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up. */\nexport function Conversation({ messages, emptyState, className }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const
|
|
1
|
+
{"version":3,"file":"Conversation.js","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiArrowDown } from \"../../icons\";\nimport { Message, type MessageActionHandlers } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = MessageActionHandlers & {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up — in which case a jump-to-bottom button\n * appears. */\nexport function Conversation({ messages, emptyState, className, ...actions }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [pinned, setPinned] = useState(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n setPinned(distance < 32);\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinned) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n const scrollToBottom = () => {\n const el = scrollRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior: \"smooth\" });\n };\n\n return (\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} {...actions} />\n ))}\n </div>\n )}\n </div>\n\n {!pinned && messages.length > 0 && (\n <button\n type=\"button\"\n aria-label=\"Scroll to latest\"\n onClick={scrollToBottom}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground\"\n >\n <Icon icon={UiArrowDown} className=\"size-4\" />\n </button>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAiBO,SAAS,aAAa,EAAE,UAAU,YAAY,WAAW,GAAG,WAA8B;AAC/F,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AAEzC,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,WAAW,EAAE;AAAA,EACzB;AAEA,YAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,QAAQ;AAChB,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM;AAC3B,UAAM,KAAK,UAAU;AACrB,QAAI,OAAO,SAAS,EAAE,KAAK,GAAG,cAAc,UAAU,UAAU;AAAA,EAClE;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,yCACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,MAAK;AAAA,QACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,QAEhD,UAAA,SAAS,WAAW,KAAK,aACxB,oBAAC,OAAA,EAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEA,oBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACb,oBAAC,SAAA,EAAyB,SAAmB,GAAG,QAAA,GAAlC,QAAQ,EAAmC,CAC1D,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,IAIH,CAAC,UAAU,SAAS,SAAS,KAC5B;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAU;AAAA,QAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,aAAa,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9C,GAEJ;AAEJ;"}
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const utils = require("../../lib/utils.cjs");
|
|
5
|
+
const Icon = require("../Icon.cjs");
|
|
5
6
|
const Markdown = require("../Markdown.cjs");
|
|
6
7
|
const ToolCall = require("./ToolCall.cjs");
|
|
8
|
+
const MessageActions = require("./MessageActions.cjs");
|
|
9
|
+
const Reasoning = require("./Reasoning.cjs");
|
|
7
10
|
const types = require("./types.cjs");
|
|
8
|
-
|
|
11
|
+
const UiFile = require("../../icons/components/UiFile.cjs");
|
|
12
|
+
function Message({ message, className, onRegenerate, onApprove }) {
|
|
9
13
|
const isUser = message.role === "user";
|
|
10
|
-
|
|
14
|
+
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
15
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11
16
|
"div",
|
|
12
17
|
{
|
|
13
18
|
className: utils.cn(
|
|
@@ -15,30 +20,84 @@ function Message({ message, className }) {
|
|
|
15
20
|
isUser ? "ml-auto items-end" : "items-start",
|
|
16
21
|
className
|
|
17
22
|
),
|
|
18
|
-
children:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
25
|
+
"div",
|
|
26
|
+
{
|
|
27
|
+
className: utils.cn(
|
|
28
|
+
"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
|
|
29
|
+
isUser && "rounded-lg bg-secondary px-4 py-3 text-secondary-foreground"
|
|
30
|
+
),
|
|
31
|
+
children: message.parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
32
|
+
MessagePart,
|
|
33
|
+
{
|
|
34
|
+
part,
|
|
35
|
+
isUser,
|
|
36
|
+
onApprove
|
|
37
|
+
},
|
|
38
|
+
`${message.id}-${i}`
|
|
39
|
+
))
|
|
40
|
+
}
|
|
41
|
+
),
|
|
42
|
+
!isUser && text && /* @__PURE__ */ jsxRuntime.jsx(
|
|
43
|
+
MessageActions.MessageActions,
|
|
44
|
+
{
|
|
45
|
+
text,
|
|
46
|
+
onRegenerate: onRegenerate ? () => onRegenerate(message.id) : void 0
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
]
|
|
28
50
|
}
|
|
29
51
|
);
|
|
30
52
|
}
|
|
31
|
-
function MessagePart({
|
|
53
|
+
function MessagePart({
|
|
54
|
+
part,
|
|
55
|
+
isUser,
|
|
56
|
+
onApprove
|
|
57
|
+
}) {
|
|
32
58
|
if (part.type === "text") {
|
|
33
59
|
if (isUser) {
|
|
34
60
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre-wrap break-words", children: part.text });
|
|
35
61
|
}
|
|
36
62
|
return /* @__PURE__ */ jsxRuntime.jsx(Markdown.Markdown, { text: part.text });
|
|
37
63
|
}
|
|
64
|
+
if (types.isReasoningPart(part)) {
|
|
65
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Reasoning.Reasoning, { text: part.text });
|
|
66
|
+
}
|
|
67
|
+
if (types.isFilePart(part)) {
|
|
68
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FilePart, { part });
|
|
69
|
+
}
|
|
38
70
|
if (types.isDynamicToolPart(part) || types.isTypedToolPart(part)) {
|
|
39
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ToolCall.ToolCall, { part });
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ToolCall.ToolCall, { part, onApprove });
|
|
40
72
|
}
|
|
41
73
|
return null;
|
|
42
74
|
}
|
|
75
|
+
function FilePart({ part }) {
|
|
76
|
+
var _a;
|
|
77
|
+
const isImage = (_a = part.mediaType) == null ? void 0 : _a.startsWith("image/");
|
|
78
|
+
if (isImage && part.url) {
|
|
79
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
80
|
+
"img",
|
|
81
|
+
{
|
|
82
|
+
src: part.url,
|
|
83
|
+
alt: part.filename ?? "attachment",
|
|
84
|
+
className: "max-h-48 max-w-full rounded-md border border-border"
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
89
|
+
"a",
|
|
90
|
+
{
|
|
91
|
+
href: part.url,
|
|
92
|
+
target: "_blank",
|
|
93
|
+
rel: "noopener noreferrer",
|
|
94
|
+
className: "flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiFile.UiFile, className: "size-3.5 shrink-0" }),
|
|
97
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: part.filename ?? part.mediaType ?? "file" })
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
43
102
|
exports.Message = Message;
|
|
44
103
|
//# sourceMappingURL=Message.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport type { UIMessage } from \"./types\";\nimport {
|
|
1
|
+
{"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n} from \"./types\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove?: ((approvalId: string, approved: boolean, reason?: string) => void) | undefined;\n};\n\nexport type MessageProps = MessageActionHandlers & {\n message: UIMessage;\n className?: string;\n};\n\n/** Renders one chat message. User messages are right-aligned bubbles; assistant\n * messages render text as markdown, reasoning and tool parts inline, file parts\n * as thumbnails/chips, and a hover action row (copy / regenerate). */\nexport function Message({ message, className, onRegenerate, onApprove }: MessageProps) {\n const isUser = message.role === \"user\";\n const text = message.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\");\n\n return (\n <div\n className={cn(\n \"group flex w-full max-w-[95%] flex-col gap-2\",\n isUser ? \"ml-auto items-end\" : \"items-start\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm\",\n isUser && \"rounded-lg bg-secondary px-4 py-3 text-secondary-foreground\",\n )}\n >\n {message.parts.map((part, i) => (\n <MessagePart\n key={`${message.id}-${i}`}\n part={part}\n isUser={isUser}\n onApprove={onApprove}\n />\n ))}\n </div>\n\n {!isUser && text && (\n <MessageActions\n text={text}\n onRegenerate={onRegenerate ? () => onRegenerate(message.id) : undefined}\n />\n )}\n </div>\n );\n}\n\nfunction MessagePart({\n part,\n isUser,\n onApprove,\n}: {\n part: UIMessage[\"parts\"][number];\n isUser: boolean;\n onApprove: MessageActionHandlers[\"onApprove\"];\n}) {\n if (part.type === \"text\") {\n if (isUser) {\n return <span className=\"whitespace-pre-wrap break-words\">{part.text}</span>;\n }\n return <Markdown text={part.text} />;\n }\n if (isReasoningPart(part)) {\n return <Reasoning text={part.text} />;\n }\n if (isFilePart(part)) {\n return <FilePart part={part} />;\n }\n if (isDynamicToolPart(part) || isTypedToolPart(part)) {\n return <ToolCall part={part as AnyToolPart} onApprove={onApprove} />;\n }\n return null;\n}\n\n/** Renders an attachment: images inline as a thumbnail, everything else as a\n * labelled file chip. */\nfunction FilePart({ part }: { part: FileUIPart }) {\n const isImage = part.mediaType?.startsWith(\"image/\");\n if (isImage && part.url) {\n return (\n <img\n src={part.url}\n alt={part.filename ?? \"attachment\"}\n className=\"max-h-48 max-w-full rounded-md border border-border\"\n />\n );\n }\n return (\n <a\n href={part.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFile} className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">{part.filename ?? part.mediaType ?? \"file\"}</span>\n </a>\n );\n}\n"],"names":["jsxs","cn","jsx","MessageActions","Markdown","isReasoningPart","Reasoning","isFilePart","isDynamicToolPart","isTypedToolPart","ToolCall","Icon","UiFile"],"mappings":";;;;;;;;;;;AAgCO,SAAS,QAAQ,EAAE,SAAS,WAAW,cAAc,aAA2B;AACrF,QAAM,SAAS,QAAQ,SAAS;AAChC,QAAM,OAAO,QAAQ,MAClB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAO,EAAuB,IAAI,EACvC,KAAK,EAAE;AAEV,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,MAAAA;AAAAA,QACT;AAAA,QACA,SAAS,sBAAsB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAAC,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAWD,MAAAA;AAAAA,cACT;AAAA,cACA,UAAU;AAAA,YAAA;AAAA,YAGX,UAAA,QAAQ,MAAM,IAAI,CAAC,MAAM,MACxBC,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBAEC;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,cAHK,GAAG,QAAQ,EAAE,IAAI,CAAC;AAAA,YAAA,CAK1B;AAAA,UAAA;AAAA,QAAA;AAAA,QAGF,CAAC,UAAU,QACVA,2BAAAA;AAAAA,UAACC,eAAAA;AAAAA,UAAA;AAAA,YACC;AAAA,YACA,cAAc,eAAe,MAAM,aAAa,QAAQ,EAAE,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,MAChE;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,KAAK,SAAS,QAAQ;AACxB,QAAI,QAAQ;AACV,aAAOD,2BAAAA,IAAC,QAAA,EAAK,WAAU,mCAAmC,eAAK,MAAK;AAAA,IACtE;AACA,WAAOA,2BAAAA,IAACE,SAAAA,UAAA,EAAS,MAAM,KAAK,KAAA,CAAM;AAAA,EACpC;AACA,MAAIC,MAAAA,gBAAgB,IAAI,GAAG;AACzB,WAAOH,2BAAAA,IAACI,UAAAA,WAAA,EAAU,MAAM,KAAK,KAAA,CAAM;AAAA,EACrC;AACA,MAAIC,MAAAA,WAAW,IAAI,GAAG;AACpB,WAAOL,+BAAC,YAAS,MAAY;AAAA,EAC/B;AACA,MAAIM,MAAAA,kBAAkB,IAAI,KAAKC,MAAAA,gBAAgB,IAAI,GAAG;AACpD,WAAOP,2BAAAA,IAACQ,SAAAA,UAAA,EAAS,MAA2B,UAAA,CAAsB;AAAA,EACpE;AACA,SAAO;AACT;AAIA,SAAS,SAAS,EAAE,QAA8B;;AAChD,QAAM,WAAU,UAAK,cAAL,mBAAgB,WAAW;AAC3C,MAAI,WAAW,KAAK,KAAK;AACvB,WACER,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK,YAAY;AAAA,QACtB,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhB;AACA,SACEF,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,WAAU;AAAA,MAEV,UAAA;AAAA,QAAAE,2BAAAA,IAACS,KAAAA,MAAA,EAAK,MAAMC,OAAAA,QAAQ,WAAU,qBAAoB;AAAA,QAClDV,2BAAAA,IAAC,UAAK,WAAU,YAAY,eAAK,YAAY,KAAK,aAAa,OAAA,CAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG5E;;"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { UIMessage } from './types';
|
|
2
|
-
|
|
2
|
+
/** Callbacks the conversation threads down to each message. */
|
|
3
|
+
export type MessageActionHandlers = {
|
|
4
|
+
/** Re-generate the assistant message with the given id. */
|
|
5
|
+
onRegenerate?: ((messageId: string) => void) | undefined;
|
|
6
|
+
/** Respond to a tool approval request. */
|
|
7
|
+
onApprove?: ((approvalId: string, approved: boolean, reason?: string) => void) | undefined;
|
|
8
|
+
};
|
|
9
|
+
export type MessageProps = MessageActionHandlers & {
|
|
3
10
|
message: UIMessage;
|
|
4
11
|
className?: string;
|
|
5
12
|
};
|
|
6
13
|
/** Renders one chat message. User messages are right-aligned bubbles; assistant
|
|
7
|
-
* messages render text
|
|
8
|
-
*
|
|
9
|
-
export declare function Message({ message, className }: MessageProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
* messages render text as markdown, reasoning and tool parts inline, file parts
|
|
15
|
+
* as thumbnails/chips, and a hover action row (copy / regenerate). */
|
|
16
|
+
export declare function Message({ message, className, onRegenerate, onApprove }: MessageProps): import("react/jsx-runtime").JSX.Element;
|
|
10
17
|
//# sourceMappingURL=Message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AASrD,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACzD,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC5F,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,qBAAqB,GAAG;IACjD,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;uEAEuE;AACvE,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,YAAY,2CAuCpF"}
|