tempest-react-sdk 0.33.1 → 0.34.0
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/README.md +3 -3
- package/dist/components/AIChat/AIChat.cjs +2 -0
- package/dist/components/AIChat/AIChat.cjs.map +1 -0
- package/dist/components/AIChat/AIChat.js +141 -0
- package/dist/components/AIChat/AIChat.js.map +1 -0
- package/dist/components/AIChat/AIChat.module.cjs +2 -0
- package/dist/components/AIChat/AIChat.module.cjs.map +1 -0
- package/dist/components/AIChat/AIChat.module.js +58 -0
- package/dist/components/AIChat/AIChat.module.js.map +1 -0
- package/dist/components/AIChat/AIChatComposer.cjs +2 -0
- package/dist/components/AIChat/AIChatComposer.cjs.map +1 -0
- package/dist/components/AIChat/AIChatComposer.js +87 -0
- package/dist/components/AIChat/AIChatComposer.js.map +1 -0
- package/dist/components/AIChat/AIChatTurn.cjs +3 -0
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -0
- package/dist/components/AIChat/AIChatTurn.js +217 -0
- package/dist/components/AIChat/AIChatTurn.js.map +1 -0
- package/dist/components/AIChat/ai-chat-turns.cjs +2 -0
- package/dist/components/AIChat/ai-chat-turns.cjs.map +1 -0
- package/dist/components/AIChat/ai-chat-turns.js +90 -0
- package/dist/components/AIChat/ai-chat-turns.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +1 -1
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular.d.ts +7 -0
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +415 -9
- package/dist/tempest-react-sdk.js +118 -114
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +54 -37
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +49 -18
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template/src/lib/api.ts +27 -5
- package/template/src/stores/auth.ts +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIChatTurn.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["import { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { formatBytes as t } from "../../utils/numbers.js";
|
|
3
|
+
import { CopyButton as n } from "../CopyButton/CopyButton.js";
|
|
4
|
+
import { VisuallyHidden as ee } from "../VisuallyHidden/VisuallyHidden.js";
|
|
5
|
+
import { Collapsible as r } from "../Collapsible/Collapsible.js";
|
|
6
|
+
import { aiChatStrings as i, roleLabel as a, turnTime as o } from "./ai-chat-turns.js";
|
|
7
|
+
import s from "./AIChat.module.js";
|
|
8
|
+
import { Markdown as c } from "../Markdown/Markdown.js";
|
|
9
|
+
import { useMemo as l, useState as u } from "react";
|
|
10
|
+
import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
11
|
+
import { ChevronRight as m, Copy as h, Pencil as g, RefreshCw as _, ThumbsDown as v, ThumbsUp as y } from "lucide-react";
|
|
12
|
+
//#region src/components/AIChat/AIChatTurn.tsx
|
|
13
|
+
function b({ message: b, locale: x = "pt-BR", canRegenerate: S = !1, onRegenerate: C, onFeedback: w, onEditSubmit: T, onEditError: E, onRetry: D, renderAvatar: O, renderContent: k, vote: A, defaultReasoningOpen: j = !1, showLineNumbers: M = !1 }) {
|
|
14
|
+
let N = i(x), { role: P, content: F, reasoning: I, streaming: L, error: R, model: z, createdAt: B, attachments: V } = b, [H, U] = u(null), [W, G] = u(!1), [K, q] = u(F), [J, Y] = u(!1), X = A ?? H, Z = P === "user", Q = l(() => /* @__PURE__ */ f(c, {
|
|
15
|
+
source: F,
|
|
16
|
+
showLineNumbers: M,
|
|
17
|
+
className: s.markdownRoot
|
|
18
|
+
}), [F, M]), te = k ? k(b) : Z ? /* @__PURE__ */ f("div", {
|
|
19
|
+
className: s.plain,
|
|
20
|
+
children: F
|
|
21
|
+
}) : F === "" ? null : Q, ne = async () => {
|
|
22
|
+
let e = K.trim();
|
|
23
|
+
if (!(!e || J || !T)) {
|
|
24
|
+
Y(!0);
|
|
25
|
+
try {
|
|
26
|
+
await T(b, e), G(!1);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
E?.(e);
|
|
29
|
+
} finally {
|
|
30
|
+
Y(!1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, $ = (e) => {
|
|
34
|
+
X !== e && (U(e), w?.(b, e));
|
|
35
|
+
}, re = !L && !W, ie = w !== void 0 || S && C !== void 0;
|
|
36
|
+
return /* @__PURE__ */ p("article", {
|
|
37
|
+
className: e(s.turn, Z && s.userTurn, P === "system" && s.systemTurn, L && s.streamingTurn),
|
|
38
|
+
"aria-busy": L || void 0,
|
|
39
|
+
"data-role": P,
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ p("header", {
|
|
42
|
+
className: s.turnHeader,
|
|
43
|
+
children: [
|
|
44
|
+
O && /* @__PURE__ */ f("span", {
|
|
45
|
+
className: s.avatar,
|
|
46
|
+
"aria-hidden": "true",
|
|
47
|
+
children: O(b)
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ f("span", {
|
|
50
|
+
className: e(s.role, Z && s.visuallyHiddenRole),
|
|
51
|
+
children: a(P, N)
|
|
52
|
+
}),
|
|
53
|
+
z && !Z && /* @__PURE__ */ f("span", {
|
|
54
|
+
className: s.model,
|
|
55
|
+
children: z
|
|
56
|
+
}),
|
|
57
|
+
B !== void 0 && /* @__PURE__ */ f("time", {
|
|
58
|
+
className: s.time,
|
|
59
|
+
dateTime: new Date(B).toISOString(),
|
|
60
|
+
children: o(B, x)
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
}),
|
|
64
|
+
V && V.length > 0 && /* @__PURE__ */ f("ul", {
|
|
65
|
+
className: s.attachments,
|
|
66
|
+
children: V.map((e) => /* @__PURE__ */ f("li", {
|
|
67
|
+
className: s.attachment,
|
|
68
|
+
children: e.url ? /* @__PURE__ */ f("img", {
|
|
69
|
+
className: s.thumb,
|
|
70
|
+
src: e.url,
|
|
71
|
+
alt: e.name
|
|
72
|
+
}) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("span", {
|
|
73
|
+
className: s.attachmentName,
|
|
74
|
+
children: e.name
|
|
75
|
+
}), /* @__PURE__ */ f("span", {
|
|
76
|
+
className: s.attachmentMeta,
|
|
77
|
+
children: e.size === void 0 ? e.mimeType : t(e.size)
|
|
78
|
+
})] })
|
|
79
|
+
}, e.id))
|
|
80
|
+
}),
|
|
81
|
+
I && /* @__PURE__ */ f(r, {
|
|
82
|
+
className: s.reasoning,
|
|
83
|
+
defaultOpen: j || L === !0 && F === "",
|
|
84
|
+
trigger: /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(m, {
|
|
85
|
+
className: s.reasoningChevron,
|
|
86
|
+
size: 14,
|
|
87
|
+
"aria-hidden": !0
|
|
88
|
+
}), N.reasoning] }),
|
|
89
|
+
children: /* @__PURE__ */ f("div", {
|
|
90
|
+
className: s.reasoningBody,
|
|
91
|
+
children: I
|
|
92
|
+
})
|
|
93
|
+
}),
|
|
94
|
+
W ? /* @__PURE__ */ p("div", {
|
|
95
|
+
className: s.editor,
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ f(ee, { children: /* @__PURE__ */ f("label", {
|
|
98
|
+
htmlFor: `${b.id}-edit`,
|
|
99
|
+
children: N.editing
|
|
100
|
+
}) }),
|
|
101
|
+
/* @__PURE__ */ f("textarea", {
|
|
102
|
+
id: `${b.id}-edit`,
|
|
103
|
+
className: s.editField,
|
|
104
|
+
value: K,
|
|
105
|
+
rows: Math.min(K.split("\n").length + 1, 10),
|
|
106
|
+
onChange: (e) => q(e.target.value)
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ p("div", {
|
|
109
|
+
className: s.editActions,
|
|
110
|
+
children: [/* @__PURE__ */ f("button", {
|
|
111
|
+
type: "button",
|
|
112
|
+
className: s.ghost,
|
|
113
|
+
onClick: () => {
|
|
114
|
+
q(F), G(!1);
|
|
115
|
+
},
|
|
116
|
+
children: N.cancel
|
|
117
|
+
}), /* @__PURE__ */ f("button", {
|
|
118
|
+
type: "button",
|
|
119
|
+
className: s.primary,
|
|
120
|
+
disabled: J || K.trim() === "",
|
|
121
|
+
onClick: () => void ne(),
|
|
122
|
+
children: N.save
|
|
123
|
+
})]
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
}) : /* @__PURE__ */ p("div", {
|
|
127
|
+
className: e(s.body, L && s.streamingBody),
|
|
128
|
+
children: [te, L === !0 && F === "" && /* @__PURE__ */ p("span", {
|
|
129
|
+
className: s.dots,
|
|
130
|
+
"aria-hidden": "true",
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ f("span", {}),
|
|
133
|
+
/* @__PURE__ */ f("span", {}),
|
|
134
|
+
/* @__PURE__ */ f("span", {})
|
|
135
|
+
]
|
|
136
|
+
})]
|
|
137
|
+
}),
|
|
138
|
+
R && /* @__PURE__ */ p("p", {
|
|
139
|
+
className: s.error,
|
|
140
|
+
role: "alert",
|
|
141
|
+
children: [/* @__PURE__ */ f("span", { children: R }), D && /* @__PURE__ */ f("button", {
|
|
142
|
+
type: "button",
|
|
143
|
+
className: s.retry,
|
|
144
|
+
onClick: () => D(b),
|
|
145
|
+
children: N.retry
|
|
146
|
+
})]
|
|
147
|
+
}),
|
|
148
|
+
re && /* @__PURE__ */ p("div", {
|
|
149
|
+
className: s.actions,
|
|
150
|
+
"aria-label": N.turnActions,
|
|
151
|
+
role: "group",
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ f(n, {
|
|
154
|
+
className: s.action,
|
|
155
|
+
value: F,
|
|
156
|
+
"aria-label": N.copy,
|
|
157
|
+
title: N.copy,
|
|
158
|
+
children: /* @__PURE__ */ f(h, {
|
|
159
|
+
size: 14,
|
|
160
|
+
"aria-hidden": !0
|
|
161
|
+
})
|
|
162
|
+
}),
|
|
163
|
+
Z && T && /* @__PURE__ */ f("button", {
|
|
164
|
+
type: "button",
|
|
165
|
+
className: s.action,
|
|
166
|
+
"aria-label": N.edit,
|
|
167
|
+
title: N.edit,
|
|
168
|
+
onClick: () => {
|
|
169
|
+
q(F), G(!0);
|
|
170
|
+
},
|
|
171
|
+
children: /* @__PURE__ */ f(g, {
|
|
172
|
+
size: 14,
|
|
173
|
+
"aria-hidden": !0
|
|
174
|
+
})
|
|
175
|
+
}),
|
|
176
|
+
!Z && ie && /* @__PURE__ */ p(d, { children: [S && C && /* @__PURE__ */ f("button", {
|
|
177
|
+
type: "button",
|
|
178
|
+
className: s.action,
|
|
179
|
+
"aria-label": N.regenerate,
|
|
180
|
+
title: N.regenerate,
|
|
181
|
+
onClick: () => C(b),
|
|
182
|
+
children: /* @__PURE__ */ f(_, {
|
|
183
|
+
size: 14,
|
|
184
|
+
"aria-hidden": !0
|
|
185
|
+
})
|
|
186
|
+
}), w && /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("button", {
|
|
187
|
+
type: "button",
|
|
188
|
+
className: e(s.action, X === "up" && s.actionActive),
|
|
189
|
+
"aria-label": N.good,
|
|
190
|
+
title: N.good,
|
|
191
|
+
"aria-pressed": X === "up",
|
|
192
|
+
onClick: () => $("up"),
|
|
193
|
+
children: /* @__PURE__ */ f(y, {
|
|
194
|
+
size: 14,
|
|
195
|
+
"aria-hidden": !0
|
|
196
|
+
})
|
|
197
|
+
}), /* @__PURE__ */ f("button", {
|
|
198
|
+
type: "button",
|
|
199
|
+
className: e(s.action, X === "down" && s.actionActive),
|
|
200
|
+
"aria-label": N.bad,
|
|
201
|
+
title: N.bad,
|
|
202
|
+
"aria-pressed": X === "down",
|
|
203
|
+
onClick: () => $("down"),
|
|
204
|
+
children: /* @__PURE__ */ f(v, {
|
|
205
|
+
size: 14,
|
|
206
|
+
"aria-hidden": !0
|
|
207
|
+
})
|
|
208
|
+
})] })] })
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
export { b as AIChatTurn };
|
|
216
|
+
|
|
217
|
+
//# sourceMappingURL=AIChatTurn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIChatTurn.js","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["import { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e({messages:e,showSystem:t=!1}){return e.filter(e=>t||e.role!==`system`)}function t(e){return e.some(e=>e.streaming===!0)}function n(e){for(let t=e.length-1;t>=0;--t)if(e[t].role===`assistant`)return e[t].id;return null}function r(e){let t=e[e.length-1];if(!t)return`0`;let n=t.reasoning?.length??0;return`${e.length}:${t.id}:${t.content.length}:${n}:${+!!t.streaming}`}var i={thread:`Conversa`,empty:`Comece a conversa`,emptyHint:`Pergunte qualquer coisa.`,placeholder:`Pergunte alguma coisa…`,send:`Enviar`,stop:`Parar`,regenerate:`Gerar de novo`,copy:`Copiar`,copied:`Copiado`,edit:`Editar`,save:`Enviar edição`,cancel:`Cancelar`,editing:`Editando a mensagem`,good:`Boa resposta`,bad:`Resposta ruim`,reasoning:`Raciocínio`,thinking:`Pensando…`,generating:`Gerando resposta`,done:`Resposta concluída`,stopped:`Geração interrompida`,you:`Você`,assistant:`Assistente`,system:`Sistema`,retry:`Tentar de novo`,jumpToLatest:`Ir para a última mensagem`,attachment:`Anexo`,turnActions:`Ações da mensagem`},a={thread:`Conversation`,empty:`Start the conversation`,emptyHint:`Ask anything.`,placeholder:`Ask anything…`,send:`Send`,stop:`Stop`,regenerate:`Regenerate`,copy:`Copy`,copied:`Copied`,edit:`Edit`,save:`Send edit`,cancel:`Cancel`,editing:`Editing the message`,good:`Good response`,bad:`Bad response`,reasoning:`Reasoning`,thinking:`Thinking…`,generating:`Generating a response`,done:`Response complete`,stopped:`Generation stopped`,you:`You`,assistant:`Assistant`,system:`System`,retry:`Try again`,jumpToLatest:`Jump to the latest message`,attachment:`Attachment`,turnActions:`Message actions`};function o(e){return e===`en`?a:i}function s(e,t){return e===`user`?t.you:e===`assistant`?t.assistant:t.system}function c(e,t=`pt-BR`){return new Date(e).toLocaleTimeString(t===`en`?`en-US`:`pt-BR`,{hour:`2-digit`,minute:`2-digit`})}exports.aiChatStrings=o,exports.isGenerating=t,exports.lastAssistantId=n,exports.roleLabel=s,exports.tailSignature=r,exports.turnTime=c,exports.visibleTurns=e;
|
|
2
|
+
//# sourceMappingURL=ai-chat-turns.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-chat-turns.cjs","names":[],"sources":["../../../src/components/AIChat/ai-chat-turns.ts"],"sourcesContent":["/** Who produced a turn. */\nexport type AIChatRole = \"user\" | \"assistant\" | \"system\";\n\n/** Rating an app can collect on an assistant turn. */\nexport type AIChatVote = \"up\" | \"down\";\n\n/** A file carried by a turn — an upload on the way in, a document on the way out. */\nexport interface AIChatAttachment {\n /** Stable identity. Used as the React key. */\n id: string;\n /** Name shown in the chip. */\n name: string;\n /** Size in bytes. Formatted for display when given. */\n size?: number;\n /** Image URL. When set the attachment renders as a thumbnail instead of a chip. */\n url?: string;\n /** MIME type. Used as the chip's secondary label when there is no size. */\n mimeType?: string;\n}\n\n/** One turn of a conversation with a model. */\nexport interface AIChatMessage {\n /** Stable identity. Used as the React key and by every callback. */\n id: string;\n role: AIChatRole;\n /**\n * The text of the turn.\n *\n * An assistant turn is rendered as Markdown; a user turn is rendered as plain\n * text with newlines preserved. That asymmetry is deliberate: a model emits\n * Markdown by contract, while a person typing `2 * 3 * 4` did not mean to open\n * an emphasis span.\n */\n content: string;\n /**\n * Reasoning the model exposed before answering — extended thinking, a\n * chain-of-thought trace.\n *\n * Rendered in its own collapsible block above the answer, so a long trace never\n * pushes the answer off screen.\n */\n reasoning?: string;\n /**\n * The turn is still arriving.\n *\n * Shows the caret, marks the block `aria-busy`, and hides the action row —\n * copying or rating half an answer is never what somebody meant to do.\n */\n streaming?: boolean;\n /** Generation failed. Shown under whatever streamed, with the retry control. */\n error?: string;\n /** Epoch milliseconds. */\n createdAt?: number;\n /** Model that produced the turn. Shown in the meta row of an assistant turn. */\n model?: string;\n attachments?: readonly AIChatAttachment[];\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/**\n * Turns to render, in order.\n *\n * System turns are dropped unless asked for: a system prompt is configuration, and\n * an app that shows it by default leaks its own instructions into the transcript.\n *\n * @param params.messages - The thread, oldest first. Never reordered.\n * @param params.showSystem - Keep `\"system\"` turns. Default `false`.\n * @returns The visible turns, in the given order.\n */\nexport function visibleTurns({\n messages,\n showSystem = false,\n}: {\n messages: readonly AIChatMessage[];\n showSystem?: boolean;\n}): AIChatMessage[] {\n return messages.filter((message) => showSystem || message.role !== \"system\");\n}\n\n/** Whether any turn in the thread is still streaming. */\nexport function isGenerating(messages: readonly AIChatMessage[]): boolean {\n return messages.some((message) => message.streaming === true);\n}\n\n/**\n * Id of the newest assistant turn, or `null` when there is none.\n *\n * Only that turn gets the regenerate control: re-asking an older one would throw\n * away every turn after it, which is a different operation (\"branch here\") and\n * needs its own confirmation.\n */\nexport function lastAssistantId(messages: readonly AIChatMessage[]): string | null {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n if (messages[index].role === \"assistant\") return messages[index].id;\n }\n return null;\n}\n\n/**\n * A value that changes whenever the tail of the thread grows.\n *\n * The scroll effect cannot depend on the `messages` array alone. Streaming appends\n * to the **last** turn, and an app that mutates that object in place — or that\n * re-renders from a store holding the same array identity — would keep the same\n * dependency while the text grows, so the view would stop following the answer.\n * Length of the array, identity of the tail and length of its text together cover\n * both shapes.\n *\n * @param messages - The thread, oldest first.\n * @returns An opaque signature; compare with `===`.\n */\nexport function tailSignature(messages: readonly AIChatMessage[]): string {\n const tail = messages[messages.length - 1];\n if (!tail) return \"0\";\n const reasoning = tail.reasoning?.length ?? 0;\n return `${messages.length}:${tail.id}:${tail.content.length}:${reasoning}:${tail.streaming ? 1 : 0}`;\n}\n\n/** Labels the conversation needs, per locale. */\nexport interface AIChatStrings {\n thread: string;\n empty: string;\n emptyHint: string;\n placeholder: string;\n send: string;\n stop: string;\n regenerate: string;\n copy: string;\n copied: string;\n edit: string;\n save: string;\n cancel: string;\n editing: string;\n good: string;\n bad: string;\n reasoning: string;\n thinking: string;\n generating: string;\n done: string;\n stopped: string;\n you: string;\n assistant: string;\n system: string;\n retry: string;\n jumpToLatest: string;\n attachment: string;\n turnActions: string;\n}\n\nconst PT_BR: AIChatStrings = {\n thread: \"Conversa\",\n empty: \"Comece a conversa\",\n emptyHint: \"Pergunte qualquer coisa.\",\n placeholder: \"Pergunte alguma coisa…\",\n send: \"Enviar\",\n stop: \"Parar\",\n regenerate: \"Gerar de novo\",\n copy: \"Copiar\",\n copied: \"Copiado\",\n edit: \"Editar\",\n save: \"Enviar edição\",\n cancel: \"Cancelar\",\n editing: \"Editando a mensagem\",\n good: \"Boa resposta\",\n bad: \"Resposta ruim\",\n reasoning: \"Raciocínio\",\n thinking: \"Pensando…\",\n generating: \"Gerando resposta\",\n done: \"Resposta concluída\",\n stopped: \"Geração interrompida\",\n you: \"Você\",\n assistant: \"Assistente\",\n system: \"Sistema\",\n retry: \"Tentar de novo\",\n jumpToLatest: \"Ir para a última mensagem\",\n attachment: \"Anexo\",\n turnActions: \"Ações da mensagem\",\n};\n\nconst EN: AIChatStrings = {\n thread: \"Conversation\",\n empty: \"Start the conversation\",\n emptyHint: \"Ask anything.\",\n placeholder: \"Ask anything…\",\n send: \"Send\",\n stop: \"Stop\",\n regenerate: \"Regenerate\",\n copy: \"Copy\",\n copied: \"Copied\",\n edit: \"Edit\",\n save: \"Send edit\",\n cancel: \"Cancel\",\n editing: \"Editing the message\",\n good: \"Good response\",\n bad: \"Bad response\",\n reasoning: \"Reasoning\",\n thinking: \"Thinking…\",\n generating: \"Generating a response\",\n done: \"Response complete\",\n stopped: \"Generation stopped\",\n you: \"You\",\n assistant: \"Assistant\",\n system: \"System\",\n retry: \"Try again\",\n jumpToLatest: \"Jump to the latest message\",\n attachment: \"Attachment\",\n turnActions: \"Message actions\",\n};\n\n/** Locale strings for the conversation. */\nexport function aiChatStrings(locale: \"pt-BR\" | \"en\"): AIChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/** Role label used in the turn header and by screen readers. */\nexport function roleLabel(role: AIChatRole, strings: AIChatStrings): string {\n if (role === \"user\") return strings.you;\n if (role === \"assistant\") return strings.assistant;\n return strings.system;\n}\n\n/**\n * Clock label for a turn — the time, not a relative phrase.\n *\n * A transcript is read top to bottom in one sitting, so \"há 2 minutos\" on every turn\n * is noise that also has to be re-rendered on a timer. The wall clock is stable and\n * enough to answer the only question anyone asks of it (\"was this today?\").\n */\nexport function turnTime(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n"],"mappings":"AAsEA,SAAgB,EAAa,CACzB,WACA,aAAa,IAIG,CAChB,OAAO,EAAS,OAAQ,GAAY,GAAc,EAAQ,OAAS,QAAQ,CAC/E,CAGA,SAAgB,EAAa,EAA6C,CACtE,OAAO,EAAS,KAAM,GAAY,EAAQ,YAAc,EAAI,CAChE,CASA,SAAgB,EAAgB,EAAmD,CAC/E,IAAK,IAAI,EAAQ,EAAS,OAAS,EAAG,GAAS,EAAG,IAC9C,GAAI,EAAS,EAAM,CAAC,OAAS,YAAa,OAAO,EAAS,EAAM,CAAC,GAErE,OAAO,IACX,CAeA,SAAgB,EAAc,EAA4C,CACtE,IAAM,EAAO,EAAS,EAAS,OAAS,GACxC,GAAI,CAAC,EAAM,MAAO,IAClB,IAAM,EAAY,EAAK,WAAW,QAAU,EAC5C,MAAO,GAAG,EAAS,OAAO,GAAG,EAAK,GAAG,GAAG,EAAK,QAAQ,OAAO,GAAG,EAAU,GAAG,KAAK,WACrF,CAiCA,IAAM,EAAuB,CACzB,OAAQ,WACR,MAAO,oBACP,UAAW,2BACX,YAAa,yBACb,KAAM,SACN,KAAM,QACN,WAAY,gBACZ,KAAM,SACN,OAAQ,UACR,KAAM,SACN,KAAM,gBACN,OAAQ,WACR,QAAS,sBACT,KAAM,eACN,IAAK,gBACL,UAAW,aACX,SAAU,YACV,WAAY,mBACZ,KAAM,qBACN,QAAS,uBACT,IAAK,OACL,UAAW,aACX,OAAQ,UACR,MAAO,iBACP,aAAc,4BACd,WAAY,QACZ,YAAa,mBACjB,EAEM,EAAoB,CACtB,OAAQ,eACR,MAAO,yBACP,UAAW,gBACX,YAAa,gBACb,KAAM,OACN,KAAM,OACN,WAAY,aACZ,KAAM,OACN,OAAQ,SACR,KAAM,OACN,KAAM,YACN,OAAQ,SACR,QAAS,sBACT,KAAM,gBACN,IAAK,eACL,UAAW,YACX,SAAU,YACV,WAAY,wBACZ,KAAM,oBACN,QAAS,qBACT,IAAK,MACL,UAAW,YACX,OAAQ,SACR,MAAO,YACP,aAAc,6BACd,WAAY,aACZ,YAAa,iBACjB,EAGA,SAAgB,EAAc,EAAuC,CACjE,OAAO,IAAW,KAAO,EAAK,CAClC,CAGA,SAAgB,EAAU,EAAkB,EAAgC,CAGxE,OAFI,IAAS,OAAe,EAAQ,IAChC,IAAS,YAAoB,EAAQ,UAClC,EAAQ,MACnB,CASA,SAAgB,EAAS,EAAmB,EAAyB,QAAiB,CAClF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,IAAW,KAAO,QAAU,QAAS,CAC/E,KAAM,UACN,OAAQ,SACZ,CAAC,CACL"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region src/components/AIChat/ai-chat-turns.ts
|
|
2
|
+
function e({ messages: e, showSystem: t = !1 }) {
|
|
3
|
+
return e.filter((e) => t || e.role !== "system");
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
return e.some((e) => e.streaming === !0);
|
|
7
|
+
}
|
|
8
|
+
function n(e) {
|
|
9
|
+
for (let t = e.length - 1; t >= 0; --t) if (e[t].role === "assistant") return e[t].id;
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function r(e) {
|
|
13
|
+
let t = e[e.length - 1];
|
|
14
|
+
if (!t) return "0";
|
|
15
|
+
let n = t.reasoning?.length ?? 0;
|
|
16
|
+
return `${e.length}:${t.id}:${t.content.length}:${n}:${+!!t.streaming}`;
|
|
17
|
+
}
|
|
18
|
+
var i = {
|
|
19
|
+
thread: "Conversa",
|
|
20
|
+
empty: "Comece a conversa",
|
|
21
|
+
emptyHint: "Pergunte qualquer coisa.",
|
|
22
|
+
placeholder: "Pergunte alguma coisa…",
|
|
23
|
+
send: "Enviar",
|
|
24
|
+
stop: "Parar",
|
|
25
|
+
regenerate: "Gerar de novo",
|
|
26
|
+
copy: "Copiar",
|
|
27
|
+
copied: "Copiado",
|
|
28
|
+
edit: "Editar",
|
|
29
|
+
save: "Enviar edição",
|
|
30
|
+
cancel: "Cancelar",
|
|
31
|
+
editing: "Editando a mensagem",
|
|
32
|
+
good: "Boa resposta",
|
|
33
|
+
bad: "Resposta ruim",
|
|
34
|
+
reasoning: "Raciocínio",
|
|
35
|
+
thinking: "Pensando…",
|
|
36
|
+
generating: "Gerando resposta",
|
|
37
|
+
done: "Resposta concluída",
|
|
38
|
+
stopped: "Geração interrompida",
|
|
39
|
+
you: "Você",
|
|
40
|
+
assistant: "Assistente",
|
|
41
|
+
system: "Sistema",
|
|
42
|
+
retry: "Tentar de novo",
|
|
43
|
+
jumpToLatest: "Ir para a última mensagem",
|
|
44
|
+
attachment: "Anexo",
|
|
45
|
+
turnActions: "Ações da mensagem"
|
|
46
|
+
}, a = {
|
|
47
|
+
thread: "Conversation",
|
|
48
|
+
empty: "Start the conversation",
|
|
49
|
+
emptyHint: "Ask anything.",
|
|
50
|
+
placeholder: "Ask anything…",
|
|
51
|
+
send: "Send",
|
|
52
|
+
stop: "Stop",
|
|
53
|
+
regenerate: "Regenerate",
|
|
54
|
+
copy: "Copy",
|
|
55
|
+
copied: "Copied",
|
|
56
|
+
edit: "Edit",
|
|
57
|
+
save: "Send edit",
|
|
58
|
+
cancel: "Cancel",
|
|
59
|
+
editing: "Editing the message",
|
|
60
|
+
good: "Good response",
|
|
61
|
+
bad: "Bad response",
|
|
62
|
+
reasoning: "Reasoning",
|
|
63
|
+
thinking: "Thinking…",
|
|
64
|
+
generating: "Generating a response",
|
|
65
|
+
done: "Response complete",
|
|
66
|
+
stopped: "Generation stopped",
|
|
67
|
+
you: "You",
|
|
68
|
+
assistant: "Assistant",
|
|
69
|
+
system: "System",
|
|
70
|
+
retry: "Try again",
|
|
71
|
+
jumpToLatest: "Jump to the latest message",
|
|
72
|
+
attachment: "Attachment",
|
|
73
|
+
turnActions: "Message actions"
|
|
74
|
+
};
|
|
75
|
+
function o(e) {
|
|
76
|
+
return e === "en" ? a : i;
|
|
77
|
+
}
|
|
78
|
+
function s(e, t) {
|
|
79
|
+
return e === "user" ? t.you : e === "assistant" ? t.assistant : t.system;
|
|
80
|
+
}
|
|
81
|
+
function c(e, t = "pt-BR") {
|
|
82
|
+
return new Date(e).toLocaleTimeString(t === "en" ? "en-US" : "pt-BR", {
|
|
83
|
+
hour: "2-digit",
|
|
84
|
+
minute: "2-digit"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { o as aiChatStrings, t as isGenerating, n as lastAssistantId, s as roleLabel, r as tailSignature, c as turnTime, e as visibleTurns };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=ai-chat-turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-chat-turns.js","names":[],"sources":["../../../src/components/AIChat/ai-chat-turns.ts"],"sourcesContent":["/** Who produced a turn. */\nexport type AIChatRole = \"user\" | \"assistant\" | \"system\";\n\n/** Rating an app can collect on an assistant turn. */\nexport type AIChatVote = \"up\" | \"down\";\n\n/** A file carried by a turn — an upload on the way in, a document on the way out. */\nexport interface AIChatAttachment {\n /** Stable identity. Used as the React key. */\n id: string;\n /** Name shown in the chip. */\n name: string;\n /** Size in bytes. Formatted for display when given. */\n size?: number;\n /** Image URL. When set the attachment renders as a thumbnail instead of a chip. */\n url?: string;\n /** MIME type. Used as the chip's secondary label when there is no size. */\n mimeType?: string;\n}\n\n/** One turn of a conversation with a model. */\nexport interface AIChatMessage {\n /** Stable identity. Used as the React key and by every callback. */\n id: string;\n role: AIChatRole;\n /**\n * The text of the turn.\n *\n * An assistant turn is rendered as Markdown; a user turn is rendered as plain\n * text with newlines preserved. That asymmetry is deliberate: a model emits\n * Markdown by contract, while a person typing `2 * 3 * 4` did not mean to open\n * an emphasis span.\n */\n content: string;\n /**\n * Reasoning the model exposed before answering — extended thinking, a\n * chain-of-thought trace.\n *\n * Rendered in its own collapsible block above the answer, so a long trace never\n * pushes the answer off screen.\n */\n reasoning?: string;\n /**\n * The turn is still arriving.\n *\n * Shows the caret, marks the block `aria-busy`, and hides the action row —\n * copying or rating half an answer is never what somebody meant to do.\n */\n streaming?: boolean;\n /** Generation failed. Shown under whatever streamed, with the retry control. */\n error?: string;\n /** Epoch milliseconds. */\n createdAt?: number;\n /** Model that produced the turn. Shown in the meta row of an assistant turn. */\n model?: string;\n attachments?: readonly AIChatAttachment[];\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/**\n * Turns to render, in order.\n *\n * System turns are dropped unless asked for: a system prompt is configuration, and\n * an app that shows it by default leaks its own instructions into the transcript.\n *\n * @param params.messages - The thread, oldest first. Never reordered.\n * @param params.showSystem - Keep `\"system\"` turns. Default `false`.\n * @returns The visible turns, in the given order.\n */\nexport function visibleTurns({\n messages,\n showSystem = false,\n}: {\n messages: readonly AIChatMessage[];\n showSystem?: boolean;\n}): AIChatMessage[] {\n return messages.filter((message) => showSystem || message.role !== \"system\");\n}\n\n/** Whether any turn in the thread is still streaming. */\nexport function isGenerating(messages: readonly AIChatMessage[]): boolean {\n return messages.some((message) => message.streaming === true);\n}\n\n/**\n * Id of the newest assistant turn, or `null` when there is none.\n *\n * Only that turn gets the regenerate control: re-asking an older one would throw\n * away every turn after it, which is a different operation (\"branch here\") and\n * needs its own confirmation.\n */\nexport function lastAssistantId(messages: readonly AIChatMessage[]): string | null {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n if (messages[index].role === \"assistant\") return messages[index].id;\n }\n return null;\n}\n\n/**\n * A value that changes whenever the tail of the thread grows.\n *\n * The scroll effect cannot depend on the `messages` array alone. Streaming appends\n * to the **last** turn, and an app that mutates that object in place — or that\n * re-renders from a store holding the same array identity — would keep the same\n * dependency while the text grows, so the view would stop following the answer.\n * Length of the array, identity of the tail and length of its text together cover\n * both shapes.\n *\n * @param messages - The thread, oldest first.\n * @returns An opaque signature; compare with `===`.\n */\nexport function tailSignature(messages: readonly AIChatMessage[]): string {\n const tail = messages[messages.length - 1];\n if (!tail) return \"0\";\n const reasoning = tail.reasoning?.length ?? 0;\n return `${messages.length}:${tail.id}:${tail.content.length}:${reasoning}:${tail.streaming ? 1 : 0}`;\n}\n\n/** Labels the conversation needs, per locale. */\nexport interface AIChatStrings {\n thread: string;\n empty: string;\n emptyHint: string;\n placeholder: string;\n send: string;\n stop: string;\n regenerate: string;\n copy: string;\n copied: string;\n edit: string;\n save: string;\n cancel: string;\n editing: string;\n good: string;\n bad: string;\n reasoning: string;\n thinking: string;\n generating: string;\n done: string;\n stopped: string;\n you: string;\n assistant: string;\n system: string;\n retry: string;\n jumpToLatest: string;\n attachment: string;\n turnActions: string;\n}\n\nconst PT_BR: AIChatStrings = {\n thread: \"Conversa\",\n empty: \"Comece a conversa\",\n emptyHint: \"Pergunte qualquer coisa.\",\n placeholder: \"Pergunte alguma coisa…\",\n send: \"Enviar\",\n stop: \"Parar\",\n regenerate: \"Gerar de novo\",\n copy: \"Copiar\",\n copied: \"Copiado\",\n edit: \"Editar\",\n save: \"Enviar edição\",\n cancel: \"Cancelar\",\n editing: \"Editando a mensagem\",\n good: \"Boa resposta\",\n bad: \"Resposta ruim\",\n reasoning: \"Raciocínio\",\n thinking: \"Pensando…\",\n generating: \"Gerando resposta\",\n done: \"Resposta concluída\",\n stopped: \"Geração interrompida\",\n you: \"Você\",\n assistant: \"Assistente\",\n system: \"Sistema\",\n retry: \"Tentar de novo\",\n jumpToLatest: \"Ir para a última mensagem\",\n attachment: \"Anexo\",\n turnActions: \"Ações da mensagem\",\n};\n\nconst EN: AIChatStrings = {\n thread: \"Conversation\",\n empty: \"Start the conversation\",\n emptyHint: \"Ask anything.\",\n placeholder: \"Ask anything…\",\n send: \"Send\",\n stop: \"Stop\",\n regenerate: \"Regenerate\",\n copy: \"Copy\",\n copied: \"Copied\",\n edit: \"Edit\",\n save: \"Send edit\",\n cancel: \"Cancel\",\n editing: \"Editing the message\",\n good: \"Good response\",\n bad: \"Bad response\",\n reasoning: \"Reasoning\",\n thinking: \"Thinking…\",\n generating: \"Generating a response\",\n done: \"Response complete\",\n stopped: \"Generation stopped\",\n you: \"You\",\n assistant: \"Assistant\",\n system: \"System\",\n retry: \"Try again\",\n jumpToLatest: \"Jump to the latest message\",\n attachment: \"Attachment\",\n turnActions: \"Message actions\",\n};\n\n/** Locale strings for the conversation. */\nexport function aiChatStrings(locale: \"pt-BR\" | \"en\"): AIChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/** Role label used in the turn header and by screen readers. */\nexport function roleLabel(role: AIChatRole, strings: AIChatStrings): string {\n if (role === \"user\") return strings.you;\n if (role === \"assistant\") return strings.assistant;\n return strings.system;\n}\n\n/**\n * Clock label for a turn — the time, not a relative phrase.\n *\n * A transcript is read top to bottom in one sitting, so \"há 2 minutos\" on every turn\n * is noise that also has to be re-rendered on a timer. The wall clock is stable and\n * enough to answer the only question anyone asks of it (\"was this today?\").\n */\nexport function turnTime(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n"],"mappings":";AAsEA,SAAgB,EAAa,EACzB,aACA,gBAAa,MAIG;CAChB,OAAO,EAAS,QAAQ,MAAY,KAAc,EAAQ,SAAS,QAAQ;AAC/E;AAGA,SAAgB,EAAa,GAA6C;CACtE,OAAO,EAAS,MAAM,MAAY,EAAQ,cAAc,EAAI;AAChE;AASA,SAAgB,EAAgB,GAAmD;CAC/E,KAAK,IAAI,IAAQ,EAAS,SAAS,GAAG,KAAS,GAAG,KAC9C,IAAI,EAAS,EAAM,CAAC,SAAS,aAAa,OAAO,EAAS,EAAM,CAAC;CAErE,OAAO;AACX;AAeA,SAAgB,EAAc,GAA4C;CACtE,IAAM,IAAO,EAAS,EAAS,SAAS;CACxC,IAAI,CAAC,GAAM,OAAO;CAClB,IAAM,IAAY,EAAK,WAAW,UAAU;CAC5C,OAAO,GAAG,EAAS,OAAO,GAAG,EAAK,GAAG,GAAG,EAAK,QAAQ,OAAO,GAAG,EAAU,GAAG,KAAK;AACrF;AAiCA,IAAM,IAAuB;CACzB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,aAAa;CACb,MAAM;CACN,MAAM;CACN,YAAY;CACZ,MAAM;CACN,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;CACR,SAAS;CACT,MAAM;CACN,KAAK;CACL,WAAW;CACX,UAAU;CACV,YAAY;CACZ,MAAM;CACN,SAAS;CACT,KAAK;CACL,WAAW;CACX,QAAQ;CACR,OAAO;CACP,cAAc;CACd,YAAY;CACZ,aAAa;AACjB,GAEM,IAAoB;CACtB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,aAAa;CACb,MAAM;CACN,MAAM;CACN,YAAY;CACZ,MAAM;CACN,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;CACR,SAAS;CACT,MAAM;CACN,KAAK;CACL,WAAW;CACX,UAAU;CACV,YAAY;CACZ,MAAM;CACN,SAAS;CACT,KAAK;CACL,WAAW;CACX,QAAQ;CACR,OAAO;CACP,cAAc;CACd,YAAY;CACZ,aAAa;AACjB;AAGA,SAAgB,EAAc,GAAuC;CACjE,OAAO,MAAW,OAAO,IAAK;AAClC;AAGA,SAAgB,EAAU,GAAkB,GAAgC;CAGxE,OAFI,MAAS,SAAe,EAAQ,MAChC,MAAS,cAAoB,EAAQ,YAClC,EAAQ;AACnB;AASA,SAAgB,EAAS,GAAmB,IAAyB,SAAiB;CAClF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,MAAW,OAAO,UAAU,SAAS;EAC/E,MAAM;EACN,QAAQ;CACZ,CAAC;AACL"}
|