@pactor-app/ui 1.44.0 → 1.46.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.
@@ -2,7 +2,7 @@ import {
2
2
  Icon,
3
3
  Menu,
4
4
  Sidebar
5
- } from "./chunk-SD2XSQIP.js";
5
+ } from "./chunk-FU4P7CAM.js";
6
6
  import {
7
7
  Alert,
8
8
  AlertDescription,
@@ -27,8 +27,6 @@ import {
27
27
  BreadcrumbList,
28
28
  BreadcrumbPage,
29
29
  BreadcrumbSeparator,
30
- Bubble,
31
- BubbleContent,
32
30
  ButtonGroup,
33
31
  Calendar,
34
32
  Carousel,
@@ -140,7 +138,20 @@ import {
140
138
  Typography,
141
139
  navigationMenuTriggerStyle,
142
140
  toast
143
- } from "./chunk-T3TGKYAB.js";
141
+ } from "./chunk-XA7J5SGA.js";
142
+ import {
143
+ Dialog,
144
+ DialogContent,
145
+ DialogDescription,
146
+ DialogHeader,
147
+ DialogTitle,
148
+ DialogTrigger,
149
+ Drawer,
150
+ DrawerContent,
151
+ DrawerFooter,
152
+ DrawerHeader,
153
+ DrawerTitle
154
+ } from "./chunk-YVHPJHW3.js";
144
155
  import {
145
156
  Accordion,
146
157
  AccordionContent,
@@ -164,19 +175,6 @@ import {
164
175
  TooltipContent,
165
176
  TooltipTrigger
166
177
  } from "./chunk-7OBUSV5S.js";
167
- import {
168
- Dialog,
169
- DialogContent,
170
- DialogDescription,
171
- DialogHeader,
172
- DialogTitle,
173
- DialogTrigger,
174
- Drawer,
175
- DrawerContent,
176
- DrawerFooter,
177
- DrawerHeader,
178
- DrawerTitle
179
- } from "./chunk-YVHPJHW3.js";
180
178
  import {
181
179
  Button,
182
180
  buttonVariants,
@@ -184,7 +182,7 @@ import {
184
182
  } from "./chunk-WKJUCGV4.js";
185
183
 
186
184
  // src/components/register.ts
187
- import { Registry as Registry2 } from "@pactor-app/core";
185
+ import { Registry } from "@pactor-app/core";
188
186
 
189
187
  // src/components/components/Accordion/index.tsx
190
188
  import { useRenderer } from "@pactor-app/runtime";
@@ -1821,19 +1819,28 @@ function Command2({
1821
1819
  // src/components/components/ChatInput/index.tsx
1822
1820
  import { useI18n as useI18n8 } from "@pactor-app/runtime";
1823
1821
  import { useRenderer as useRenderer6 } from "@pactor-app/runtime";
1824
- import { ArrowUp } from "lucide-react";
1822
+ import { ArrowUp, Square } from "lucide-react";
1825
1823
  import {
1826
1824
  isValidElement,
1827
1825
  useRef as useRef3,
1828
1826
  useState as useState10
1829
1827
  } from "react";
1830
1828
  import { jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
1829
+ function useOptionalRenderer() {
1830
+ try {
1831
+ return useRenderer6();
1832
+ } catch {
1833
+ return null;
1834
+ }
1835
+ }
1831
1836
  function ChatInput({
1832
1837
  value,
1833
1838
  placeholder,
1834
1839
  disabled,
1835
1840
  rows = 3,
1836
1841
  hideSend,
1842
+ streaming = false,
1843
+ onStop,
1837
1844
  toolbarStart,
1838
1845
  toolbarEnd,
1839
1846
  header,
@@ -1844,14 +1851,14 @@ function ChatInput({
1844
1851
  className,
1845
1852
  style
1846
1853
  }) {
1847
- const { renderChildren } = useRenderer6();
1854
+ const renderer = useOptionalRenderer();
1848
1855
  const controlled = value !== void 0;
1849
1856
  const [draft, setDraft] = useState10("");
1850
1857
  const { t } = useI18n8();
1851
1858
  const text = controlled ? value : draft;
1852
1859
  const canSubmit = !disabled && text.trim() !== "";
1853
1860
  const customField = field !== void 0 && field !== null;
1854
- const renderSlot = (slot) => isValidElement(slot) || typeof slot !== "object" || slot === null ? slot : renderChildren([slot]);
1861
+ const renderSlot = (slot) => isValidElement(slot) || typeof slot !== "object" || slot === null ? slot : renderer?.renderChildren([slot]) ?? null;
1855
1862
  const composingRef = useRef3(false);
1856
1863
  const handleChange = (next) => {
1857
1864
  if (!controlled) {
@@ -1911,11 +1918,11 @@ function ChatInput({
1911
1918
  {
1912
1919
  type: "button",
1913
1920
  size: "icon",
1914
- "aria-label": t("pactor.chatInput.send"),
1915
- disabled: !canSubmit,
1916
- onClick: submit,
1921
+ "aria-label": streaming ? t("pactor.chatInput.stop") : t("pactor.chatInput.send"),
1922
+ disabled: streaming ? false : !canSubmit,
1923
+ onClick: streaming ? () => onStop?.() : submit,
1917
1924
  className: "size-8 rounded-full",
1918
- children: /* @__PURE__ */ jsx29(ArrowUp, {})
1925
+ children: streaming ? /* @__PURE__ */ jsx29(Square, {}) : /* @__PURE__ */ jsx29(ArrowUp, {})
1919
1926
  }
1920
1927
  )
1921
1928
  ] }) : null,
@@ -8730,2391 +8737,6 @@ function Watermark({
8730
8737
  );
8731
8738
  }
8732
8739
 
8733
- // src/components/chat/AgentStatus/index.tsx
8734
- import { jsx as jsx117, jsxs as jsxs78 } from "react/jsx-runtime";
8735
- var statusMeta = {
8736
- running: { icon: "loader-circle", className: "text-info", spin: true },
8737
- waiting: { icon: "clock", className: "text-warning" },
8738
- done: { icon: "circle-check", className: "text-success" },
8739
- error: { icon: "circle-x", className: "text-destructive" }
8740
- };
8741
- function AgentStatus({ status = "running", text, elapsed, className, style }) {
8742
- const meta = statusMeta[status];
8743
- return /* @__PURE__ */ jsxs78(
8744
- "div",
8745
- {
8746
- "data-slot": "agent-status",
8747
- "data-status": status,
8748
- className: cn("flex items-center gap-2 text-sm", className),
8749
- style,
8750
- children: [
8751
- /* @__PURE__ */ jsx117("span", { className: cn(meta.className, meta.spin && "animate-spin motion-reduce:animate-none"), children: /* @__PURE__ */ jsx117(Icon, { name: meta.icon, size: "sm" }) }),
8752
- text ? /* @__PURE__ */ jsx117("span", { "data-slot": "agent-status-text", children: text }) : null,
8753
- elapsed !== void 0 ? /* @__PURE__ */ jsxs78("span", { "data-slot": "agent-status-elapsed", className: "text-xs text-muted-foreground", children: [
8754
- "\u5DF2\u7528\u65F6 ",
8755
- elapsed,
8756
- "s"
8757
- ] }) : null
8758
- ]
8759
- }
8760
- );
8761
- }
8762
-
8763
- // src/components/chat/ArtifactPanel/index.tsx
8764
- import { useEffect as useEffect35, useState as useState39 } from "react";
8765
-
8766
- // src/components/chat/ArtifactPanel/artifact.ts
8767
- var ARTIFACT_IMAGE_EXTENSIONS = [
8768
- "png",
8769
- "jpg",
8770
- "jpeg",
8771
- "gif",
8772
- "svg",
8773
- "webp",
8774
- "avif",
8775
- "bmp",
8776
- "ico"
8777
- ];
8778
- function resolveArtifactFileType(artifact) {
8779
- const declared = artifact.fileType?.trim().toLowerCase().replace(/^\./, "");
8780
- if (declared) {
8781
- return declared;
8782
- }
8783
- const match = /\.([A-Za-z0-9]+)$/.exec(artifact.title.trim());
8784
- return match ? match[1].toLowerCase() : null;
8785
- }
8786
- function isImageArtifact(artifact) {
8787
- const ext = resolveArtifactFileType(artifact);
8788
- return ext !== null && ARTIFACT_IMAGE_EXTENSIONS.includes(ext);
8789
- }
8790
- var ARTIFACT_ICON_BY_EXTENSION = {
8791
- // 表格数据
8792
- csv: "table",
8793
- tsv: "table",
8794
- xls: "table",
8795
- xlsx: "table",
8796
- // 数据 / 数据库
8797
- json: "database",
8798
- sql: "database",
8799
- // 脚本
8800
- sh: "terminal",
8801
- bash: "terminal",
8802
- zsh: "terminal",
8803
- // 代码 / 配置
8804
- js: "code",
8805
- mjs: "code",
8806
- cjs: "code",
8807
- jsx: "code",
8808
- ts: "code",
8809
- mts: "code",
8810
- cts: "code",
8811
- tsx: "code",
8812
- py: "code",
8813
- rb: "code",
8814
- go: "code",
8815
- rs: "code",
8816
- java: "code",
8817
- kt: "code",
8818
- c: "code",
8819
- h: "code",
8820
- cc: "code",
8821
- cpp: "code",
8822
- cs: "code",
8823
- swift: "code",
8824
- php: "code",
8825
- css: "code",
8826
- scss: "code",
8827
- less: "code",
8828
- html: "code",
8829
- vue: "code",
8830
- svelte: "code",
8831
- yaml: "code",
8832
- yml: "code",
8833
- toml: "code",
8834
- xml: "code",
8835
- // 文档 / 文本
8836
- md: "file-text",
8837
- markdown: "file-text",
8838
- mdx: "file-text",
8839
- txt: "file-text",
8840
- text: "file-text",
8841
- log: "file-text",
8842
- rst: "file-text",
8843
- pdf: "file-text",
8844
- doc: "file-text",
8845
- docx: "file-text"
8846
- };
8847
- function resolveArtifactIconName(artifact) {
8848
- const ext = resolveArtifactFileType(artifact);
8849
- if (ext !== null) {
8850
- if (ARTIFACT_IMAGE_EXTENSIONS.includes(ext)) {
8851
- return "image";
8852
- }
8853
- const name = ARTIFACT_ICON_BY_EXTENSION[ext];
8854
- if (name !== void 0) {
8855
- return name;
8856
- }
8857
- }
8858
- if (artifact.kind === "code") {
8859
- return "code";
8860
- }
8861
- if (artifact.kind === "dsl") {
8862
- return "layout-grid";
8863
- }
8864
- return ext === null ? "file-text" : "file";
8865
- }
8866
- function buildArtifactTree(artifacts) {
8867
- const root = { name: "", path: "", children: [] };
8868
- for (const artifact of artifacts) {
8869
- const segments = artifact.id.split("/").filter(Boolean);
8870
- let node = root;
8871
- for (let i = 0; i < segments.length - 1; i++) {
8872
- const path = segments.slice(0, i + 1).join("/");
8873
- let child = node.children.find(
8874
- (item) => item.path === path && item.artifact === void 0
8875
- );
8876
- if (!child) {
8877
- child = { name: segments[i], path, children: [] };
8878
- node.children.push(child);
8879
- }
8880
- node = child;
8881
- }
8882
- node.children.push({
8883
- name: artifact.title,
8884
- path: artifact.id,
8885
- artifact,
8886
- children: []
8887
- });
8888
- }
8889
- const sortLevel = (nodes) => {
8890
- nodes.sort((a, b) => {
8891
- const dirDelta = Number(b.artifact === void 0) - Number(a.artifact === void 0);
8892
- return dirDelta !== 0 ? dirDelta : a.name.localeCompare(b.name);
8893
- });
8894
- for (const item of nodes) {
8895
- sortLevel(item.children);
8896
- }
8897
- };
8898
- sortLevel(root.children);
8899
- return root.children;
8900
- }
8901
-
8902
- // src/components/chat/ArtifactPanel/download.ts
8903
- var KIND_EXTENSIONS = {
8904
- markdown: "md",
8905
- text: "txt",
8906
- code: "txt",
8907
- dsl: "json"
8908
- };
8909
- function resolveArtifactFilename(artifact) {
8910
- const title = artifact.title.trim() || "artifact";
8911
- if (/\.[A-Za-z0-9]+$/.test(title)) {
8912
- return title;
8913
- }
8914
- const ext = resolveArtifactFileType(artifact) ?? KIND_EXTENSIONS[artifact.kind];
8915
- return `${title}.${ext}`;
8916
- }
8917
- function dataUrlToBlob(dataUrl) {
8918
- const comma = dataUrl.indexOf(",");
8919
- const meta = dataUrl.slice(0, comma);
8920
- const payload = dataUrl.slice(comma + 1);
8921
- const mime = /^data:([^;,]+)/.exec(meta)?.[1] ?? "application/octet-stream";
8922
- if (meta.includes(";base64")) {
8923
- const binary = atob(payload);
8924
- const bytes = new Uint8Array(binary.length);
8925
- for (let i = 0; i < binary.length; i++) {
8926
- bytes[i] = binary.charCodeAt(i);
8927
- }
8928
- return new Blob([bytes], { type: mime });
8929
- }
8930
- return new Blob([decodeURIComponent(payload)], { type: mime });
8931
- }
8932
- function isRemoteUrl(raw) {
8933
- return /^https?:\/\//.test(raw.trim());
8934
- }
8935
- function buildArtifactBlob(artifact) {
8936
- if (artifact.kind === "dsl" && typeof artifact.content === "object") {
8937
- return new Blob([JSON.stringify(artifact.content, null, 2)], {
8938
- type: "application/json"
8939
- });
8940
- }
8941
- const raw = String(artifact.content);
8942
- if (isImageArtifact(artifact)) {
8943
- const trimmed = raw.trim();
8944
- if (trimmed.startsWith("data:")) {
8945
- return dataUrlToBlob(trimmed);
8946
- }
8947
- if (trimmed.startsWith("<svg") || trimmed.startsWith("<?xml")) {
8948
- return new Blob([raw], { type: "image/svg+xml" });
8949
- }
8950
- if (isRemoteUrl(trimmed)) {
8951
- return null;
8952
- }
8953
- }
8954
- const ext = resolveArtifactFileType(artifact);
8955
- const mime = ext === "md" || artifact.kind === "markdown" ? "text/markdown" : "text/plain";
8956
- return new Blob([raw], { type: `${mime};charset=utf-8` });
8957
- }
8958
- function saveBlob(blob, filename) {
8959
- const url = URL.createObjectURL(blob);
8960
- const anchor = document.createElement("a");
8961
- anchor.href = url;
8962
- anchor.download = filename;
8963
- document.body.appendChild(anchor);
8964
- anchor.click();
8965
- anchor.remove();
8966
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
8967
- }
8968
- async function downloadArtifact(artifact) {
8969
- const filename = resolveArtifactFilename(artifact);
8970
- const raw = String(artifact.content ?? "").trim();
8971
- if (isImageArtifact(artifact) && isRemoteUrl(raw)) {
8972
- try {
8973
- const response = await fetch(raw);
8974
- if (!response.ok) {
8975
- throw new Error(`HTTP ${response.status}`);
8976
- }
8977
- saveBlob(await response.blob(), filename);
8978
- return;
8979
- } catch {
8980
- window.open(raw, "_blank", "noopener");
8981
- return;
8982
- }
8983
- }
8984
- const blob = buildArtifactBlob(artifact);
8985
- if (blob) {
8986
- saveBlob(blob, filename);
8987
- }
8988
- }
8989
-
8990
- // src/components/chat/ArtifactPanel/dsl-preview.tsx
8991
- import { isDslSourceRef as isDslSourceRef2, Registry } from "@pactor-app/core";
8992
- import {
8993
- ActionEngine,
8994
- createRuntimeContext,
8995
- DslRenderer,
8996
- registerBuiltinActions,
8997
- useRenderer as useRenderer20
8998
- } from "@pactor-app/runtime";
8999
- import {
9000
- Component,
9001
- useMemo as useMemo6
9002
- } from "react";
9003
- import { Fragment as Fragment13, jsx as jsx118, jsxs as jsxs79 } from "react/jsx-runtime";
9004
- var ARTIFACT_DENIED_TYPE = "ChatDenied";
9005
- function ArtifactDenied({ deniedType }) {
9006
- return /* @__PURE__ */ jsxs79(
9007
- "div",
9008
- {
9009
- "data-slot": "artifact-denied",
9010
- "data-denied-type": deniedType,
9011
- className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
9012
- children: [
9013
- "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728\u4EA7\u7269\u9884\u89C8\u4E2D\u4F7F\u7528",
9014
- deniedType ? `\uFF1A${deniedType}` : ""
9015
- ]
9016
- }
9017
- );
9018
- }
9019
- function guardArtifactDsl(node, registry) {
9020
- const type = node.type ?? "(reference)";
9021
- if (type !== ARTIFACT_DENIED_TYPE && !registry.has(type)) {
9022
- return {
9023
- type: ARTIFACT_DENIED_TYPE,
9024
- props: { deniedType: type }
9025
- };
9026
- }
9027
- if (node.children === void 0) {
9028
- return node;
9029
- }
9030
- if (!Array.isArray(node.children)) {
9031
- return {
9032
- type: ARTIFACT_DENIED_TYPE,
9033
- props: { deniedType: `${type}(remote)` }
9034
- };
9035
- }
9036
- if (node.children.length === 0) {
9037
- return node;
9038
- }
9039
- return {
9040
- ...node,
9041
- // 数组中混排的引用元素同样无法静态过白名单:替换为受控占位
9042
- children: node.children.map(
9043
- (child) => isDslSourceRef2(child) ? { type: ARTIFACT_DENIED_TYPE, props: { deniedType: "(remote)" } } : guardArtifactDsl(child, registry)
9044
- )
9045
- };
9046
- }
9047
- function useArtifactPreviewRuntime(safeRegistry, user) {
9048
- return useMemo6(() => {
9049
- if (!safeRegistry) {
9050
- return void 0;
9051
- }
9052
- const components = new Registry("ArtifactPreviewRegistry");
9053
- components.register(ARTIFACT_DENIED_TYPE, ArtifactDenied);
9054
- for (const name of safeRegistry.list()) {
9055
- components.register(name, safeRegistry.get(name));
9056
- }
9057
- const context = createRuntimeContext({ components, user });
9058
- registerBuiltinActions(context.actions);
9059
- return { context, engine: new ActionEngine(context) };
9060
- }, [safeRegistry, user]);
9061
- }
9062
- var ArtifactDslGuard = class extends Component {
9063
- constructor() {
9064
- super(...arguments);
9065
- this.state = { failed: false };
9066
- }
9067
- static getDerivedStateFromError() {
9068
- return { failed: true };
9069
- }
9070
- componentDidCatch(error) {
9071
- console.warn("[@pactor-app/ui] DSL \u4EA7\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
9072
- }
9073
- render() {
9074
- return this.state.failed ? /* @__PURE__ */ jsx118(
9075
- "div",
9076
- {
9077
- "data-slot": "artifact-denied",
9078
- className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
9079
- children: "DSL \u4EA7\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A"
9080
- }
9081
- ) : this.props.children;
9082
- }
9083
- };
9084
- function ArtifactDslNode({ node }) {
9085
- const { renderChildren } = useRenderer20();
9086
- return /* @__PURE__ */ jsx118(Fragment13, { children: renderChildren([node]) });
9087
- }
9088
- function ArtifactDslContent({
9089
- content,
9090
- runtime,
9091
- style
9092
- }) {
9093
- const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
9094
- if (!node || !runtime) {
9095
- return /* @__PURE__ */ jsx118(
9096
- "div",
9097
- {
9098
- "data-slot": "artifact-denied",
9099
- className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
9100
- style,
9101
- children: node ? "DSL \u9884\u89C8\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "DSL \u4EA7\u7269\u5185\u5BB9\u65E0\u6548"
9102
- }
9103
- );
9104
- }
9105
- const guarded = guardArtifactDsl(node, runtime.context.components);
9106
- return /* @__PURE__ */ jsx118(DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx118(ArtifactDslGuard, { children: /* @__PURE__ */ jsx118(ArtifactDslNode, { node: guarded }) }) });
9107
- }
9108
-
9109
- // src/components/chat/ArtifactPanel/index.tsx
9110
- import { Fragment as Fragment14, jsx as jsx119, jsxs as jsxs80 } from "react/jsx-runtime";
9111
- function ArtifactKindIcon({ artifact }) {
9112
- return /* @__PURE__ */ jsx119(Icon, { name: resolveArtifactIconName(artifact), size: 13 });
9113
- }
9114
- function ArtifactListView({
9115
- nodes,
9116
- activeId,
9117
- onSelect
9118
- }) {
9119
- return /* @__PURE__ */ jsx119("ul", { "data-slot": "artifact-panel-list", className: "flex flex-col gap-0.5", children: nodes.map((node) => /* @__PURE__ */ jsx119(
9120
- ArtifactTreeRow,
9121
- {
9122
- node,
9123
- activeId,
9124
- onSelect
9125
- },
9126
- node.path
9127
- )) });
9128
- }
9129
- function ArtifactTreeRow({
9130
- node,
9131
- activeId,
9132
- onSelect
9133
- }) {
9134
- const [open, setOpen] = useState39(true);
9135
- if (node.artifact) {
9136
- return /* @__PURE__ */ jsx119("li", { children: /* @__PURE__ */ jsxs80(
9137
- "button",
9138
- {
9139
- type: "button",
9140
- "data-slot": "artifact-panel-list-item",
9141
- "data-active": node.artifact.id === activeId,
9142
- className: "flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent data-[active=true]:bg-accent",
9143
- onClick: () => onSelect?.(node.artifact.id),
9144
- children: [
9145
- /* @__PURE__ */ jsx119(ArtifactKindIcon, { artifact: node.artifact }),
9146
- /* @__PURE__ */ jsx119("span", { className: "min-w-0 flex-1 truncate", children: node.artifact.title }),
9147
- /* @__PURE__ */ jsx119("span", { className: "shrink-0 text-xs text-muted-foreground", children: node.artifact.kind })
9148
- ]
9149
- }
9150
- ) });
9151
- }
9152
- return /* @__PURE__ */ jsxs80("li", { children: [
9153
- /* @__PURE__ */ jsxs80(
9154
- "button",
9155
- {
9156
- type: "button",
9157
- "data-slot": "artifact-panel-list-folder",
9158
- "aria-expanded": open,
9159
- "data-open": open,
9160
- className: "flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent",
9161
- onClick: () => setOpen((value) => !value),
9162
- children: [
9163
- /* @__PURE__ */ jsx119(
9164
- "span",
9165
- {
9166
- className: "inline-flex transition-transform data-[open=true]:rotate-90",
9167
- "data-open": open,
9168
- children: /* @__PURE__ */ jsx119(Icon, { name: "chevron-right", size: 12 })
9169
- }
9170
- ),
9171
- /* @__PURE__ */ jsx119(Icon, { name: "folder", size: 13 }),
9172
- /* @__PURE__ */ jsx119("span", { className: "min-w-0 flex-1 truncate", children: node.name })
9173
- ]
9174
- }
9175
- ),
9176
- open && /* @__PURE__ */ jsx119("div", { className: "pl-4", children: /* @__PURE__ */ jsx119(
9177
- ArtifactListView,
9178
- {
9179
- nodes: node.children,
9180
- activeId,
9181
- onSelect
9182
- }
9183
- ) })
9184
- ] });
9185
- }
9186
- function ArtifactImageContent({ artifact }) {
9187
- const raw = String(artifact.content).trim();
9188
- const src = raw.startsWith("<svg") || raw.startsWith("<?xml") ? `data:image/svg+xml;utf8,${encodeURIComponent(raw)}` : raw;
9189
- return /* @__PURE__ */ jsx119("div", { "data-slot": "artifact-panel-image", className: "flex justify-center", children: /* @__PURE__ */ jsx119("img", { src, alt: artifact.title, className: "max-w-full rounded-md" }) });
9190
- }
9191
- function ArtifactContent({
9192
- artifact,
9193
- runtime,
9194
- viewers
9195
- }) {
9196
- const ext = resolveArtifactFileType(artifact);
9197
- const custom = (ext ? viewers?.[ext] : void 0) ?? viewers?.[artifact.kind];
9198
- if (custom) {
9199
- return /* @__PURE__ */ jsx119(Fragment14, { children: custom(artifact) });
9200
- }
9201
- if (isImageArtifact(artifact)) {
9202
- return /* @__PURE__ */ jsx119(ArtifactImageContent, { artifact });
9203
- }
9204
- switch (artifact.kind) {
9205
- case "markdown":
9206
- return /* @__PURE__ */ jsx119(MarkdownDslComponent, { content: String(artifact.content) });
9207
- case "code":
9208
- return /* @__PURE__ */ jsx119(
9209
- CodeBlock,
9210
- {
9211
- code: String(artifact.content),
9212
- language: artifact.language
9213
- }
9214
- );
9215
- case "text":
9216
- return /* @__PURE__ */ jsx119(
9217
- "pre",
9218
- {
9219
- "data-slot": "artifact-panel-text",
9220
- className: "text-sm break-words whitespace-pre-wrap",
9221
- children: String(artifact.content)
9222
- }
9223
- );
9224
- case "dsl":
9225
- return /* @__PURE__ */ jsx119(ArtifactDslContent, { content: artifact.content, runtime });
9226
- default:
9227
- return null;
9228
- }
9229
- }
9230
- function ArtifactPanel({
9231
- artifact,
9232
- artifacts,
9233
- activeArtifactId,
9234
- onSelectArtifact,
9235
- title = "\u4EA7\u7269",
9236
- emptyText = "\u6682\u65E0\u4EA7\u7269",
9237
- onClose,
9238
- onDownload,
9239
- safeRegistry,
9240
- user,
9241
- artifactViewers,
9242
- className,
9243
- style
9244
- }) {
9245
- const runtime = useArtifactPreviewRuntime(safeRegistry, user);
9246
- const [view, setView] = useState39("content");
9247
- const activeId = activeArtifactId ?? artifact?.id;
9248
- useEffect35(() => {
9249
- setView("content");
9250
- }, [activeId]);
9251
- const hasArtifacts = artifacts !== void 0 && artifacts.length > 0;
9252
- const headerTitle = view === "list" ? "\u4EA7\u7269\u76EE\u5F55" : artifact?.title ?? title;
9253
- return /* @__PURE__ */ jsxs80(
9254
- "aside",
9255
- {
9256
- "data-slot": "artifact-panel",
9257
- "data-kind": artifact?.kind,
9258
- className: cn("flex h-full min-w-0 flex-col", className),
9259
- style,
9260
- children: [
9261
- /* @__PURE__ */ jsxs80("div", { className: "flex shrink-0 items-center gap-1 border-b border-border px-2 py-2.5", children: [
9262
- hasArtifacts ? /* @__PURE__ */ jsx119(
9263
- Button2,
9264
- {
9265
- icon: "list",
9266
- variant: "text",
9267
- tooltip: view === "list" ? "\u8FD4\u56DE\u5185\u5BB9" : "\u4EA7\u7269\u76EE\u5F55",
9268
- onClick: () => setView((value) => value === "list" ? "content" : "list")
9269
- }
9270
- ) : null,
9271
- hasArtifacts ? /* @__PURE__ */ jsx119(
9272
- DropdownMenu2,
9273
- {
9274
- items: artifacts.map((item) => ({
9275
- label: item.title,
9276
- value: item.id,
9277
- icon: resolveArtifactIconName(item)
9278
- })),
9279
- onSelect: (id) => onSelectArtifact?.(id),
9280
- children: /* @__PURE__ */ jsxs80(
9281
- "span",
9282
- {
9283
- "data-slot": "artifact-panel-menu-trigger",
9284
- className: "flex min-w-0 cursor-pointer items-center gap-1 rounded-md px-1.5 py-1 text-sm font-medium hover:bg-accent",
9285
- children: [
9286
- view !== "list" && artifact ? /* @__PURE__ */ jsx119(ArtifactKindIcon, { artifact }) : null,
9287
- /* @__PURE__ */ jsx119("span", { className: "truncate", children: headerTitle }),
9288
- /* @__PURE__ */ jsx119(Icon, { name: "chevron-down", size: 12 })
9289
- ]
9290
- }
9291
- )
9292
- }
9293
- ) : /* @__PURE__ */ jsx119("span", { className: "min-w-0 flex-1 truncate px-1.5 text-sm font-medium", children: headerTitle }),
9294
- view !== "list" && artifact ? /* @__PURE__ */ jsx119("span", { className: "shrink-0 rounded-md bg-accent px-1.5 py-0.5 text-xs text-muted-foreground", children: artifact.kind }) : null,
9295
- /* @__PURE__ */ jsx119("span", { className: "flex-1" }),
9296
- artifact ? /* @__PURE__ */ jsx119(
9297
- Button2,
9298
- {
9299
- icon: "download",
9300
- variant: "text",
9301
- tooltip: "\u4E0B\u8F7D\u4EA7\u7269",
9302
- className: "px-2",
9303
- onClick: () => onDownload ? onDownload(artifact) : void downloadArtifact(artifact)
9304
- }
9305
- ) : null,
9306
- /* @__PURE__ */ jsx119(
9307
- Button2,
9308
- {
9309
- icon: "x",
9310
- variant: "text",
9311
- tooltip: "\u6536\u8D77\u4EA7\u7269\u533A",
9312
- className: "px-2",
9313
- onClick: () => onClose?.()
9314
- }
9315
- )
9316
- ] }),
9317
- /* @__PURE__ */ jsx119("div", { className: "scrollbar-subtle min-h-0 flex-1 overflow-auto p-4", children: view === "list" && artifacts !== void 0 ? /* @__PURE__ */ jsx119(
9318
- ArtifactListView,
9319
- {
9320
- nodes: buildArtifactTree(artifacts),
9321
- activeId,
9322
- onSelect: onSelectArtifact
9323
- }
9324
- ) : artifact ? /* @__PURE__ */ jsx119(
9325
- ArtifactContent,
9326
- {
9327
- artifact,
9328
- runtime,
9329
- viewers: artifactViewers
9330
- }
9331
- ) : /* @__PURE__ */ jsx119("p", { className: "text-sm text-muted-foreground", children: emptyText }) })
9332
- ]
9333
- }
9334
- );
9335
- }
9336
-
9337
- // src/components/chat/Attachment/index.tsx
9338
- import { jsx as jsx120, jsxs as jsxs81 } from "react/jsx-runtime";
9339
- var kindIcon2 = { image: "image", text: "file-text", video: "video", audio: "music", other: "file" };
9340
- function formatSize3(bytes) {
9341
- if (bytes < 1024) return `${bytes} B`;
9342
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
9343
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
9344
- }
9345
- function Attachment({ items = [], onRemove, onRetry, className, style }) {
9346
- return /* @__PURE__ */ jsx120("div", { "data-slot": "attachment", className: cn("flex flex-wrap gap-2", className), style, children: items.map((item, index) => {
9347
- const key = item.id ?? index;
9348
- const kind = inferKind(item.name, item.fileType);
9349
- const status = item.status ?? "done";
9350
- return /* @__PURE__ */ jsxs81(
9351
- "div",
9352
- {
9353
- "data-slot": "attachment-item",
9354
- "data-status": status,
9355
- className: "flex w-56 flex-col rounded-md border border-border px-2 py-1.5",
9356
- children: [
9357
- /* @__PURE__ */ jsxs81("div", { className: "flex items-center gap-1.5", children: [
9358
- /* @__PURE__ */ jsx120("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx120(Icon, { name: kindIcon2[kind], size: "sm" }) }),
9359
- /* @__PURE__ */ jsx120("span", { className: "min-w-0 flex-1 truncate text-sm", children: item.name }),
9360
- status === "uploading" ? /* @__PURE__ */ jsx120("span", { className: "shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none", children: /* @__PURE__ */ jsx120(Icon, { name: "loader-circle", size: "sm" }) }) : null,
9361
- status === "error" && onRetry ? /* @__PURE__ */ jsx120(
9362
- "button",
9363
- {
9364
- type: "button",
9365
- "data-slot": "attachment-retry",
9366
- "aria-label": `\u91CD\u8BD5 ${item.name}`,
9367
- className: "shrink-0 text-destructive hover:opacity-80",
9368
- onClick: () => onRetry(key),
9369
- children: /* @__PURE__ */ jsx120(Icon, { name: "refresh-ccw", size: "sm" })
9370
- }
9371
- ) : null,
9372
- onRemove ? /* @__PURE__ */ jsx120(
9373
- "button",
9374
- {
9375
- type: "button",
9376
- "data-slot": "attachment-remove",
9377
- "aria-label": `\u79FB\u9664 ${item.name}`,
9378
- className: "shrink-0 text-muted-foreground hover:text-foreground",
9379
- onClick: () => onRemove(key),
9380
- children: /* @__PURE__ */ jsx120(Icon, { name: "x", size: "sm" })
9381
- }
9382
- ) : null
9383
- ] }),
9384
- /* @__PURE__ */ jsxs81("div", { className: "mt-0.5 flex items-center gap-1.5 pl-5 text-xs", children: [
9385
- item.fileSize !== void 0 ? /* @__PURE__ */ jsx120("span", { className: "text-muted-foreground", children: formatSize3(item.fileSize) }) : null,
9386
- status === "error" ? /* @__PURE__ */ jsx120("span", { className: "text-destructive", children: "\u4E0A\u4F20\u5931\u8D25" }) : null
9387
- ] }),
9388
- status === "uploading" && item.progress !== void 0 ? /* @__PURE__ */ jsx120("div", { "data-slot": "attachment-progress", className: "mt-1 ml-5 h-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx120("div", { className: "h-full bg-primary transition-[width]", style: { width: `${item.progress}%` } }) }) : null
9389
- ]
9390
- },
9391
- key
9392
- );
9393
- }) });
9394
- }
9395
-
9396
- // src/components/chat/Bubble/index.tsx
9397
- import { jsx as jsx121 } from "react/jsx-runtime";
9398
- function Bubble2({ align = "start", variant = "muted", className, style, children }) {
9399
- return /* @__PURE__ */ jsx121(Bubble, { variant, align, className, style, children: /* @__PURE__ */ jsx121(BubbleContent, { children }) });
9400
- }
9401
-
9402
- // src/components/chat/Sender/index.tsx
9403
- import { jsx as jsx122, jsxs as jsxs82 } from "react/jsx-runtime";
9404
- function Sender({ name, avatar, avatarText, role, time, align = "start", className, style }) {
9405
- return /* @__PURE__ */ jsxs82(
9406
- "div",
9407
- {
9408
- "data-slot": "sender",
9409
- className: cn("flex items-center gap-2", align === "end" && "flex-row-reverse", className),
9410
- style,
9411
- children: [
9412
- /* @__PURE__ */ jsx122(Avatar2, { src: avatar, fallback: avatarText ?? name?.slice(0, 1) ?? "?", className: "size-7" }),
9413
- /* @__PURE__ */ jsxs82("div", { className: cn("flex min-w-0 items-baseline gap-1.5", align === "end" && "flex-row-reverse"), children: [
9414
- name ? /* @__PURE__ */ jsx122("span", { "data-slot": "sender-name", className: "truncate text-sm font-medium", children: name }) : null,
9415
- role ? /* @__PURE__ */ jsx122("span", { "data-slot": "sender-role", className: "shrink-0 text-xs text-muted-foreground", children: role }) : null,
9416
- time ? /* @__PURE__ */ jsx122("span", { "data-slot": "sender-time", className: "shrink-0 text-xs text-muted-foreground", children: time }) : null
9417
- ] })
9418
- ]
9419
- }
9420
- );
9421
- }
9422
-
9423
- // src/components/chat/StreamingText/index.tsx
9424
- import { useEffect as useEffect36, useState as useState40 } from "react";
9425
- import { jsx as jsx123, jsxs as jsxs83 } from "react/jsx-runtime";
9426
- function StreamingText({ content = "", streaming, typing, speed = 2, className, style }) {
9427
- const [revealed, setRevealed] = useState40(typing ? 0 : content.length);
9428
- useEffect36(() => {
9429
- if (!typing) {
9430
- setRevealed(content.length);
9431
- return;
9432
- }
9433
- setRevealed(0);
9434
- const timer = window.setInterval(() => {
9435
- setRevealed((value) => {
9436
- if (value >= content.length) {
9437
- window.clearInterval(timer);
9438
- return value;
9439
- }
9440
- return Math.min(content.length, value + speed);
9441
- });
9442
- }, 24);
9443
- return () => window.clearInterval(timer);
9444
- }, [content, typing, speed]);
9445
- const typingDone = !typing || revealed >= content.length;
9446
- const showCursor = streaming || typing && !typingDone;
9447
- return /* @__PURE__ */ jsxs83("span", { "data-slot": "streaming-text", className: cn("whitespace-pre-wrap", className), style, children: [
9448
- typing ? content.slice(0, revealed) : content,
9449
- showCursor ? /* @__PURE__ */ jsx123(
9450
- "span",
9451
- {
9452
- "data-slot": "streaming-text-cursor",
9453
- "aria-hidden": "true",
9454
- className: "ml-0.5 inline-block animate-pulse text-primary motion-reduce:animate-none",
9455
- children: "\u258D"
9456
- }
9457
- ) : null
9458
- ] });
9459
- }
9460
-
9461
- // src/components/chat/Thinking/index.tsx
9462
- import { useState as useState41 } from "react";
9463
- import { jsx as jsx124, jsxs as jsxs84 } from "react/jsx-runtime";
9464
- function Thinking({
9465
- content,
9466
- status = "thinking",
9467
- title,
9468
- expanded,
9469
- defaultExpanded,
9470
- onToggle,
9471
- className,
9472
- style
9473
- }) {
9474
- const [innerExpanded, setInnerExpanded] = useState41(defaultExpanded ?? status === "thinking");
9475
- const isExpanded = expanded ?? innerExpanded;
9476
- const resolvedTitle = title ?? (status === "thinking" ? "\u601D\u8003\u4E2D\u2026" : "\u601D\u8003\u5B8C\u6210");
9477
- const toggle = () => {
9478
- if (expanded === void 0) {
9479
- setInnerExpanded(!isExpanded);
9480
- }
9481
- onToggle?.(!isExpanded);
9482
- };
9483
- return /* @__PURE__ */ jsxs84(
9484
- "div",
9485
- {
9486
- "data-slot": "thinking",
9487
- "data-status": status,
9488
- className: cn("w-full", className),
9489
- style,
9490
- children: [
9491
- /* @__PURE__ */ jsxs84(
9492
- "button",
9493
- {
9494
- type: "button",
9495
- "data-slot": "thinking-trigger",
9496
- "aria-expanded": isExpanded,
9497
- className: "flex w-full cursor-pointer items-center gap-2 py-1 text-left text-sm",
9498
- onClick: toggle,
9499
- children: [
9500
- /* @__PURE__ */ jsx124("span", { className: cn("text-muted-foreground", status === "thinking" && "animate-pulse motion-reduce:animate-none"), children: /* @__PURE__ */ jsx124(Icon, { name: "brain", size: "sm" }) }),
9501
- /* @__PURE__ */ jsx124("span", { "data-slot": "thinking-title", className: "text-muted-foreground", children: resolvedTitle }),
9502
- /* @__PURE__ */ jsx124("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx124(Icon, { name: isExpanded ? "chevron-down" : "chevron-right", size: "sm" }) })
9503
- ]
9504
- }
9505
- ),
9506
- isExpanded && content ? /* @__PURE__ */ jsx124(
9507
- "div",
9508
- {
9509
- "data-slot": "thinking-content",
9510
- className: "py-1 pl-6 text-sm whitespace-pre-wrap text-muted-foreground",
9511
- children: content
9512
- }
9513
- ) : null
9514
- ]
9515
- }
9516
- );
9517
- }
9518
-
9519
- // src/components/chat/ToolCall/index.tsx
9520
- import { useState as useState42 } from "react";
9521
- import { Fragment as Fragment15, jsx as jsx125, jsxs as jsxs85 } from "react/jsx-runtime";
9522
- var statusIcon3 = {
9523
- running: { icon: "loader-circle", className: "text-info", spin: true },
9524
- done: { icon: "circle-check", className: "text-success" },
9525
- error: { icon: "circle-x", className: "text-destructive" }
9526
- };
9527
- function renderValue(value) {
9528
- if (value === void 0 || value === null) return "";
9529
- if (typeof value === "string") return value;
9530
- try {
9531
- return JSON.stringify(value, null, 2);
9532
- } catch {
9533
- return String(value);
9534
- }
9535
- }
9536
- function ToolCall({ name, params, status = "done", result, durationMs, defaultExpanded, className, style }) {
9537
- const [expanded, setExpanded] = useState42(defaultExpanded ?? status === "running");
9538
- const meta = statusIcon3[status];
9539
- const paramsText = renderValue(params);
9540
- const hasDetail = Boolean(paramsText) || Boolean(result);
9541
- const headerInner = /* @__PURE__ */ jsxs85(Fragment15, { children: [
9542
- /* @__PURE__ */ jsx125("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx125(Icon, { name: "terminal", size: "sm" }) }),
9543
- /* @__PURE__ */ jsx125("span", { "data-slot": "tool-call-name", className: "min-w-0 truncate font-medium", children: name ?? "\u5DE5\u5177\u8C03\u7528" }),
9544
- /* @__PURE__ */ jsx125("span", { className: cn("shrink-0", meta.className, meta.spin && "animate-spin motion-reduce:animate-none"), children: /* @__PURE__ */ jsx125(Icon, { name: meta.icon, size: "sm" }) }),
9545
- durationMs !== void 0 ? /* @__PURE__ */ jsxs85("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
9546
- (durationMs / 1e3).toFixed(1),
9547
- "s"
9548
- ] }) : null,
9549
- hasDetail ? /* @__PURE__ */ jsx125("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx125(Icon, { name: expanded ? "chevron-down" : "chevron-right", size: "sm" }) }) : null
9550
- ] });
9551
- return /* @__PURE__ */ jsxs85(
9552
- "div",
9553
- {
9554
- "data-slot": "tool-call",
9555
- "data-status": status,
9556
- className: cn("w-full", className),
9557
- style,
9558
- children: [
9559
- hasDetail ? /* @__PURE__ */ jsx125(
9560
- "button",
9561
- {
9562
- type: "button",
9563
- "data-slot": "tool-call-trigger",
9564
- "aria-expanded": expanded,
9565
- className: "flex w-full cursor-pointer items-center gap-2 py-1 text-left text-sm",
9566
- onClick: () => setExpanded((value) => !value),
9567
- children: headerInner
9568
- }
9569
- ) : /* @__PURE__ */ jsx125(
9570
- "div",
9571
- {
9572
- "data-slot": "tool-call-trigger",
9573
- className: "flex w-full items-center gap-2 py-1 text-left text-sm",
9574
- children: headerInner
9575
- }
9576
- ),
9577
- hasDetail && expanded ? /* @__PURE__ */ jsxs85("div", { "data-slot": "tool-call-body", className: "flex flex-col gap-2 py-1 pl-6", children: [
9578
- paramsText ? /* @__PURE__ */ jsxs85("div", { children: [
9579
- /* @__PURE__ */ jsx125("div", { className: "text-xs font-medium text-muted-foreground", children: "\u53C2\u6570" }),
9580
- /* @__PURE__ */ jsx125("pre", { "data-slot": "tool-call-params", className: "mt-1 overflow-x-auto text-xs whitespace-pre-wrap text-muted-foreground", children: paramsText })
9581
- ] }) : null,
9582
- result ? /* @__PURE__ */ jsxs85("div", { children: [
9583
- /* @__PURE__ */ jsx125("div", { className: "text-xs font-medium text-muted-foreground", children: "\u7ED3\u679C" }),
9584
- /* @__PURE__ */ jsx125("pre", { "data-slot": "tool-call-result", className: "mt-1 overflow-x-auto text-xs whitespace-pre-wrap text-muted-foreground", children: result })
9585
- ] }) : null
9586
- ] }) : null
9587
- ]
9588
- }
9589
- );
9590
- }
9591
-
9592
- // src/components/chat/ChatMessage/index.tsx
9593
- import { jsx as jsx126, jsxs as jsxs86 } from "react/jsx-runtime";
9594
- function PartView({
9595
- part,
9596
- role,
9597
- onOpenArtifacts
9598
- }) {
9599
- if (role === "system" && (part.type === "text" || part.type === "markdown")) {
9600
- return /* @__PURE__ */ jsx126(
9601
- "div",
9602
- {
9603
- "data-slot": "chat-message-system-part",
9604
- className: "w-full text-center text-xs text-muted-foreground",
9605
- children: part.type === "markdown" ? /* @__PURE__ */ jsx126(MarkdownDslComponent, { content: part.content }) : part.content
9606
- }
9607
- );
9608
- }
9609
- switch (part.type) {
9610
- case "text":
9611
- return /* @__PURE__ */ jsx126(Bubble2, { align: role === "user" ? "end" : "start", variant: role === "user" ? "default" : "muted", children: /* @__PURE__ */ jsx126("span", { className: "text-sm whitespace-pre-wrap", children: part.content }) });
9612
- case "markdown":
9613
- return /* @__PURE__ */ jsx126(Bubble2, { align: role === "user" ? "end" : "start", variant: role === "user" ? "default" : "ghost", children: /* @__PURE__ */ jsx126(MarkdownDslComponent, { content: part.content }) });
9614
- case "thinking":
9615
- return /* @__PURE__ */ jsx126(Thinking, { content: part.content, status: part.status ?? "done" });
9616
- case "tool-call":
9617
- return /* @__PURE__ */ jsx126(ToolCall, { name: part.name, params: part.params, status: part.status ?? "done", result: part.result });
9618
- case "tool-result":
9619
- return /* @__PURE__ */ jsx126("pre", { "data-slot": "chat-message-tool-result", className: "overflow-x-auto rounded-md bg-muted p-2 text-xs whitespace-pre-wrap", children: part.content });
9620
- case "status":
9621
- return /* @__PURE__ */ jsxs86("div", { "data-slot": "chat-message-status", className: "flex w-fit items-center gap-1.5 text-muted-foreground", children: [
9622
- /* @__PURE__ */ jsx126(Icon, { name: "clock", size: "sm" }),
9623
- part.text
9624
- ] });
9625
- case "artifacts": {
9626
- const artifactsClass = "flex w-fit items-center gap-1.5 text-muted-foreground";
9627
- const label = `\u5DF2\u751F\u6210 ${part.count} \u4E2A\u4EA7\u7269`;
9628
- return onOpenArtifacts ? /* @__PURE__ */ jsxs86(
9629
- "button",
9630
- {
9631
- type: "button",
9632
- "data-slot": "chat-message-artifacts",
9633
- className: cn(artifactsClass, "cursor-pointer hover:text-foreground"),
9634
- onClick: onOpenArtifacts,
9635
- children: [
9636
- /* @__PURE__ */ jsx126(Icon, { name: "package", size: "sm" }),
9637
- label,
9638
- /* @__PURE__ */ jsx126(Icon, { name: "chevron-right", size: 12 })
9639
- ]
9640
- }
9641
- ) : /* @__PURE__ */ jsxs86("div", { "data-slot": "chat-message-artifacts", className: artifactsClass, children: [
9642
- /* @__PURE__ */ jsx126(Icon, { name: "package", size: "sm" }),
9643
- label
9644
- ] });
9645
- }
9646
- case "error":
9647
- return /* @__PURE__ */ jsxs86("div", { "data-slot": "chat-message-error", className: "flex items-center gap-1.5 text-sm text-destructive", children: [
9648
- /* @__PURE__ */ jsx126(Icon, { name: "circle-alert", size: "sm" }),
9649
- part.message
9650
- ] });
9651
- default:
9652
- return null;
9653
- }
9654
- }
9655
- var ACTION_ICON_BY_NAME = {
9656
- copy: "copy",
9657
- retry: "rotate-ccw",
9658
- regenerate: "refresh-ccw",
9659
- edit: "pencil",
9660
- like: "thumbs-up",
9661
- dislike: "thumbs-down",
9662
- share: "share",
9663
- delete: "trash"
9664
- };
9665
- function ChatMessage({
9666
- role = "assistant",
9667
- parts = [],
9668
- status = "completed",
9669
- agent,
9670
- time,
9671
- showSender = false,
9672
- actions = [],
9673
- onAction,
9674
- onOpenArtifacts,
9675
- className,
9676
- style
9677
- }) {
9678
- const isUser = role === "user";
9679
- const isSystem = role === "system";
9680
- const senderName = isUser ? "\u6211" : agent?.name ?? "\u52A9\u624B";
9681
- return /* @__PURE__ */ jsxs86(
9682
- "div",
9683
- {
9684
- "data-slot": "chat-message",
9685
- "data-role": role,
9686
- "data-status": status,
9687
- className: cn(
9688
- "flex w-full flex-col gap-1.5",
9689
- isUser && "items-end",
9690
- // system 居中弱提示(参考 @pactor-app/chat MessageBubble)
9691
- isSystem && "items-center text-muted-foreground",
9692
- className
9693
- ),
9694
- style,
9695
- children: [
9696
- showSender ? /* @__PURE__ */ jsx126(
9697
- Sender,
9698
- {
9699
- name: senderName,
9700
- avatarText: isUser ? void 0 : agent?.icon ? void 0 : senderName.slice(0, 1),
9701
- time,
9702
- align: isUser ? "end" : "start"
9703
- }
9704
- ) : null,
9705
- /* @__PURE__ */ jsxs86("div", { className: cn("flex min-w-0 w-full max-w-full flex-col gap-2", isUser && "items-end", isSystem && "items-center"), children: [
9706
- parts.map((part, index) => /* @__PURE__ */ jsx126(PartView, { part, role, onOpenArtifacts }, index)),
9707
- status === "streaming" ? /* @__PURE__ */ jsx126(StreamingText, { content: "", streaming: true }) : null
9708
- ] }),
9709
- actions.length > 0 && (status === "completed" || status === "error") ? /* @__PURE__ */ jsx126("div", { "data-slot": "chat-message-actions", className: "flex items-center gap-1 mt-2", children: actions.map((action) => {
9710
- const label = action.label ?? action.name;
9711
- const iconName = action.icon ?? ACTION_ICON_BY_NAME[action.name];
9712
- if (iconName) {
9713
- return /* @__PURE__ */ jsx126(Tooltip2, { content: label, side: "top", children: /* @__PURE__ */ jsx126(
9714
- "button",
9715
- {
9716
- type: "button",
9717
- "data-slot": "chat-message-action",
9718
- "data-action": action.name,
9719
- "aria-label": label,
9720
- className: "flex cursor-pointer items-center rounded-sm p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
9721
- onClick: () => onAction?.(action.name),
9722
- children: /* @__PURE__ */ jsx126(Icon, { name: iconName, size: "sm" })
9723
- }
9724
- ) }, action.name);
9725
- }
9726
- return /* @__PURE__ */ jsx126(
9727
- "button",
9728
- {
9729
- type: "button",
9730
- "data-slot": "chat-message-action",
9731
- "data-action": action.name,
9732
- className: "flex items-center cursor-pointer gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
9733
- onClick: () => onAction?.(action.name),
9734
- children: label
9735
- },
9736
- action.name
9737
- );
9738
- }) }) : null
9739
- ]
9740
- }
9741
- );
9742
- }
9743
-
9744
- // src/components/chat/ChatPage/index.tsx
9745
- import { useRenderer as useRenderer21 } from "@pactor-app/runtime";
9746
- import { isValidElement as isValidElement3, useEffect as useEffect39, useState as useState46 } from "react";
9747
-
9748
- // src/components/chat/Composer/index.tsx
9749
- import {
9750
- useRef as useRef12,
9751
- useState as useState44
9752
- } from "react";
9753
-
9754
- // src/components/chat/Composer/mention-input.tsx
9755
- import {
9756
- forwardRef,
9757
- useEffect as useEffect37,
9758
- useImperativeHandle,
9759
- useRef as useRef11,
9760
- useState as useState43
9761
- } from "react";
9762
- import { renderToStaticMarkup } from "react-dom/server";
9763
- import { Fragment as Fragment16, jsx as jsx127, jsxs as jsxs87 } from "react/jsx-runtime";
9764
- var NBSP = "\xA0";
9765
- var CHIP_ATTR = "data-mention-chip";
9766
- function escapeRegExp(text) {
9767
- return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9768
- }
9769
- function isChipElement(node) {
9770
- return node instanceof HTMLElement && node.hasAttribute(CHIP_ATTR);
9771
- }
9772
- function serializeNode(node) {
9773
- let out = "";
9774
- node.childNodes.forEach((child) => {
9775
- if (child.nodeType === Node.TEXT_NODE) {
9776
- out += (child.textContent ?? "").replace(/\u00A0/g, " ");
9777
- } else if (isChipElement(child)) {
9778
- out += `${child.dataset["trigger"] ?? ""}${child.dataset["label"] ?? ""}`;
9779
- } else if (child instanceof HTMLBRElement) {
9780
- out += "\n";
9781
- } else if (child instanceof HTMLElement) {
9782
- const inner = serializeNode(child).replace(/\n$/, "");
9783
- out += (out === "" || out.endsWith("\n") ? "" : "\n") + inner + "\n";
9784
- }
9785
- });
9786
- return out;
9787
- }
9788
- function collectChips(field) {
9789
- return Array.from(field.querySelectorAll(`[${CHIP_ATTR}]`)).map((el) => ({
9790
- id: el.dataset["id"] ?? "",
9791
- label: el.dataset["label"] ?? "",
9792
- trigger: el.dataset["trigger"] ?? ""
9793
- }));
9794
- }
9795
- function detectActiveMention(field, sources) {
9796
- const selection = window.getSelection();
9797
- if (!selection || selection.rangeCount === 0 || !selection.isCollapsed) {
9798
- return null;
9799
- }
9800
- const anchor = selection.anchorNode;
9801
- if (!anchor || anchor.nodeType !== Node.TEXT_NODE || !field.contains(anchor)) {
9802
- return null;
9803
- }
9804
- const caretOffset = selection.anchorOffset;
9805
- const beforeCaret = (anchor.textContent ?? "").slice(0, caretOffset);
9806
- for (const source of sources) {
9807
- const trigger = source.trigger;
9808
- const match = new RegExp(
9809
- `(?:^|\\s)${escapeRegExp(trigger)}([^\\s${escapeRegExp(trigger)}]*)$`
9810
- ).exec(beforeCaret);
9811
- if (match) {
9812
- const query = match[1] ?? "";
9813
- return {
9814
- source,
9815
- query,
9816
- node: anchor,
9817
- triggerStart: caretOffset - query.length - trigger.length,
9818
- caretOffset
9819
- };
9820
- }
9821
- }
9822
- return null;
9823
- }
9824
- function chipIcon(item, source) {
9825
- return item.icon ?? source.icon ?? (source.trigger === "@" ? "user" : "file-text");
9826
- }
9827
- var MentionInput = forwardRef(
9828
- function MentionInput2({
9829
- value,
9830
- placeholder,
9831
- disabled,
9832
- sources,
9833
- chips,
9834
- onChipsChange,
9835
- onChange,
9836
- onSubmit,
9837
- className,
9838
- style
9839
- }, ref) {
9840
- const fieldRef = useRef11(null);
9841
- const [menu, setMenu] = useState43(null);
9842
- const [empty, setEmpty] = useState43(true);
9843
- const suppressSyncRef = useRef11(false);
9844
- const composingRef = useRef11(false);
9845
- const emitChips = () => {
9846
- if (suppressSyncRef.current || !fieldRef.current) {
9847
- return;
9848
- }
9849
- onChipsChange?.(collectChips(fieldRef.current));
9850
- };
9851
- const serialize = () => fieldRef.current ? serializeNode(fieldRef.current).replace(/\n$/, "") : "";
9852
- const buildChipElement = (item, source) => {
9853
- const chip = document.createElement("span");
9854
- chip.setAttribute(CHIP_ATTR, "");
9855
- chip.dataset["id"] = item.id;
9856
- chip.dataset["label"] = item.label;
9857
- chip.dataset["trigger"] = item.trigger ?? source.trigger;
9858
- chip.contentEditable = "false";
9859
- chip.className = "mx-0.5 inline-flex items-center gap-1 rounded-md bg-muted px-1.5 py-0.5 align-middle text-xs text-foreground";
9860
- const iconMarkup = renderToStaticMarkup(
9861
- /* @__PURE__ */ jsx127(Icon, { name: chipIcon(item, source), size: 12 })
9862
- );
9863
- const label = document.createElement("span");
9864
- label.textContent = item.label;
9865
- chip.innerHTML = iconMarkup;
9866
- chip.appendChild(label);
9867
- return chip;
9868
- };
9869
- const insertChipElement = (item, source, replace) => {
9870
- const field = fieldRef.current;
9871
- if (!field) {
9872
- return;
9873
- }
9874
- const chip = buildChipElement(item, source);
9875
- let caretNode;
9876
- if (replace) {
9877
- const { node, start, end } = replace;
9878
- const text = node.textContent ?? "";
9879
- node.textContent = text.slice(0, start);
9880
- caretNode = document.createTextNode(`${NBSP}${text.slice(end)}`);
9881
- node.after(chip, caretNode);
9882
- } else {
9883
- caretNode = document.createTextNode(NBSP);
9884
- const selection2 = window.getSelection();
9885
- if (selection2 && selection2.rangeCount > 0 && selection2.anchorNode !== null && field.contains(selection2.anchorNode)) {
9886
- const range = selection2.getRangeAt(0);
9887
- range.deleteContents();
9888
- range.insertNode(caretNode);
9889
- range.insertNode(chip);
9890
- } else {
9891
- field.append(chip, caretNode);
9892
- }
9893
- }
9894
- const selection = window.getSelection();
9895
- if (selection) {
9896
- const range = document.createRange();
9897
- range.setStart(caretNode, 1);
9898
- range.collapse(true);
9899
- selection.removeAllRanges();
9900
- selection.addRange(range);
9901
- }
9902
- setEmpty(false);
9903
- onChange?.(serialize());
9904
- emitChips();
9905
- };
9906
- useImperativeHandle(ref, () => ({
9907
- insertChip: (item, source) => {
9908
- fieldRef.current?.focus();
9909
- insertChipElement(item, source);
9910
- },
9911
- focus: () => fieldRef.current?.focus()
9912
- }));
9913
- useEffect37(() => {
9914
- const field = fieldRef.current;
9915
- if (field && value === "" && serializeNode(field).trim() !== "") {
9916
- suppressSyncRef.current = true;
9917
- field.innerHTML = "";
9918
- setEmpty(true);
9919
- setMenu(null);
9920
- suppressSyncRef.current = false;
9921
- }
9922
- }, [value]);
9923
- useEffect37(() => {
9924
- const field = fieldRef.current;
9925
- if (!field) {
9926
- return;
9927
- }
9928
- const keep = new Set(chips.map((chip) => `${chip.trigger}:${chip.id}`));
9929
- const stale = Array.from(field.querySelectorAll(`[${CHIP_ATTR}]`)).filter(
9930
- (el) => !keep.has(
9931
- `${el.dataset["trigger"]}:${el.dataset["id"]}`
9932
- )
9933
- );
9934
- if (stale.length > 0) {
9935
- suppressSyncRef.current = true;
9936
- stale.forEach((el) => el.remove());
9937
- suppressSyncRef.current = false;
9938
- setEmpty(serializeNode(field).trim() === "");
9939
- }
9940
- }, [chips]);
9941
- const closeMenu = () => setMenu(null);
9942
- const filteredItems = (state) => state.query ? state.source.items.filter((item) => item.label.includes(state.query)) : state.source.items;
9943
- const refreshMenu = () => {
9944
- const field = fieldRef.current;
9945
- if (!field) {
9946
- return;
9947
- }
9948
- const active = detectActiveMention(field, sources);
9949
- if (!active) {
9950
- setMenu(null);
9951
- return;
9952
- }
9953
- const range = document.createRange();
9954
- range.setStart(active.node, active.triggerStart);
9955
- range.setEnd(active.node, active.caretOffset);
9956
- const rect = typeof range.getBoundingClientRect === "function" ? range.getBoundingClientRect() : field.getBoundingClientRect();
9957
- setMenu((prev) => ({
9958
- source: active.source,
9959
- query: active.query,
9960
- left: rect.left,
9961
- top: rect.top,
9962
- // 过滤结果变化后候选下标钳制在有效范围内(DSL 每次渲染产出新
9963
- // props 对象,source 按 trigger 比较而非引用)
9964
- activeIndex: Math.min(
9965
- prev?.source.trigger === active.source.trigger ? prev.activeIndex : 0,
9966
- Math.max(filteredItems({ source: active.source, query: active.query, left: 0, top: 0, activeIndex: 0 }).length - 1, 0)
9967
- )
9968
- }));
9969
- };
9970
- const selectMenuItem = (item) => {
9971
- const field = fieldRef.current;
9972
- if (!field || !menu) {
9973
- return;
9974
- }
9975
- const active = detectActiveMention(field, sources);
9976
- insertChipElement(
9977
- { ...item, trigger: item.trigger ?? menu.source.trigger },
9978
- menu.source,
9979
- active && active.source.trigger === menu.source.trigger ? { node: active.node, start: active.triggerStart, end: active.caretOffset } : void 0
9980
- );
9981
- setMenu(null);
9982
- };
9983
- const handleInput = () => {
9984
- const field = fieldRef.current;
9985
- if (!field) {
9986
- return;
9987
- }
9988
- if ((field.textContent ?? "") === "" && field.querySelector(`[${CHIP_ATTR}]`) === null) {
9989
- field.innerHTML = "";
9990
- }
9991
- setEmpty(serializeNode(field).trim() === "");
9992
- onChange?.(serialize());
9993
- refreshMenu();
9994
- };
9995
- const handleKeyDown = (event) => {
9996
- if (menu) {
9997
- const items = filteredItems(menu);
9998
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
9999
- event.preventDefault();
10000
- if (items.length > 0) {
10001
- const delta = event.key === "ArrowDown" ? 1 : -1;
10002
- setMenu({
10003
- ...menu,
10004
- activeIndex: (menu.activeIndex + delta + items.length) % items.length
10005
- });
10006
- }
10007
- return;
10008
- }
10009
- if (event.key === "Enter" && !composingRef.current && !event.nativeEvent.isComposing) {
10010
- event.preventDefault();
10011
- const item = items[Math.min(menu.activeIndex, items.length - 1)];
10012
- if (item) {
10013
- selectMenuItem(item);
10014
- } else {
10015
- closeMenu();
10016
- }
10017
- return;
10018
- }
10019
- if (event.key === "Escape") {
10020
- event.preventDefault();
10021
- closeMenu();
10022
- return;
10023
- }
10024
- }
10025
- if (event.key === "Enter" && !event.shiftKey && !composingRef.current && !event.nativeEvent.isComposing) {
10026
- event.preventDefault();
10027
- const text = serialize();
10028
- if (text.trim() !== "" && !disabled) {
10029
- onSubmit?.(text);
10030
- }
10031
- return;
10032
- }
10033
- if (event.key === "Backspace") {
10034
- const selection = window.getSelection();
10035
- const field = fieldRef.current;
10036
- if (!selection || !selection.isCollapsed || !field) {
10037
- return;
10038
- }
10039
- let chip = null;
10040
- const anchor = selection.anchorNode;
10041
- if (anchor === field && selection.anchorOffset > 0) {
10042
- const before = field.childNodes[selection.anchorOffset - 1];
10043
- if (isChipElement(before)) {
10044
- chip = before;
10045
- }
10046
- } else if (anchor?.nodeType === Node.TEXT_NODE && selection.anchorOffset === 0) {
10047
- if (isChipElement(anchor.previousSibling)) {
10048
- chip = anchor.previousSibling;
10049
- }
10050
- }
10051
- if (chip) {
10052
- event.preventDefault();
10053
- chip.remove();
10054
- onChange?.(serialize());
10055
- emitChips();
10056
- setEmpty(serializeNode(field).trim() === "");
10057
- }
10058
- }
10059
- };
10060
- const menuItems = menu ? filteredItems(menu) : [];
10061
- const activeIndex = Math.min(menu?.activeIndex ?? 0, Math.max(menuItems.length - 1, 0));
10062
- return /* @__PURE__ */ jsxs87("div", { className: "relative", children: [
10063
- /* @__PURE__ */ jsx127(
10064
- "div",
10065
- {
10066
- ref: fieldRef,
10067
- "data-slot": "mention-input",
10068
- role: "textbox",
10069
- "aria-multiline": "true",
10070
- "aria-placeholder": placeholder,
10071
- contentEditable: !disabled,
10072
- suppressContentEditableWarning: true,
10073
- className: cn(
10074
- "min-h-[60px] w-full px-4 pt-3 pb-1 text-sm whitespace-pre-wrap break-words outline-none",
10075
- className
10076
- ),
10077
- style,
10078
- onInput: handleInput,
10079
- onKeyDown: handleKeyDown,
10080
- onCompositionStart: () => {
10081
- composingRef.current = true;
10082
- },
10083
- onCompositionEnd: () => {
10084
- composingRef.current = false;
10085
- },
10086
- onPaste: (event) => {
10087
- event.preventDefault();
10088
- const text = event.clipboardData?.getData("text/plain") ?? "";
10089
- if (text) {
10090
- document.execCommand("insertText", false, text);
10091
- }
10092
- }
10093
- }
10094
- ),
10095
- empty && placeholder !== void 0 ? /* @__PURE__ */ jsx127("span", { className: "pointer-events-none absolute top-3 left-4 text-sm text-muted-foreground", children: placeholder }) : null,
10096
- menu ? /* @__PURE__ */ jsxs87(Fragment16, { children: [
10097
- /* @__PURE__ */ jsx127(
10098
- "div",
10099
- {
10100
- className: "fixed inset-0 z-40",
10101
- onMouseDown: (event) => {
10102
- event.preventDefault();
10103
- closeMenu();
10104
- }
10105
- }
10106
- ),
10107
- /* @__PURE__ */ jsxs87(
10108
- "div",
10109
- {
10110
- "data-slot": "mention-menu",
10111
- role: "listbox",
10112
- className: "scrollbar-subtle fixed z-50 max-h-72 w-64 overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
10113
- style: {
10114
- left: menu.left,
10115
- bottom: typeof window === "undefined" ? void 0 : window.innerHeight - menu.top + 6
10116
- },
10117
- children: [
10118
- /* @__PURE__ */ jsx127("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: menu.source.label }),
10119
- menuItems.length === 0 ? /* @__PURE__ */ jsx127("div", { className: "px-2 py-2 text-xs text-muted-foreground", children: "\u65E0\u5339\u914D\u9879" }) : menuItems.map((item, index) => /* @__PURE__ */ jsxs87(
10120
- "button",
10121
- {
10122
- type: "button",
10123
- role: "option",
10124
- "aria-selected": index === activeIndex,
10125
- className: cn(
10126
- "flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm",
10127
- index === activeIndex ? "bg-accent" : "hover:bg-accent"
10128
- ),
10129
- onMouseDown: (event) => {
10130
- event.preventDefault();
10131
- selectMenuItem(item);
10132
- },
10133
- children: [
10134
- /* @__PURE__ */ jsx127(Icon, { name: chipIcon(item, menu.source), size: "sm" }),
10135
- /* @__PURE__ */ jsx127("span", { className: "min-w-0 flex-1 truncate", children: item.label })
10136
- ]
10137
- },
10138
- item.id
10139
- ))
10140
- ]
10141
- }
10142
- )
10143
- ] }) : null
10144
- ] });
10145
- }
10146
- );
10147
-
10148
- // src/components/chat/Composer/index.tsx
10149
- import { Fragment as Fragment17, jsx as jsx128, jsxs as jsxs88 } from "react/jsx-runtime";
10150
- var toolButtonClass3 = "flex cursor-pointer items-center rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground";
10151
- var popoverPanelClass = "absolute bottom-full left-0 z-50 mb-2 max-h-72 w-64 overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md";
10152
- var popoverItemClass = "flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent";
10153
- function Composer({
10154
- value,
10155
- placeholder,
10156
- disabled,
10157
- loading,
10158
- rows,
10159
- attachments = [],
10160
- onRemoveAttachment,
10161
- onRetryAttachment,
10162
- onAttachFiles,
10163
- onChange,
10164
- onSubmit,
10165
- models,
10166
- model,
10167
- onModelChange,
10168
- contexts,
10169
- contextItems,
10170
- onContextItemsChange,
10171
- footer,
10172
- className,
10173
- style
10174
- }) {
10175
- const textControlled = value !== void 0;
10176
- const [innerText, setInnerText] = useState44("");
10177
- const text = textControlled ? value : innerText;
10178
- const [dragOver, setDragOver] = useState44(false);
10179
- const [plusOpen, setPlusOpen] = useState44(false);
10180
- const [modelOpen, setModelOpen] = useState44(false);
10181
- const fileInputRef = useRef12(null);
10182
- const mentionRef = useRef12(null);
10183
- const sources = contexts ?? [];
10184
- const rich = sources.length > 0;
10185
- const plusSources = sources.filter((s) => s.trigger === "+");
10186
- const plusEnabled = plusSources.length > 0;
10187
- const modelEnabled = models !== void 0 && models.length > 0;
10188
- const currentModel = models?.find((m) => m.value === model) ?? models?.[0];
10189
- const [innerChips, setInnerChips] = useState44([]);
10190
- const chipsControlled = contextItems !== void 0;
10191
- const chips = chipsControlled ? contextItems : innerChips;
10192
- const handleChipsChange = (next) => {
10193
- if (!chipsControlled) {
10194
- setInnerChips(next);
10195
- }
10196
- onContextItemsChange?.(next);
10197
- };
10198
- const closePopovers = () => {
10199
- setPlusOpen(false);
10200
- setModelOpen(false);
10201
- };
10202
- const handleChange = (next) => {
10203
- if (!textControlled) {
10204
- setInnerText(next);
10205
- }
10206
- onChange?.(next);
10207
- };
10208
- const handleSubmit = (submitted) => {
10209
- onSubmit?.(submitted);
10210
- if (!textControlled) {
10211
- setInnerText("");
10212
- }
10213
- closePopovers();
10214
- };
10215
- const selectContext = (item, source) => {
10216
- if (rich) {
10217
- mentionRef.current?.insertChip(item, source);
10218
- } else {
10219
- const chip = { ...item, trigger: item.trigger ?? source.trigger };
10220
- const duplicated = chips.some(
10221
- (existing) => existing.id === chip.id && existing.trigger === chip.trigger
10222
- );
10223
- if (!duplicated) {
10224
- handleChipsChange([...chips, chip]);
10225
- }
10226
- }
10227
- closePopovers();
10228
- };
10229
- const emitFiles = (files) => {
10230
- if (files.length > 0 && onAttachFiles) {
10231
- onAttachFiles(files);
10232
- }
10233
- };
10234
- const handleDrop = (event) => {
10235
- if (!onAttachFiles) {
10236
- return;
10237
- }
10238
- event.preventDefault();
10239
- setDragOver(false);
10240
- emitFiles(Array.from(event.dataTransfer?.files ?? []));
10241
- };
10242
- const toolbarStart = plusEnabled || onAttachFiles ? /* @__PURE__ */ jsxs88(Fragment17, { children: [
10243
- plusEnabled ? /* @__PURE__ */ jsx128(
10244
- "button",
10245
- {
10246
- type: "button",
10247
- "aria-label": "\u6DFB\u52A0\u4E0A\u4E0B\u6587",
10248
- title: "\u6DFB\u52A0\u4E0A\u4E0B\u6587",
10249
- className: toolButtonClass3,
10250
- onClick: () => {
10251
- setPlusOpen((prev) => !prev);
10252
- setModelOpen(false);
10253
- },
10254
- children: /* @__PURE__ */ jsx128(Icon, { name: "plus", size: "sm" })
10255
- }
10256
- ) : null,
10257
- onAttachFiles ? /* @__PURE__ */ jsx128(
10258
- "button",
10259
- {
10260
- type: "button",
10261
- "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
10262
- title: "\u6DFB\u52A0\u9644\u4EF6",
10263
- className: toolButtonClass3,
10264
- onClick: () => fileInputRef.current?.click(),
10265
- children: /* @__PURE__ */ jsx128(Icon, { name: "paperclip", size: "sm" })
10266
- }
10267
- ) : null
10268
- ] }) : void 0;
10269
- const toolbarEnd = modelEnabled ? /* @__PURE__ */ jsxs88(
10270
- "button",
10271
- {
10272
- type: "button",
10273
- "aria-label": "\u9009\u62E9\u6A21\u578B",
10274
- title: "\u9009\u62E9\u6A21\u578B",
10275
- className: "flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
10276
- onClick: () => {
10277
- setModelOpen((prev) => !prev);
10278
- setPlusOpen(false);
10279
- },
10280
- children: [
10281
- currentModel?.label,
10282
- /* @__PURE__ */ jsx128(Icon, { name: "chevrons-up-down", size: 12 })
10283
- ]
10284
- }
10285
- ) : void 0;
10286
- const activePopover = plusOpen ? "plus" : modelOpen ? "model" : null;
10287
- return /* @__PURE__ */ jsxs88(
10288
- "div",
10289
- {
10290
- "data-slot": "composer",
10291
- className: cn("relative flex w-full flex-col gap-2", className),
10292
- style,
10293
- onDragOver: (event) => {
10294
- if (onAttachFiles) {
10295
- event.preventDefault();
10296
- setDragOver(true);
10297
- }
10298
- },
10299
- onDragLeave: () => setDragOver(false),
10300
- onDrop: handleDrop,
10301
- onPaste: (event) => {
10302
- const files = Array.from(event.clipboardData?.files ?? []);
10303
- if (files.length > 0 && onAttachFiles) {
10304
- event.preventDefault();
10305
- emitFiles(files);
10306
- }
10307
- },
10308
- children: [
10309
- attachments.length > 0 ? /* @__PURE__ */ jsx128(Attachment, { items: attachments, onRemove: onRemoveAttachment, onRetry: onRetryAttachment }) : null,
10310
- /* @__PURE__ */ jsx128("div", { className: cn(dragOver && "rounded-xl ring-2 ring-ring/50"), children: /* @__PURE__ */ jsx128(
10311
- ChatInput,
10312
- {
10313
- value: text,
10314
- placeholder,
10315
- disabled: disabled || loading,
10316
- rows,
10317
- toolbarStart,
10318
- toolbarEnd,
10319
- footer,
10320
- field: rich ? /* @__PURE__ */ jsx128(
10321
- MentionInput,
10322
- {
10323
- ref: mentionRef,
10324
- value: text,
10325
- placeholder,
10326
- disabled: disabled || loading,
10327
- sources,
10328
- chips,
10329
- onChipsChange: handleChipsChange,
10330
- onChange: handleChange,
10331
- onSubmit: handleSubmit
10332
- }
10333
- ) : void 0,
10334
- onChange: handleChange,
10335
- onSubmit: handleSubmit
10336
- }
10337
- ) }),
10338
- onAttachFiles ? /* @__PURE__ */ jsx128(
10339
- "input",
10340
- {
10341
- ref: fileInputRef,
10342
- type: "file",
10343
- multiple: true,
10344
- hidden: true,
10345
- onChange: (event) => {
10346
- emitFiles(Array.from(event.target.files ?? []));
10347
- event.target.value = "";
10348
- }
10349
- }
10350
- ) : null,
10351
- activePopover !== null ? /* @__PURE__ */ jsxs88(Fragment17, { children: [
10352
- /* @__PURE__ */ jsx128("div", { className: "fixed inset-0 z-40", onClick: closePopovers }),
10353
- activePopover === "model" ? /* @__PURE__ */ jsx128("div", { "data-slot": "composer-model-popover", className: cn(popoverPanelClass, "right-0 left-auto w-48"), role: "listbox", children: models.map((option) => /* @__PURE__ */ jsxs88(
10354
- "button",
10355
- {
10356
- type: "button",
10357
- role: "option",
10358
- "aria-selected": option.value === currentModel?.value,
10359
- className: popoverItemClass,
10360
- onClick: () => {
10361
- onModelChange?.(option.value);
10362
- closePopovers();
10363
- },
10364
- children: [
10365
- /* @__PURE__ */ jsx128("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
10366
- option.value === currentModel?.value ? /* @__PURE__ */ jsx128(Icon, { name: "check", size: 14 }) : null
10367
- ]
10368
- },
10369
- option.value
10370
- )) }) : /* @__PURE__ */ jsx128("div", { "data-slot": "composer-context-popover", className: popoverPanelClass, role: "listbox", children: plusSources.map(
10371
- (source) => source.items.length === 0 ? null : /* @__PURE__ */ jsxs88("div", { children: [
10372
- /* @__PURE__ */ jsx128("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: source.label }),
10373
- source.items.map((item) => /* @__PURE__ */ jsxs88(
10374
- "button",
10375
- {
10376
- type: "button",
10377
- className: popoverItemClass,
10378
- onClick: () => selectContext(item, source),
10379
- children: [
10380
- /* @__PURE__ */ jsx128(Icon, { name: item.icon ?? source.icon ?? "file-text", size: "sm" }),
10381
- /* @__PURE__ */ jsx128("span", { className: "min-w-0 flex-1 truncate", children: item.label })
10382
- ]
10383
- },
10384
- item.id
10385
- ))
10386
- ] }, source.trigger)
10387
- ) })
10388
- ] }) : null
10389
- ]
10390
- }
10391
- );
10392
- }
10393
-
10394
- // src/components/chat/ConversationList/index.tsx
10395
- import { useMemo as useMemo7, useState as useState45 } from "react";
10396
- import { jsx as jsx129, jsxs as jsxs89 } from "react/jsx-runtime";
10397
- function ConversationList({ items = [], activeId, searchable, onSelect, onDelete, className, style }) {
10398
- const [innerActive, setInnerActive] = useState45();
10399
- const [keyword, setKeyword] = useState45("");
10400
- const active = activeId ?? innerActive;
10401
- const filtered = useMemo7(() => {
10402
- const query = keyword.trim().toLowerCase();
10403
- if (!query) return items;
10404
- return items.filter(
10405
- (item) => item.title.toLowerCase().includes(query) || (item.description ?? "").toLowerCase().includes(query)
10406
- );
10407
- }, [items, keyword]);
10408
- const select = (id) => {
10409
- if (activeId === void 0) {
10410
- setInnerActive(id);
10411
- }
10412
- onSelect?.(id);
10413
- };
10414
- return /* @__PURE__ */ jsxs89("div", { "data-slot": "conversation-list", className: cn("flex w-full flex-col gap-1", className), style, children: [
10415
- searchable ? /* @__PURE__ */ jsxs89("div", { className: "relative mb-1", children: [
10416
- /* @__PURE__ */ jsx129("span", { className: "absolute top-1/2 left-2 -translate-y-1/2 text-muted-foreground", children: /* @__PURE__ */ jsx129(Icon, { name: "search", size: "sm" }) }),
10417
- /* @__PURE__ */ jsx129(
10418
- "input",
10419
- {
10420
- "data-slot": "conversation-list-search",
10421
- value: keyword,
10422
- placeholder: "\u641C\u7D22\u4F1A\u8BDD",
10423
- className: "h-8 w-full rounded-md border border-border bg-background pr-2 pl-7 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
10424
- onChange: (event) => setKeyword(event.target.value)
10425
- }
10426
- )
10427
- ] }) : null,
10428
- filtered.map((item) => /* @__PURE__ */ jsxs89(
10429
- "div",
10430
- {
10431
- "data-slot": "conversation-list-item",
10432
- "data-active": item.id === active || void 0,
10433
- className: cn(
10434
- "group flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent",
10435
- item.id === active && "bg-accent"
10436
- ),
10437
- onClick: () => select(item.id),
10438
- children: [
10439
- /* @__PURE__ */ jsxs89("div", { className: "min-w-0 flex-1", children: [
10440
- /* @__PURE__ */ jsx129("div", { className: cn("truncate text-sm", item.id === active && "font-medium"), children: item.title }),
10441
- item.description ? /* @__PURE__ */ jsx129("div", { className: "truncate text-xs text-muted-foreground", children: item.description }) : null
10442
- ] }),
10443
- item.time ? /* @__PURE__ */ jsx129("span", { className: "shrink-0 text-xs text-muted-foreground", children: item.time }) : null,
10444
- onDelete ? /* @__PURE__ */ jsx129(
10445
- "button",
10446
- {
10447
- type: "button",
10448
- "data-slot": "conversation-list-delete",
10449
- "aria-label": `\u5220\u9664 ${item.title}`,
10450
- className: "shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-destructive",
10451
- onClick: (event) => {
10452
- event.stopPropagation();
10453
- onDelete(item.id);
10454
- },
10455
- children: /* @__PURE__ */ jsx129(Icon, { name: "trash-2", size: "sm" })
10456
- }
10457
- ) : null
10458
- ]
10459
- },
10460
- item.id
10461
- )),
10462
- filtered.length === 0 ? /* @__PURE__ */ jsx129("div", { className: "px-2 py-4 text-center text-sm text-muted-foreground", children: "\u65E0\u5339\u914D\u4F1A\u8BDD" }) : null
10463
- ] });
10464
- }
10465
-
10466
- // src/components/chat/MessageList/index.tsx
10467
- import { useEffect as useEffect38, useRef as useRef13 } from "react";
10468
- import { jsx as jsx130 } from "react/jsx-runtime";
10469
- function MessageList({ messages = [], autoScroll = true, onAction, onOpenArtifacts, className, style }) {
10470
- const listRef = useRef13(null);
10471
- useEffect38(() => {
10472
- if (!autoScroll) return;
10473
- const el = listRef.current;
10474
- if (el) {
10475
- el.scrollTop = el.scrollHeight;
10476
- }
10477
- }, [messages, autoScroll]);
10478
- return /* @__PURE__ */ jsx130(
10479
- "div",
10480
- {
10481
- ref: listRef,
10482
- "data-slot": "message-list",
10483
- className: cn("flex w-full flex-col gap-4 overflow-y-auto", className),
10484
- style,
10485
- children: messages.map((message, index) => /* @__PURE__ */ jsx130(
10486
- ChatMessage,
10487
- {
10488
- ...message,
10489
- onAction: (action) => onAction?.({ id: message.id, action }),
10490
- onOpenArtifacts: onOpenArtifacts ? () => onOpenArtifacts({ id: message.id }) : void 0
10491
- },
10492
- message.id ?? index
10493
- ))
10494
- }
10495
- );
10496
- }
10497
-
10498
- // src/components/chat/Prompt/index.tsx
10499
- import { jsx as jsx131, jsxs as jsxs90 } from "react/jsx-runtime";
10500
- function Prompt({ title, description, icon, value, disabled, onSelect, className, style }) {
10501
- return /* @__PURE__ */ jsxs90(
10502
- "button",
10503
- {
10504
- type: "button",
10505
- "data-slot": "prompt",
10506
- disabled,
10507
- className: cn(
10508
- "flex items-start gap-2.5 rounded-md border border-border p-3 text-left transition-colors hover:border-primary disabled:pointer-events-none disabled:opacity-50",
10509
- className
10510
- ),
10511
- style,
10512
- onClick: () => onSelect?.(value ?? title ?? ""),
10513
- children: [
10514
- icon ? /* @__PURE__ */ jsx131("span", { className: "mt-0.5 shrink-0 text-primary", children: /* @__PURE__ */ jsx131(Icon, { name: icon, size: "sm" }) }) : null,
10515
- /* @__PURE__ */ jsxs90("span", { className: "min-w-0", children: [
10516
- /* @__PURE__ */ jsx131("span", { "data-slot": "prompt-title", className: "block text-sm font-medium", children: title }),
10517
- description ? /* @__PURE__ */ jsx131("span", { "data-slot": "prompt-description", className: "mt-0.5 block text-xs text-muted-foreground", children: description }) : null
10518
- ] })
10519
- ]
10520
- }
10521
- );
10522
- }
10523
-
10524
- // src/components/chat/Welcome/index.tsx
10525
- import { jsx as jsx132, jsxs as jsxs91 } from "react/jsx-runtime";
10526
- function Welcome({
10527
- title = "\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F",
10528
- description,
10529
- icon = "chat",
10530
- prompts = [],
10531
- onSelect,
10532
- className,
10533
- style
10534
- }) {
10535
- return /* @__PURE__ */ jsxs91(
10536
- "div",
10537
- {
10538
- "data-slot": "welcome",
10539
- className: cn("flex w-full flex-col items-center gap-4 py-10 text-center", className),
10540
- style,
10541
- children: [
10542
- /* @__PURE__ */ jsx132("span", { "data-slot": "welcome-icon", className: "text-primary", children: /* @__PURE__ */ jsx132(Icon, { name: icon, size: "lg" }) }),
10543
- /* @__PURE__ */ jsxs91("div", { children: [
10544
- /* @__PURE__ */ jsx132("div", { "data-slot": "welcome-title", className: "text-xl font-semibold", children: title }),
10545
- description ? /* @__PURE__ */ jsx132("div", { "data-slot": "welcome-description", className: "mt-1 text-sm text-muted-foreground", children: description }) : null
10546
- ] }),
10547
- prompts.length > 0 ? /* @__PURE__ */ jsx132("div", { "data-slot": "welcome-prompts", className: "grid w-full max-w-xl grid-cols-1 gap-3 text-left sm:grid-cols-2", children: prompts.map((item, index) => /* @__PURE__ */ jsx132(
10548
- Prompt,
10549
- {
10550
- title: item.title,
10551
- description: item.description,
10552
- icon: item.icon,
10553
- value: item.value,
10554
- onSelect
10555
- },
10556
- `${item.title}-${index}`
10557
- )) }) : null
10558
- ]
10559
- }
10560
- );
10561
- }
10562
-
10563
- // src/components/chat/ChatPage/index.tsx
10564
- import { jsx as jsx133, jsxs as jsxs92 } from "react/jsx-runtime";
10565
- function ChatPage({
10566
- brand,
10567
- menuItems = [],
10568
- activeMenuKey,
10569
- railItems = [],
10570
- railFooterItems = [],
10571
- menuAction,
10572
- onMenuSelect,
10573
- onMenuAction,
10574
- conversationSectionTitle,
10575
- conversationSectionCollapsed,
10576
- defaultConversationSectionCollapsed = false,
10577
- onConversationSectionToggle,
10578
- conversations = [],
10579
- activeConversationId,
10580
- onSelectConversation,
10581
- onDeleteConversation,
10582
- moreLabel,
10583
- onMore,
10584
- footerSectionTitle,
10585
- footerItems = [],
10586
- footerSectionCollapsed,
10587
- defaultFooterSectionCollapsed = false,
10588
- onFooterSectionToggle,
10589
- conversationTitle,
10590
- conversationHeaderAction,
10591
- onConversationHeaderAction,
10592
- conversationRailItems = [],
10593
- hideArtifactRailItem = false,
10594
- activeConversationRailKey,
10595
- onConversationRailChange,
10596
- messages = [],
10597
- sending,
10598
- composerPlaceholder,
10599
- welcome,
10600
- onSend,
10601
- onMessageAction,
10602
- models,
10603
- model,
10604
- onModelChange,
10605
- contexts,
10606
- contextItems,
10607
- onContextItemsChange,
10608
- attachments,
10609
- onAttachFiles,
10610
- onRemoveAttachment,
10611
- onRetryAttachment,
10612
- composerFooter,
10613
- composerBottom,
10614
- artifactTitle = "\u4EA7\u7269",
10615
- artifacts = [],
10616
- activeArtifactId,
10617
- onSelectArtifact,
10618
- artifactEmptyText = "\u6682\u65E0\u4EA7\u7269\uFF0C\u53D1\u9001\u6D88\u606F\u540E\u751F\u6210\u7684\u6587\u4EF6\u5C06\u5C55\u793A\u5728\u8FD9\u91CC",
10619
- safeRegistry,
10620
- user,
10621
- artifactViewers,
10622
- onDownloadArtifact,
10623
- artifactOpen,
10624
- defaultArtifactOpen = false,
10625
- onArtifactOpenChange,
10626
- content,
10627
- className,
10628
- style
10629
- }) {
10630
- const controlled = artifactOpen !== void 0;
10631
- const [innerOpen, setInnerOpen] = useState46(defaultArtifactOpen);
10632
- const open = controlled ? artifactOpen === true : innerOpen;
10633
- const setOpen = (next) => {
10634
- if (!controlled) setInnerOpen(next);
10635
- onArtifactOpenChange?.(next);
10636
- };
10637
- const railControlled = activeConversationRailKey !== void 0;
10638
- const [activeRailInner, setActiveRailInner] = useState46(null);
10639
- const activeRailKey = railControlled ? activeConversationRailKey : activeRailInner;
10640
- const setActiveRailKey = (next) => {
10641
- if (!railControlled) setActiveRailInner(next);
10642
- onConversationRailChange?.(next);
10643
- };
10644
- const visibleRailItems = conversationRailItems.filter((item) => item.hidden !== true);
10645
- const activeRailItem = visibleRailItems.find(
10646
- (item) => item.key === activeRailKey
10647
- );
10648
- const activePanelItem = activeRailItem?.display === "panel" && activeRailItem.content !== void 0 ? activeRailItem : void 0;
10649
- const activePopoverItem = activeRailItem !== void 0 && activeRailItem.display !== "panel" && activeRailItem.content !== void 0 ? activeRailItem : void 0;
10650
- const toggleRailItem = (item) => {
10651
- const next = activeRailKey === item.key ? null : item.key;
10652
- if (next !== null && item.display === "panel") {
10653
- setOpen(false);
10654
- }
10655
- setActiveRailKey(next);
10656
- };
10657
- const toggleArtifact = () => {
10658
- if (!open && activePanelItem) {
10659
- setActiveRailKey(null);
10660
- }
10661
- setOpen(!open);
10662
- };
10663
- useEffect39(() => {
10664
- if (!activePopoverItem) return;
10665
- const onKeyDown = (event) => {
10666
- if (event.key === "Escape") setActiveRailKey(null);
10667
- };
10668
- const onPointerDown = (event) => {
10669
- const target = event.target;
10670
- if (target.closest("[data-slot=chat-page-rail-popover]") || target.closest("[data-slot=chat-page-conv-rail]")) {
10671
- return;
10672
- }
10673
- setActiveRailKey(null);
10674
- };
10675
- document.addEventListener("keydown", onKeyDown);
10676
- document.addEventListener("mousedown", onPointerDown);
10677
- return () => {
10678
- document.removeEventListener("keydown", onKeyDown);
10679
- document.removeEventListener("mousedown", onPointerDown);
10680
- };
10681
- });
10682
- const { renderChildren } = useRenderer21();
10683
- const renderSlot = (slot) => isValidElement3(slot) || typeof slot !== "object" || slot === null ? slot : renderChildren([slot]);
10684
- const convCollControlled = conversationSectionCollapsed !== void 0;
10685
- const [convCollapsedInner, setConvCollapsedInner] = useState46(
10686
- defaultConversationSectionCollapsed
10687
- );
10688
- const convCollapsed = convCollControlled ? conversationSectionCollapsed === true : convCollapsedInner;
10689
- const toggleConvSection = () => {
10690
- const next = !convCollapsed;
10691
- if (!convCollControlled) setConvCollapsedInner(next);
10692
- onConversationSectionToggle?.(next);
10693
- };
10694
- const footerCollControlled = footerSectionCollapsed !== void 0;
10695
- const [footerCollapsedInner, setFooterCollapsedInner] = useState46(
10696
- defaultFooterSectionCollapsed
10697
- );
10698
- const footerCollapsed = footerCollControlled ? footerSectionCollapsed === true : footerCollapsedInner;
10699
- const toggleFooterSection = () => {
10700
- const next = !footerCollapsed;
10701
- if (!footerCollControlled) setFooterCollapsedInner(next);
10702
- onFooterSectionToggle?.(next);
10703
- };
10704
- const railVisible = railItems.length > 0 || railFooterItems.length > 0;
10705
- const railButton = (item) => /* @__PURE__ */ jsx133(Tooltip2, { content: item.label, side: "right", children: /* @__PURE__ */ jsx133(
10706
- Button2,
10707
- {
10708
- icon: item.icon,
10709
- variant: "text",
10710
- onClick: () => onMenuSelect?.(item.key)
10711
- }
10712
- ) }, item.key);
10713
- const convRailButton = (item, active, onClick) => {
10714
- const button = /* @__PURE__ */ jsx133(
10715
- Button2,
10716
- {
10717
- icon: item.icon,
10718
- variant: "text",
10719
- className: cn(active ? "bg-accent text-accent-foreground" : void 0, "px-2"),
10720
- onClick
10721
- }
10722
- );
10723
- return item.label !== void 0 ? /* @__PURE__ */ jsx133(Tooltip2, { content: item.label, side: "left", children: button }, item.key) : /* @__PURE__ */ jsx133("span", { children: button }, item.key);
10724
- };
10725
- const convRailVisible = !hideArtifactRailItem || visibleRailItems.length > 0;
10726
- const convRail = convRailVisible ? /* @__PURE__ */ jsxs92(
10727
- "div",
10728
- {
10729
- "data-slot": "chat-page-conv-rail",
10730
- className: "relative flex w-(--pactor-chat-rail-width) shrink-0 flex-col items-center gap-1 px-1 py-2",
10731
- children: [
10732
- !hideArtifactRailItem ? convRailButton(
10733
- {
10734
- key: "artifact",
10735
- icon: open ? "panel-right-close" : "package",
10736
- label: open ? "\u6536\u8D77\u4EA7\u7269\u533A" : artifactTitle
10737
- },
10738
- open,
10739
- toggleArtifact
10740
- ) : null,
10741
- visibleRailItems.map(
10742
- (item) => convRailButton(item, activeRailKey === item.key, () => toggleRailItem(item))
10743
- ),
10744
- activePopoverItem ? /* @__PURE__ */ jsx133(
10745
- "div",
10746
- {
10747
- "data-slot": "chat-page-rail-popover",
10748
- className: "absolute right-full top-0 z-10 mr-2 overflow-auto rounded-lg border border-border bg-popover shadow-md",
10749
- style: {
10750
- width: "var(--pactor-chat-rail-popover-width)",
10751
- maxWidth: "calc(100cqw - 2rem)",
10752
- maxHeight: "calc(100% - 1rem)"
10753
- },
10754
- children: renderSlot(activePopoverItem.content)
10755
- }
10756
- ) : null
10757
- ]
10758
- }
10759
- ) : null;
10760
- const chatArea = /* @__PURE__ */ jsxs92("div", { "data-slot": "chat-page-conversation", className: "flex h-full min-w-0 flex-1 flex-col", children: [
10761
- /* @__PURE__ */ jsxs92(
10762
- "div",
10763
- {
10764
- "data-slot": "chat-page-conversation-header",
10765
- className: "flex shrink-0 items-center justify-between border-b border-border px-4 py-2.5",
10766
- children: [
10767
- /* @__PURE__ */ jsx133(Text, { text: conversationTitle, strong: true }),
10768
- conversationHeaderAction ? /* @__PURE__ */ jsx133(Tooltip2, { content: conversationHeaderAction.tooltip ?? conversationHeaderAction.label, side: "bottom", children: /* @__PURE__ */ jsx133(
10769
- Button2,
10770
- {
10771
- text: conversationHeaderAction.label,
10772
- icon: conversationHeaderAction.icon,
10773
- variant: "text",
10774
- onClick: () => onConversationHeaderAction?.()
10775
- }
10776
- ) }) : null
10777
- ]
10778
- }
10779
- ),
10780
- /* @__PURE__ */ jsxs92("div", { className: "flex min-h-0 flex-1", children: [
10781
- /* @__PURE__ */ jsxs92(
10782
- "div",
10783
- {
10784
- "data-slot": "chat-page-conversation-body",
10785
- className: "mx-auto flex min-h-0 w-[61.8cqw] max-w-full flex-col",
10786
- children: [
10787
- messages.length === 0 ? /* @__PURE__ */ jsx133(
10788
- Welcome,
10789
- {
10790
- title: welcome?.title,
10791
- description: welcome?.description,
10792
- prompts: welcome?.prompts,
10793
- style: { flex: 1 },
10794
- onSelect: (value) => onSend?.(value)
10795
- }
10796
- ) : /* @__PURE__ */ jsx133(
10797
- MessageList,
10798
- {
10799
- messages,
10800
- className: "scrollbar-subtle",
10801
- style: { flex: 1, minHeight: 0, padding: 16 },
10802
- onAction: onMessageAction,
10803
- onOpenArtifacts: () => setOpen(true)
10804
- }
10805
- ),
10806
- /* @__PURE__ */ jsx133(
10807
- Composer,
10808
- {
10809
- placeholder: composerPlaceholder,
10810
- loading: sending,
10811
- models,
10812
- model,
10813
- contexts,
10814
- contextItems,
10815
- attachments,
10816
- footer: composerFooter,
10817
- style: { padding: 16, paddingBottom: composerBottom ? 8 : 16, flexShrink: 0 },
10818
- onModelChange,
10819
- onContextItemsChange,
10820
- onAttachFiles,
10821
- onRemoveAttachment,
10822
- onRetryAttachment,
10823
- onSubmit: (text) => onSend?.(text)
10824
- }
10825
- ),
10826
- composerBottom ? /* @__PURE__ */ jsx133("div", { "data-slot": "chat-page-composer-bottom", className: "shrink-0 px-4 pb-2 text-center", children: renderSlot(composerBottom) }) : null
10827
- ]
10828
- }
10829
- ),
10830
- convRail
10831
- ] })
10832
- ] });
10833
- const activeArtifact = artifacts.find((item) => item.id === activeArtifactId) ?? (activeArtifactId === void 0 || activeArtifactId === "" ? artifacts[artifacts.length - 1] : void 0);
10834
- const artifactPanel = /* @__PURE__ */ jsx133(
10835
- "div",
10836
- {
10837
- "data-slot": "chat-page-artifact",
10838
- className: "flex h-full min-w-0 flex-col border-l border-border bg-muted",
10839
- children: /* @__PURE__ */ jsx133(
10840
- ArtifactPanel,
10841
- {
10842
- artifact: activeArtifact,
10843
- artifacts,
10844
- activeArtifactId,
10845
- title: artifactTitle,
10846
- emptyText: artifactEmptyText,
10847
- safeRegistry,
10848
- user,
10849
- artifactViewers,
10850
- onSelectArtifact: (id) => onSelectArtifact?.(id),
10851
- onDownload: onDownloadArtifact,
10852
- onClose: () => setOpen(false)
10853
- }
10854
- )
10855
- }
10856
- );
10857
- return /* @__PURE__ */ jsxs92(
10858
- "div",
10859
- {
10860
- "data-slot": "chat-page",
10861
- className: cn("flex w-full overflow-hidden", className),
10862
- style: { height: "100vh", ...style },
10863
- children: [
10864
- railVisible ? /* @__PURE__ */ jsxs92(
10865
- "div",
10866
- {
10867
- "data-slot": "chat-page-rail",
10868
- className: "flex h-full w-[52px] shrink-0 flex-col items-center gap-1 border-r border-border py-2.5",
10869
- children: [
10870
- railItems.map(railButton),
10871
- /* @__PURE__ */ jsx133("div", { className: "flex-1" }),
10872
- railFooterItems.map(railButton)
10873
- ]
10874
- }
10875
- ) : null,
10876
- /* @__PURE__ */ jsxs92(
10877
- "div",
10878
- {
10879
- "data-slot": "chat-page-menu",
10880
- className: "flex h-full w-[250px] shrink-0 flex-col gap-2 overflow-hidden border-r border-border px-2 py-3",
10881
- children: [
10882
- brand ? /* @__PURE__ */ jsxs92("div", { className: "flex shrink-0 items-center justify-between px-2", children: [
10883
- /* @__PURE__ */ jsxs92("div", { className: "flex flex-col gap-0.5", children: [
10884
- /* @__PURE__ */ jsx133(Text, { text: brand.title, strong: true, size: "large" }),
10885
- brand.version !== void 0 ? /* @__PURE__ */ jsx133(Text, { text: brand.version, type: "secondary", size: "small" }) : null
10886
- ] }),
10887
- menuAction ? /* @__PURE__ */ jsx133(
10888
- Button2,
10889
- {
10890
- text: menuAction.label,
10891
- icon: menuAction.icon,
10892
- size: "small",
10893
- onClick: () => onMenuAction?.()
10894
- }
10895
- ) : null
10896
- ] }) : null,
10897
- /* @__PURE__ */ jsx133("div", { className: "scrollbar-subtle max-h-[40%] min-h-0 shrink-0 overflow-y-auto", children: /* @__PURE__ */ jsx133(
10898
- Menu,
10899
- {
10900
- items: menuItems,
10901
- selectedKey: activeMenuKey,
10902
- onSelect: (key) => onMenuSelect?.(key)
10903
- }
10904
- ) }),
10905
- conversationSectionTitle !== void 0 ? /* @__PURE__ */ jsxs92(
10906
- "button",
10907
- {
10908
- type: "button",
10909
- "data-slot": "chat-page-section-toggle",
10910
- "aria-expanded": !convCollapsed,
10911
- className: "flex w-full shrink-0 cursor-pointer items-center justify-between px-2 pt-2",
10912
- onClick: toggleConvSection,
10913
- children: [
10914
- /* @__PURE__ */ jsx133(
10915
- Text,
10916
- {
10917
- text: `${conversationSectionTitle} (${conversations.length})`,
10918
- type: "secondary",
10919
- className: "text-sm",
10920
- strong: true
10921
- }
10922
- ),
10923
- /* @__PURE__ */ jsx133(Icon, { name: convCollapsed ? "chevron-right" : "chevron-down", size: 14 })
10924
- ]
10925
- }
10926
- ) : null,
10927
- !convCollapsed ? /* @__PURE__ */ jsxs92("div", { "data-slot": "chat-page-section-body", className: "scrollbar-subtle flex min-h-0 flex-1 flex-col overflow-y-auto", children: [
10928
- /* @__PURE__ */ jsx133(
10929
- ConversationList,
10930
- {
10931
- items: conversations,
10932
- activeId: activeConversationId,
10933
- onSelect: (id) => onSelectConversation?.(id),
10934
- onDelete: (id) => onDeleteConversation?.(id)
10935
- }
10936
- ),
10937
- moreLabel !== void 0 ? /* @__PURE__ */ jsx133("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx133(Button2, { text: `${moreLabel} >>`, variant: "link", size: "small", onClick: () => onMore?.() }) }) : null
10938
- ] }) : null,
10939
- footerSectionTitle !== void 0 ? /* @__PURE__ */ jsxs92(
10940
- "button",
10941
- {
10942
- type: "button",
10943
- "data-slot": "chat-page-section-toggle",
10944
- "aria-expanded": !footerCollapsed,
10945
- className: "flex w-full shrink-0 cursor-pointer items-center justify-between px-2 pt-2",
10946
- onClick: toggleFooterSection,
10947
- children: [
10948
- /* @__PURE__ */ jsx133(Text, { text: footerSectionTitle, type: "secondary", className: "text-sm", strong: true }),
10949
- /* @__PURE__ */ jsx133(Icon, { name: footerCollapsed ? "chevron-right" : "chevron-down", size: 14 })
10950
- ]
10951
- }
10952
- ) : null,
10953
- !footerCollapsed && footerItems.length > 0 ? /* @__PURE__ */ jsx133("div", { "data-slot": "chat-page-section-body", className: "scrollbar-subtle min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx133(
10954
- Menu,
10955
- {
10956
- items: footerItems,
10957
- onSelect: (key) => onMenuSelect?.(key)
10958
- }
10959
- ) }) : null
10960
- ]
10961
- }
10962
- ),
10963
- /* @__PURE__ */ jsx133("div", { className: "@container h-full min-w-0 flex-1", children: content !== void 0 ? renderSlot(content) : open || activePanelItem ? /* @__PURE__ */ jsxs92(PanelGroup, { orientation: "horizontal", style: { width: "100%", height: "100%" }, children: [
10964
- /* @__PURE__ */ jsx133(Panel, { defaultSize: "62%", children: chatArea }),
10965
- /* @__PURE__ */ jsx133(ResizableHandle, { withHandle: true }),
10966
- /* @__PURE__ */ jsx133(Panel, { defaultSize: "38%", minSize: "380px", maxSize: "760px", children: activePanelItem ? /* @__PURE__ */ jsx133(
10967
- "div",
10968
- {
10969
- "data-slot": "chat-page-rail-panel",
10970
- className: "flex h-full min-w-0 flex-col border-l border-border bg-muted",
10971
- children: renderSlot(activePanelItem.content)
10972
- }
10973
- ) : artifactPanel })
10974
- ] }) : chatArea })
10975
- ]
10976
- }
10977
- );
10978
- }
10979
-
10980
- // src/components/chat/Citation/index.tsx
10981
- import { jsx as jsx134, jsxs as jsxs93 } from "react/jsx-runtime";
10982
- function Citation({ items = [], start = 1, className, style }) {
10983
- return /* @__PURE__ */ jsx134("span", { "data-slot": "citation", className: cn("inline-flex gap-1 align-super", className), style, children: items.map((item, index) => /* @__PURE__ */ jsxs93(HoverCard, { children: [
10984
- /* @__PURE__ */ jsxs93(
10985
- HoverCardTrigger,
10986
- {
10987
- "data-slot": "citation-marker",
10988
- className: "cursor-pointer rounded-sm border border-border px-1 text-xs leading-4 text-primary hover:bg-accent",
10989
- children: [
10990
- "[",
10991
- start + index,
10992
- "]"
10993
- ]
10994
- }
10995
- ),
10996
- /* @__PURE__ */ jsxs93(HoverCardContent, { "data-slot": "citation-card", className: "w-72", children: [
10997
- /* @__PURE__ */ jsx134("div", { className: "text-sm font-medium", children: item.title }),
10998
- item.snippet ? /* @__PURE__ */ jsx134("div", { className: "mt-1 line-clamp-3 text-xs text-muted-foreground", children: item.snippet }) : null,
10999
- item.url ? /* @__PURE__ */ jsx134(
11000
- "a",
11001
- {
11002
- href: item.url,
11003
- target: "_blank",
11004
- rel: "noreferrer",
11005
- className: "mt-1 block truncate text-xs text-primary hover:underline",
11006
- children: item.url
11007
- }
11008
- ) : null
11009
- ] })
11010
- ] }, `${item.title}-${index}`)) });
11011
- }
11012
-
11013
- // src/components/chat/StepsPanel/index.tsx
11014
- import { jsx as jsx135, jsxs as jsxs94 } from "react/jsx-runtime";
11015
- function StepsPanel({
11016
- title = "\u6267\u884C\u6B65\u9AA4",
11017
- steps = [],
11018
- emptyText = "\u5F53\u524D\u4F1A\u8BDD\u6682\u65E0\u6267\u884C\u6B65\u9AA4",
11019
- className,
11020
- style
11021
- }) {
11022
- return /* @__PURE__ */ jsxs94(
11023
- "div",
11024
- {
11025
- "data-slot": "steps-panel",
11026
- className: cn("px-3.5 py-3 text-foreground", className),
11027
- style,
11028
- children: [
11029
- /* @__PURE__ */ jsx135("div", { className: "mb-1 text-[13px] font-semibold", children: title }),
11030
- steps.length === 0 ? /* @__PURE__ */ jsx135("div", { className: "py-4 text-center text-[13px] text-muted-foreground", children: emptyText }) : /* @__PURE__ */ jsx135("ol", { children: steps.map((step, index) => {
11031
- const status = step.status ?? "pending";
11032
- return /* @__PURE__ */ jsxs94(
11033
- "li",
11034
- {
11035
- "data-status": status,
11036
- className: cn(
11037
- "relative flex gap-2 py-1.5",
11038
- // 步骤之间的连接线(对齐 16px 图标中心)
11039
- 'not-last:before:absolute not-last:before:left-[7.5px] not-last:before:top-7 not-last:before:-bottom-1 not-last:before:w-px not-last:before:bg-border not-last:before:content-[""]'
11040
- ),
11041
- children: [
11042
- /* @__PURE__ */ jsx135(
11043
- "span",
11044
- {
11045
- className: cn(
11046
- "flex h-5 w-4 shrink-0 items-center justify-center",
11047
- status !== "pending" && "text-primary"
11048
- ),
11049
- children: status === "done" ? /* @__PURE__ */ jsx135(Icon, { name: "check-circle", size: 16 }) : status === "running" ? /* @__PURE__ */ jsx135(
11050
- Icon,
11051
- {
11052
- name: "loading",
11053
- size: 16,
11054
- className: "animate-spin motion-reduce:animate-none"
11055
- }
11056
- ) : /* @__PURE__ */ jsx135("span", { className: "size-1.5 rounded-full bg-muted-foreground/50" })
11057
- }
11058
- ),
11059
- /* @__PURE__ */ jsxs94("div", { className: "min-w-0 flex-1", children: [
11060
- /* @__PURE__ */ jsx135(
11061
- "div",
11062
- {
11063
- className: cn(
11064
- "text-[13px] leading-5",
11065
- status === "pending" && "text-muted-foreground"
11066
- ),
11067
- children: step.title
11068
- }
11069
- ),
11070
- step.description ? /* @__PURE__ */ jsx135("div", { className: "mt-0.5 text-xs leading-[18px] text-muted-foreground", children: step.description }) : null
11071
- ] }),
11072
- step.duration ? /* @__PURE__ */ jsx135("span", { className: "shrink-0 text-xs leading-5 text-muted-foreground", children: step.duration }) : null
11073
- ]
11074
- },
11075
- `${step.title}-${index}`
11076
- );
11077
- }) })
11078
- ]
11079
- }
11080
- );
11081
- }
11082
-
11083
- // src/components/chat/Suggestion/index.tsx
11084
- import { jsx as jsx136, jsxs as jsxs95 } from "react/jsx-runtime";
11085
- function normalize(item) {
11086
- if (typeof item === "string") return { label: item, value: item };
11087
- return { label: item.label, value: item.value ?? item.label, icon: item.icon };
11088
- }
11089
- function Suggestion({ items = [], justify = "start", onSelect, className, style }) {
11090
- return /* @__PURE__ */ jsx136(
11091
- "div",
11092
- {
11093
- "data-slot": "suggestion",
11094
- className: cn("flex flex-wrap gap-2", justify === "center" && "justify-center", className),
11095
- style,
11096
- children: items.map((item, index) => {
11097
- const { label, value, icon } = normalize(item);
11098
- return /* @__PURE__ */ jsxs95(
11099
- "button",
11100
- {
11101
- type: "button",
11102
- "data-slot": "suggestion-item",
11103
- "data-value": value,
11104
- className: "flex items-center gap-1.5 rounded-full border border-border px-3 py-1 text-sm transition-colors hover:bg-accent",
11105
- onClick: () => onSelect?.(value),
11106
- children: [
11107
- icon ? /* @__PURE__ */ jsx136(Icon, { name: icon, size: "sm" }) : null,
11108
- label
11109
- ]
11110
- },
11111
- `${value}-${index}`
11112
- );
11113
- })
11114
- }
11115
- );
11116
- }
11117
-
11118
8740
  // src/components/register.ts
11119
8741
  var builtinComponents = {
11120
8742
  Page,
@@ -11245,26 +8867,7 @@ var builtinComponents = {
11245
8867
  Markdown: MarkdownDslComponent,
11246
8868
  CodeBlock,
11247
8869
  DiffViewer,
11248
- JSONViewer,
11249
- // AI 与会话类扩展组件(第八批:chat 子目录)
11250
- Bubble: Bubble2,
11251
- Sender,
11252
- Thinking,
11253
- AgentStatus,
11254
- StreamingText,
11255
- Suggestion,
11256
- Prompt,
11257
- Welcome,
11258
- Citation,
11259
- Attachment,
11260
- ToolCall,
11261
- ChatMessage,
11262
- ChatPage,
11263
- ArtifactPanel,
11264
- MessageList,
11265
- Composer,
11266
- ConversationList,
11267
- StepsPanel
8870
+ JSONViewer
11268
8871
  };
11269
8872
  function registerBuiltinComponents(registry) {
11270
8873
  for (const [type, component] of Object.entries(builtinComponents)) {
@@ -11272,7 +8875,7 @@ function registerBuiltinComponents(registry) {
11272
8875
  }
11273
8876
  }
11274
8877
  function createComponentRegistry() {
11275
- const registry = new Registry2("ComponentRegistry");
8878
+ const registry = new Registry("ComponentRegistry");
11276
8879
  registerBuiltinComponents(registry);
11277
8880
  return registry;
11278
8881
  }
@@ -11310,6 +8913,7 @@ export {
11310
8913
  ColorPicker,
11311
8914
  Combobox2 as Combobox,
11312
8915
  Command2 as Command,
8916
+ useOptionalRenderer,
11313
8917
  ChatInput,
11314
8918
  Container,
11315
8919
  Content,
@@ -11402,25 +9006,6 @@ export {
11402
9006
  Upload,
11403
9007
  Video,
11404
9008
  Watermark,
11405
- AgentStatus,
11406
- buildArtifactTree,
11407
- ArtifactPanel,
11408
- Attachment,
11409
- Bubble2 as Bubble,
11410
- Sender,
11411
- StreamingText,
11412
- Thinking,
11413
- ToolCall,
11414
- ChatMessage,
11415
- Composer,
11416
- ConversationList,
11417
- MessageList,
11418
- Prompt,
11419
- Welcome,
11420
- ChatPage,
11421
- Citation,
11422
- StepsPanel,
11423
- Suggestion,
11424
9009
  builtinComponents,
11425
9010
  registerBuiltinComponents,
11426
9011
  createComponentRegistry