@pactor-app/chat 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,14 +39,13 @@ __export(index_exports, {
39
39
  ConversationStore: () => ConversationStore,
40
40
  DEFAULT_CHAT_GREETING: () => DEFAULT_CHAT_GREETING,
41
41
  EmptyState: () => EmptyState,
42
- MENU_ICONS: () => MENU_ICONS,
43
- MESSAGE_ACTION_ICONS: () => MESSAGE_ACTION_ICONS,
44
42
  MessageActions: () => MessageActions,
45
43
  MessageBubble: () => MessageBubble,
46
44
  PartRenderer: () => PartRenderer,
47
45
  applyChatEvent: () => applyChatEvent,
48
46
  buildChatScope: () => buildChatScope,
49
47
  collectMessageText: () => collectMessageText,
48
+ createChatComponentRegistry: () => createChatComponentRegistry,
50
49
  createChatId: () => createChatId,
51
50
  createChatRuntime: () => createChatRuntime,
52
51
  createChatWorkspace: () => createChatWorkspace,
@@ -858,8 +857,8 @@ function useChatAutoScroll({
858
857
  }
859
858
 
860
859
  // src/chat-composition.tsx
861
- var import_runtime4 = require("@pactor-app/runtime");
862
- var import_react15 = require("react");
860
+ var import_runtime5 = require("@pactor-app/runtime");
861
+ var import_react16 = require("react");
863
862
 
864
863
  // src/hooks/use-chat-model.ts
865
864
  var import_react3 = require("react");
@@ -884,121 +883,274 @@ function useChatModel({
884
883
  }
885
884
 
886
885
  // src/view/AppShell.tsx
887
- var import_lucide_react = require("lucide-react");
888
- var import_react4 = require("react");
886
+ var import_components = require("@pactor-app/ui/components");
889
887
  var import_jsx_runtime = require("react/jsx-runtime");
890
- var SidebarDrawerContext = (0, import_react4.createContext)(null);
891
888
  function useSidebarDrawer() {
892
- return (0, import_react4.useContext)(SidebarDrawerContext);
889
+ return (0, import_components.useSidebarDrawer)();
893
890
  }
894
- var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
895
- function useIsMobileViewport() {
896
- const [isMobile, setIsMobile] = (0, import_react4.useState)(
897
- () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
898
- );
899
- (0, import_react4.useEffect)(() => {
900
- if (typeof window.matchMedia !== "function") {
901
- return;
902
- }
903
- const media = window.matchMedia(MOBILE_MEDIA_QUERY);
904
- const onChange = (event) => {
905
- setIsMobile(event.matches);
906
- };
907
- setIsMobile(media.matches);
908
- media.addEventListener("change", onChange);
909
- return () => media.removeEventListener("change", onChange);
910
- }, []);
911
- return isMobile;
912
- }
913
- function AppShell({ sidebar, brand, locationKey, sidebarFooter, collapsible = false, children, className }) {
914
- const [drawerOpen, setDrawerOpen] = (0, import_react4.useState)(false);
915
- const isMobile = useIsMobileViewport();
916
- const close = (0, import_react4.useCallback)(() => setDrawerOpen(false), []);
917
- const [collapsed, setCollapsed] = (0, import_react4.useState)(false);
918
- const collapsedEffective = collapsible && collapsed && !isMobile;
919
- const framed = brand !== void 0 || sidebarFooter !== void 0;
920
- const [trackedLocationKey, setTrackedLocationKey] = (0, import_react4.useState)(locationKey);
921
- if (trackedLocationKey !== locationKey) {
922
- setTrackedLocationKey(locationKey);
923
- setDrawerOpen(false);
924
- }
925
- (0, import_react4.useEffect)(() => {
926
- if (!drawerOpen) {
927
- return;
891
+ function AppShell({
892
+ sidebar,
893
+ brand,
894
+ locationKey,
895
+ sidebarFooter,
896
+ collapsible = false,
897
+ children,
898
+ className
899
+ }) {
900
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
901
+ import_components.Sidebar,
902
+ {
903
+ content: sidebar,
904
+ brand,
905
+ locationKey,
906
+ sidebarFooter,
907
+ collapsible,
908
+ className: `pactor-chat-shell h-full min-h-0${className ? ` ${className}` : ""}`,
909
+ children
928
910
  }
929
- const onKeyDown = (event) => {
930
- if (event.key === "Escape") {
931
- setDrawerOpen(false);
932
- }
933
- };
934
- window.addEventListener("keydown", onKeyDown);
935
- return () => window.removeEventListener("keydown", onKeyDown);
936
- }, [drawerOpen]);
937
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarDrawerContext.Provider, { value: { close, collapsed: collapsedEffective }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `pactor-chat-shell${className ? ` ${className}` : ""}`, children: [
938
- drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__scrim", "aria-hidden": true, onClick: close }) : null,
939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
940
- "div",
941
- {
942
- className: `pactor-chat-shell__sidebar${framed ? " pactor-chat-shell__sidebar--framed" : ""}${collapsedEffective ? " pactor-chat-shell__sidebar--collapsed" : ""}`,
943
- "data-open": drawerOpen ? "true" : "false",
944
- inert: isMobile && !drawerOpen ? true : void 0,
945
- children: framed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
946
- brand !== void 0 && // 桌面品牌行(决策 D3):brand 左、折叠钮右;移动端由
947
- // CSS 隐藏(品牌维持顶栏现状)
948
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pactor-chat-shell__sidebar-header", children: [
949
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__brand", children: brand }),
950
- collapsible && !isMobile && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
951
- "button",
952
- {
953
- type: "button",
954
- className: "pactor-chat-icon-button pactor-chat-shell__collapse",
955
- "aria-label": collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
956
- "aria-expanded": !collapsedEffective,
957
- title: collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
958
- onClick: () => setCollapsed((value) => !value),
959
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.PanelLeft, { size: 16, strokeWidth: 1.7 })
960
- }
961
- )
962
- ] }),
963
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__sidebar-body", children: sidebar }),
964
- sidebarFooter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__sidebar-footer", children: sidebarFooter })
965
- ] }) : sidebar
966
- }
967
- ),
968
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "pactor-chat-shell__main", children: [
969
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pactor-chat-shell__topbar", children: [
970
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
971
- "button",
972
- {
973
- type: "button",
974
- className: "pactor-chat-icon-button pactor-chat-shell__menu",
975
- "aria-label": "\u6253\u5F00\u5BFC\u822A",
976
- "aria-expanded": drawerOpen,
977
- onClick: () => setDrawerOpen(true),
978
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Menu, { size: 18, strokeWidth: 1.7 })
979
- }
980
- ),
981
- brand
982
- ] }),
983
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__content", children })
984
- ] })
985
- ] }) });
911
+ );
986
912
  }
987
913
 
988
914
  // src/view/ArtifactPanel.tsx
989
- var import_lucide_react3 = require("lucide-react");
915
+ var import_components4 = require("@pactor-app/ui/components");
990
916
  var import_core3 = require("@pactor-app/core");
991
917
  var import_markdown2 = require("@pactor-app/markdown");
992
- var import_runtime3 = require("@pactor-app/runtime");
918
+ var import_runtime4 = require("@pactor-app/runtime");
993
919
  var import_react5 = require("react");
994
920
 
995
921
  // src/view/PartRenderer.tsx
996
- var import_lucide_react2 = require("lucide-react");
922
+ var import_components2 = require("@pactor-app/ui/components");
997
923
  var import_markdown = require("@pactor-app/markdown");
924
+ var import_runtime3 = require("@pactor-app/runtime");
925
+
926
+ // src/view/chrome/artifact-card.tsx
927
+ function buildArtifactCardFragment(artifact) {
928
+ return {
929
+ requires: ["Card", "Flex", "Text", "Badge", "Button"],
930
+ node: {
931
+ type: "Card",
932
+ props: { className: "pactor-chat-artifact-card" },
933
+ children: [
934
+ {
935
+ type: "Flex",
936
+ props: { justify: "space-between", align: "center", gap: "small" },
937
+ children: [
938
+ {
939
+ type: "Text",
940
+ props: {
941
+ text: artifact.title,
942
+ strong: true,
943
+ className: "pactor-chat-artifact-card__title"
944
+ }
945
+ },
946
+ {
947
+ type: "Badge",
948
+ props: {
949
+ text: artifact.kind,
950
+ variant: "secondary",
951
+ className: "pactor-chat-artifact-card__kind"
952
+ }
953
+ },
954
+ {
955
+ type: "Button",
956
+ props: {
957
+ text: "\u5728\u9762\u677F\u4E2D\u6253\u5F00",
958
+ variant: "default",
959
+ size: "small",
960
+ className: "pactor-chat-artifact-card__open"
961
+ },
962
+ events: {
963
+ onClick: [
964
+ {
965
+ action: "chat.openArtifact",
966
+ params: { artifact }
967
+ }
968
+ ]
969
+ }
970
+ }
971
+ ]
972
+ }
973
+ ]
974
+ }
975
+ };
976
+ }
977
+
978
+ // src/view/chrome/ChromeSlot.tsx
998
979
  var import_runtime2 = require("@pactor-app/runtime");
980
+ var import_react4 = require("react");
981
+
982
+ // src/view/chrome/actions.ts
983
+ var handlers = {};
984
+ function setChatChromeHandlers(next) {
985
+ handlers = { ...handlers, ...next };
986
+ }
987
+ var messageActionDispatchers = /* @__PURE__ */ new Map();
988
+ function setMessageActionDispatcher(token, dispatch) {
989
+ messageActionDispatchers.set(token, dispatch);
990
+ }
991
+ function clearMessageActionDispatcher(token) {
992
+ messageActionDispatchers.delete(token);
993
+ }
994
+ var CHROME_ACTION_HANDLERS = {
995
+ "chat.sendSuggestion": (action) => {
996
+ const text = action.params?.["text"];
997
+ if (typeof text === "string") {
998
+ handlers.sendSuggestion?.(text);
999
+ }
1000
+ return { ok: true };
1001
+ },
1002
+ "chat.openArtifact": (action) => {
1003
+ const artifact = action.params?.["artifact"];
1004
+ if (artifact) {
1005
+ handlers.openArtifact?.(artifact);
1006
+ }
1007
+ return { ok: true };
1008
+ },
1009
+ "chat.messageAction": (action) => {
1010
+ const token = action.params?.["token"];
1011
+ const actionId = action.params?.["actionId"];
1012
+ if (typeof token === "string" && typeof actionId === "string") {
1013
+ messageActionDispatchers.get(token)?.(actionId);
1014
+ }
1015
+ return { ok: true };
1016
+ },
1017
+ "chat.composerSubmit": (action) => {
1018
+ const value = action.params?.["value"];
1019
+ if (typeof value === "string") {
1020
+ handlers.composerSubmit?.(value);
1021
+ }
1022
+ return { ok: true };
1023
+ },
1024
+ "chat.menuSelect": (action) => {
1025
+ const key = action.params?.["key"];
1026
+ if (typeof key === "string") {
1027
+ handlers.menuSelect?.(key);
1028
+ }
1029
+ return { ok: true };
1030
+ },
1031
+ "chat.modelChange": (action) => {
1032
+ const value = action.params?.["value"];
1033
+ if (typeof value === "string") {
1034
+ handlers.modelChange?.(value);
1035
+ }
1036
+ return { ok: true };
1037
+ },
1038
+ "chat.headerAction": (action) => {
1039
+ const name = action.params?.["action"];
1040
+ if (typeof name === "string") {
1041
+ handlers.headerAction?.(name);
1042
+ }
1043
+ return { ok: true };
1044
+ },
1045
+ "chat.panelClose": () => {
1046
+ handlers.panelClose?.();
1047
+ return { ok: true };
1048
+ }
1049
+ };
1050
+ function ensureChatChromeActions(actions) {
1051
+ for (const [name, handler] of Object.entries(CHROME_ACTION_HANDLERS)) {
1052
+ if (!actions.has(name)) {
1053
+ actions.register(name, handler);
1054
+ }
1055
+ }
1056
+ }
1057
+ function invokeChatPanelClose() {
1058
+ handlers.panelClose?.();
1059
+ }
1060
+
1061
+ // src/view/chrome/ChromeSlot.tsx
999
1062
  var import_jsx_runtime2 = require("react/jsx-runtime");
1063
+ var warnedMissing = /* @__PURE__ */ new Set();
1064
+ function warnMissingOnce(key, detail) {
1065
+ if (!warnedMissing.has(key)) {
1066
+ warnedMissing.add(key);
1067
+ console.warn(`[@pactor-app/chat] ${detail}`);
1068
+ }
1069
+ }
1070
+ function ChromeSlotInner({
1071
+ fragment,
1072
+ fallback,
1073
+ components,
1074
+ wrap
1075
+ }) {
1076
+ let renderer;
1077
+ try {
1078
+ renderer = (0, import_runtime2.useRenderer)();
1079
+ } catch {
1080
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
1081
+ }
1082
+ ensureChatChromeActions(renderer.context.actions);
1083
+ if (components) {
1084
+ for (const [name, component] of Object.entries(components)) {
1085
+ const existing = renderer.context.components.get(name);
1086
+ if (existing !== void 0 && existing !== component) {
1087
+ warnMissingOnce(
1088
+ `conflict:${name}`,
1089
+ `[@pactor-app/chat] chrome \u5185\u90E8\u7EC4\u4EF6\u540D\u300C${name}\u300D\u88AB\u5BBF\u4E3B\u5360\u7528\uFF0C\u5DF2\u56DE\u9000\u5185\u5EFA\u5B9E\u73B0`
1090
+ );
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
1092
+ }
1093
+ if (existing === void 0) {
1094
+ renderer.context.components.register(
1095
+ name,
1096
+ component
1097
+ );
1098
+ }
1099
+ }
1100
+ }
1101
+ const missing = fragment.requires.filter(
1102
+ (type) => !renderer.context.components.has(type)
1103
+ );
1104
+ if (missing.length > 0) {
1105
+ warnMissingOnce(
1106
+ `${fragment.node.type}:${missing.join(",")}`,
1107
+ `[@pactor-app/chat] chrome DSL \u7247\u6BB5\u6240\u9700\u7EC4\u4EF6\u7C7B\u578B\u7F3A\u5931\uFF08${missing.join(" / ")}\uFF09\uFF0C\u5DF2\u56DE\u9000\u5185\u5EFA\u5B9E\u73B0`
1108
+ );
1109
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
1110
+ }
1111
+ const rendered = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-chrome", children: renderer.renderChildren([fragment.node]) });
1112
+ return wrap ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: wrap(rendered) }) : rendered;
1113
+ }
1114
+ var ChromeSlotGuard = class extends import_react4.Component {
1115
+ constructor() {
1116
+ super(...arguments);
1117
+ this.state = { failed: false };
1118
+ }
1119
+ static getDerivedStateFromError() {
1120
+ return { failed: true };
1121
+ }
1122
+ render() {
1123
+ return this.state.failed ? this.props.fallback : this.props.children;
1124
+ }
1125
+ };
1126
+ function ChromeSlot(props) {
1127
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChromeSlotGuard, { fallback: props.fallback, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChromeSlotInner, { ...props }) });
1128
+ }
1129
+
1130
+ // src/view/chrome/feedback.tsx
1131
+ function buildErrorFragment(message) {
1132
+ return {
1133
+ requires: ["Alert"],
1134
+ node: {
1135
+ type: "Alert",
1136
+ props: { variant: "error", message }
1137
+ }
1138
+ };
1139
+ }
1140
+ function buildStatusFragment(text) {
1141
+ return {
1142
+ requires: ["Text"],
1143
+ node: {
1144
+ type: "Text",
1145
+ props: { type: "secondary", text }
1146
+ }
1147
+ };
1148
+ }
1149
+
1150
+ // src/view/PartRenderer.tsx
1151
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1000
1152
  function ChatDenied({ deniedType }) {
1001
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pactor-chat-denied", "data-pactor-chat-denied": deniedType, children: [
1153
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-denied", "data-pactor-chat-denied": deniedType, children: [
1002
1154
  "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728 Chat \u4E2D\u4F7F\u7528",
1003
1155
  deniedType ? `\uFF1A${deniedType}` : ""
1004
1156
  ] });
@@ -1007,64 +1159,90 @@ function ChatDslNode({
1007
1159
  node,
1008
1160
  scope
1009
1161
  }) {
1010
- const renderer = (0, import_runtime2.useRenderer)();
1162
+ const renderer = (0, import_runtime3.useRenderer)();
1011
1163
  const extraScope = scope ? buildChatScope(scope) : void 0;
1012
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderer.renderChildren([node], extraScope) });
1164
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderer.renderChildren([node], extraScope) });
1013
1165
  }
1014
1166
  function SmoothText({ content, streaming }) {
1015
1167
  const shown = useSmoothStreamText(content, streaming);
1016
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-text", children: shown });
1168
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-text", children: shown });
1017
1169
  }
1018
1170
  function SmoothMarkdown({ content, streaming }) {
1019
1171
  const shown = useSmoothStreamText(content, streaming);
1020
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_markdown.MarkdownRenderer, { content: shown });
1172
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_markdown.MarkdownRenderer, { content: shown });
1021
1173
  }
1022
1174
  function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact }) {
1023
1175
  switch (part.type) {
1024
1176
  case "text":
1025
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmoothText, { content: part.content, streaming });
1177
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SmoothText, { content: part.content, streaming });
1026
1178
  case "markdown":
1027
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmoothMarkdown, { content: part.content, streaming });
1179
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SmoothMarkdown, { content: part.content, streaming });
1028
1180
  case "thinking":
1029
1181
  case "tool-call":
1030
1182
  case "tool-result":
1031
1183
  return null;
1032
1184
  case "dsl": {
1033
1185
  if (!part.schema || !runtime) {
1034
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-dsl-pending", children: part.schema ? "DSL \u6E32\u67D3\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "\u5185\u5BB9\u751F\u6210\u4E2D\u2026" });
1186
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1187
+ ChromeSlot,
1188
+ {
1189
+ fragment: buildStatusFragment(
1190
+ part.schema ? "DSL \u6E32\u67D3\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "\u5185\u5BB9\u751F\u6210\u4E2D\u2026"
1191
+ ),
1192
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-dsl-pending", children: part.schema ? "DSL \u6E32\u67D3\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "\u5185\u5BB9\u751F\u6210\u4E2D\u2026" })
1193
+ }
1194
+ );
1035
1195
  }
1036
1196
  const guarded = guardChatDsl(part.schema, runtime.context.components);
1037
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-dsl", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_runtime2.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatDslNode, { node: guarded, scope }) }) });
1197
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-dsl", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_runtime3.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatDslNode, { node: guarded, scope }) }) });
1038
1198
  }
1039
1199
  case "error":
1040
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pactor-chat-error", role: "alert", children: [
1041
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.CircleAlert, { size: 14 }),
1042
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: part.message })
1043
- ] });
1200
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1201
+ ChromeSlot,
1202
+ {
1203
+ fragment: buildErrorFragment(part.message),
1204
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-error", role: "alert", children: [
1205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_components2.Icon, { name: "error", size: 14 }),
1206
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: part.message })
1207
+ ] })
1208
+ }
1209
+ );
1044
1210
  case "status":
1045
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-status", children: part.text });
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1212
+ ChromeSlot,
1213
+ {
1214
+ fragment: buildStatusFragment(part.text),
1215
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-status", children: part.text })
1216
+ }
1217
+ );
1046
1218
  case "artifact":
1047
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1048
- "div",
1219
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1220
+ ChromeSlot,
1049
1221
  {
1050
- className: "pactor-chat-artifact-card",
1051
- "data-artifact-kind": part.artifact.kind,
1052
- children: [
1053
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
1054
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
1055
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1056
- "button",
1057
- {
1058
- type: "button",
1059
- className: "pactor-chat-artifact-card__open",
1060
- onClick: () => onOpenArtifact?.(part.artifact),
1061
- children: [
1062
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.PanelRightOpen, { size: 13, strokeWidth: 1.7 }),
1063
- "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1064
- ]
1065
- }
1066
- )
1067
- ]
1222
+ fragment: buildArtifactCardFragment(part.artifact),
1223
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1224
+ "div",
1225
+ {
1226
+ className: "pactor-chat-artifact-card",
1227
+ "data-artifact-kind": part.artifact.kind,
1228
+ children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1232
+ "button",
1233
+ {
1234
+ type: "button",
1235
+ className: "pactor-chat-artifact-card__open",
1236
+ onClick: () => onOpenArtifact?.(part.artifact),
1237
+ children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_components2.Icon, { name: "panel-right-open", size: 13, strokeWidth: 1.7 }),
1239
+ "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1240
+ ]
1241
+ }
1242
+ )
1243
+ ]
1244
+ }
1245
+ )
1068
1246
  }
1069
1247
  );
1070
1248
  default:
@@ -1072,8 +1250,65 @@ function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact
1072
1250
  }
1073
1251
  }
1074
1252
 
1253
+ // src/view/chrome/panel-header.tsx
1254
+ var import_components3 = require("@pactor-app/ui/components");
1255
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1256
+ function ChatPanelCloseButton() {
1257
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1258
+ "button",
1259
+ {
1260
+ type: "button",
1261
+ className: "pactor-chat-icon-button pactor-chat-inspector__close",
1262
+ title: "\u5173\u95ED\u9762\u677F",
1263
+ "aria-label": "\u5173\u95ED\u9762\u677F",
1264
+ onClick: () => invokeChatPanelClose(),
1265
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_components3.Icon, { name: "x", size: 16 })
1266
+ }
1267
+ );
1268
+ }
1269
+ var PANEL_HEADER_INTERNAL_COMPONENTS = { ChatPanelCloseButton };
1270
+ function buildPanelHeaderFragment(props) {
1271
+ return {
1272
+ requires: ["Flex", "Text", "Badge", "ChatPanelCloseButton"],
1273
+ node: {
1274
+ type: "Flex",
1275
+ props: {
1276
+ justify: "space-between",
1277
+ align: "center",
1278
+ gap: "small",
1279
+ className: "pactor-chat-inspector__header-dsl"
1280
+ },
1281
+ children: [
1282
+ {
1283
+ type: "Flex",
1284
+ props: { align: "center", gap: "small" },
1285
+ children: [
1286
+ {
1287
+ type: "Text",
1288
+ props: {
1289
+ text: props.title,
1290
+ strong: true,
1291
+ className: "pactor-chat-inspector__title"
1292
+ }
1293
+ },
1294
+ {
1295
+ type: "Badge",
1296
+ props: {
1297
+ text: props.kind,
1298
+ variant: "secondary",
1299
+ className: "pactor-chat-inspector__kind"
1300
+ }
1301
+ }
1302
+ ]
1303
+ },
1304
+ { type: "ChatPanelCloseButton" }
1305
+ ]
1306
+ }
1307
+ };
1308
+ }
1309
+
1075
1310
  // src/view/ArtifactPanel.tsx
1076
- var import_jsx_runtime3 = require("react/jsx-runtime");
1311
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1077
1312
  function useArtifactRuntime(safeRegistry, user) {
1078
1313
  return (0, import_react5.useMemo)(() => {
1079
1314
  if (!safeRegistry) {
@@ -1099,12 +1334,12 @@ var ArtifactDslGuard = class extends import_react5.Component {
1099
1334
  console.warn("[@pactor-app/chat] DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
1100
1335
  }
1101
1336
  render() {
1102
- return this.state.failed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-denied", children: "DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A" }) : this.props.children;
1337
+ return this.state.failed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pactor-chat-denied", children: "DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A" }) : this.props.children;
1103
1338
  }
1104
1339
  };
1105
1340
  function ArtifactDslNode({ node }) {
1106
- const { renderChildren } = (0, import_runtime3.useRenderer)();
1107
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderChildren([node]) });
1341
+ const { renderChildren } = (0, import_runtime4.useRenderer)();
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderChildren([node]) });
1108
1343
  }
1109
1344
  function ArtifactDslContent({
1110
1345
  content,
@@ -1112,10 +1347,10 @@ function ArtifactDslContent({
1112
1347
  }) {
1113
1348
  const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
1114
1349
  if (!node || !runtime) {
1115
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-denied", children: node ? "DSL \u9884\u89C8\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "DSL \u4EA7\u51FA\u7269\u5185\u5BB9\u65E0\u6548" });
1350
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pactor-chat-denied", children: node ? "DSL \u9884\u89C8\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "DSL \u4EA7\u51FA\u7269\u5185\u5BB9\u65E0\u6548" });
1116
1351
  }
1117
1352
  const guarded = guardChatDsl(node, runtime.context.components);
1118
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_runtime3.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslNode, { node: guarded }) }) });
1353
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_runtime4.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArtifactDslGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArtifactDslNode, { node: guarded }) }) });
1119
1354
  }
1120
1355
  function ArtifactContent({
1121
1356
  artifact,
@@ -1123,16 +1358,16 @@ function ArtifactContent({
1123
1358
  }) {
1124
1359
  switch (artifact.kind) {
1125
1360
  case "markdown":
1126
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_markdown2.MarkdownRenderer, { content: String(artifact.content) });
1361
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_markdown2.MarkdownRenderer, { content: String(artifact.content) });
1127
1362
  case "code":
1128
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-inspector__code", children: [
1129
- artifact.language ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1130
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: String(artifact.content) }) })
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "pactor-chat-inspector__code", children: [
1364
+ artifact.language ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: String(artifact.content) }) })
1131
1366
  ] });
1132
1367
  case "text":
1133
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className: "pactor-chat-inspector__text", children: String(artifact.content) });
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { className: "pactor-chat-inspector__text", children: String(artifact.content) });
1134
1369
  case "dsl":
1135
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslContent, { content: artifact.content, runtime });
1370
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArtifactDslContent, { content: artifact.content, runtime });
1136
1371
  default:
1137
1372
  return null;
1138
1373
  }
@@ -1146,6 +1381,11 @@ function ArtifactPanel({
1146
1381
  className
1147
1382
  }) {
1148
1383
  const runtime = useArtifactRuntime(safeRegistry, user);
1384
+ const closeRef = (0, import_react5.useRef)(onClose);
1385
+ closeRef.current = onClose;
1386
+ (0, import_react5.useEffect)(() => {
1387
+ setChatChromeHandlers({ panelClose: () => closeRef.current?.() });
1388
+ }, []);
1149
1389
  const panelRef = (0, import_react5.useRef)(null);
1150
1390
  const [width, setWidth] = (0, import_react5.useState)(null);
1151
1391
  const handleResizeStart = (event) => {
@@ -1173,7 +1413,7 @@ function ArtifactPanel({
1173
1413
  document.addEventListener("pointermove", onMove);
1174
1414
  document.addEventListener("pointerup", onUp);
1175
1415
  };
1176
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1416
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1177
1417
  "aside",
1178
1418
  {
1179
1419
  ref: panelRef,
@@ -1181,7 +1421,7 @@ function ArtifactPanel({
1181
1421
  "data-kind": artifact?.kind,
1182
1422
  style: width !== null ? { width: `${width}px` } : void 0,
1183
1423
  children: [
1184
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1424
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1185
1425
  "div",
1186
1426
  {
1187
1427
  className: "pactor-chat-inspector__resize",
@@ -1191,38 +1431,48 @@ function ArtifactPanel({
1191
1431
  onPointerDown: handleResizeStart
1192
1432
  }
1193
1433
  ),
1194
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-inspector__header", children: [
1195
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1196
- artifact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1197
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1198
- "button",
1199
- {
1200
- type: "button",
1201
- className: "pactor-chat-icon-button pactor-chat-inspector__close",
1202
- "aria-label": "\u5173\u95ED\u9762\u677F",
1203
- onClick: onClose,
1204
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.X, { size: 16, strokeWidth: 1.7 })
1205
- }
1206
- )
1207
- ] }),
1208
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-inspector__body", children: inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactContent, { artifact, runtime }) : null })
1434
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pactor-chat-inspector__header", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1435
+ ChromeSlot,
1436
+ {
1437
+ fragment: buildPanelHeaderFragment({
1438
+ title: artifact?.title ?? "\u4EA7\u51FA\u7269",
1439
+ kind: artifact?.kind ?? "text"
1440
+ }),
1441
+ components: PANEL_HEADER_INTERNAL_COMPONENTS,
1442
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1444
+ artifact ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1445
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1446
+ "button",
1447
+ {
1448
+ type: "button",
1449
+ className: "pactor-chat-icon-button pactor-chat-inspector__close",
1450
+ "aria-label": "\u5173\u95ED\u9762\u677F",
1451
+ onClick: onClose,
1452
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_components4.Icon, { name: "x", size: 16, strokeWidth: 1.7 })
1453
+ }
1454
+ )
1455
+ ] })
1456
+ }
1457
+ ) }),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pactor-chat-inspector__body", children: inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArtifactContent, { artifact, runtime }) : null })
1209
1459
  ]
1210
1460
  }
1211
1461
  );
1212
1462
  }
1213
1463
 
1214
1464
  // src/view/ChatView.tsx
1215
- var import_lucide_react8 = require("lucide-react");
1465
+ var import_components10 = require("@pactor-app/ui/components");
1216
1466
  var import_core4 = require("@pactor-app/core");
1217
- var import_react13 = require("react");
1467
+ var import_react14 = require("react");
1218
1468
 
1219
1469
  // src/view/Composer.tsx
1220
- var import_lucide_react5 = require("lucide-react");
1221
- var import_react9 = require("react");
1470
+ var import_components7 = require("@pactor-app/ui/components");
1471
+ var import_react10 = require("react");
1222
1472
 
1223
1473
  // src/view/AnchoredPopover.tsx
1224
1474
  var import_react6 = require("react");
1225
- var import_jsx_runtime4 = require("react/jsx-runtime");
1475
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1226
1476
  function AnchoredPopover({
1227
1477
  anchorRect,
1228
1478
  onClose,
@@ -1279,7 +1529,7 @@ function AnchoredPopover({
1279
1529
  position?.below ? "pactor-chat-popover--below" : "",
1280
1530
  className ?? ""
1281
1531
  ].filter(Boolean).join(" ");
1282
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1532
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1283
1533
  "div",
1284
1534
  {
1285
1535
  ref,
@@ -1298,9 +1548,34 @@ function AnchoredPopover({
1298
1548
  }
1299
1549
 
1300
1550
  // src/view/ModelChip.tsx
1301
- var import_lucide_react4 = require("lucide-react");
1551
+ var import_components5 = require("@pactor-app/ui/components");
1302
1552
  var import_react7 = require("react");
1303
- var import_jsx_runtime5 = require("react/jsx-runtime");
1553
+
1554
+ // src/view/chrome/model-select.tsx
1555
+ function buildModelSelectFragment(props) {
1556
+ return {
1557
+ requires: ["Select"],
1558
+ node: {
1559
+ type: "Select",
1560
+ props: {
1561
+ options: props.models.map((option) => ({
1562
+ value: option.value,
1563
+ label: option.label
1564
+ })),
1565
+ value: props.model,
1566
+ className: "pactor-chat-model-select"
1567
+ },
1568
+ events: {
1569
+ onChange: [
1570
+ { action: "chat.modelChange", params: { value: "${event}" } }
1571
+ ]
1572
+ }
1573
+ }
1574
+ };
1575
+ }
1576
+
1577
+ // src/view/ModelChip.tsx
1578
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1304
1579
  function ModelChip({
1305
1580
  models,
1306
1581
  model,
@@ -1309,8 +1584,8 @@ function ModelChip({
1309
1584
  const chipRef = (0, import_react7.useRef)(null);
1310
1585
  const [anchor, setAnchor] = (0, import_react7.useState)(null);
1311
1586
  const currentOption = models.find((option) => option.value === model) ?? models[0];
1312
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1313
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1587
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1314
1589
  "button",
1315
1590
  {
1316
1591
  ref: chipRef,
@@ -1320,18 +1595,18 @@ function ModelChip({
1320
1595
  "aria-label": "\u9009\u62E9\u6A21\u578B",
1321
1596
  onClick: () => setAnchor(chipRef.current.getBoundingClientRect()),
1322
1597
  children: [
1323
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1324
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.ChevronsUpDown, { size: 12 })
1598
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1599
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components5.Icon, { name: "chevrons-up-down", size: 12 })
1325
1600
  ]
1326
1601
  }
1327
1602
  ),
1328
- anchor && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1603
+ anchor && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1329
1604
  AnchoredPopover,
1330
1605
  {
1331
1606
  anchorRect: anchor,
1332
1607
  onClose: () => setAnchor(null),
1333
1608
  role: "listbox",
1334
- children: models.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1609
+ children: models.map((option) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1335
1610
  "button",
1336
1611
  {
1337
1612
  type: "button",
@@ -1343,8 +1618,8 @@ function ModelChip({
1343
1618
  setAnchor(null);
1344
1619
  },
1345
1620
  children: [
1346
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1347
- option.value === currentOption.value && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.Check, { size: 13, className: "pactor-chat-popover__item-check" })
1621
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1622
+ option.value === currentOption.value && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components5.Icon, { name: "check", size: 13, className: "pactor-chat-popover__item-check" })
1348
1623
  ]
1349
1624
  },
1350
1625
  option.value
@@ -1353,10 +1628,243 @@ function ModelChip({
1353
1628
  )
1354
1629
  ] });
1355
1630
  }
1631
+ function ModelSelect({
1632
+ models,
1633
+ model,
1634
+ onModelChange
1635
+ }) {
1636
+ const changeRef = (0, import_react7.useRef)(onModelChange);
1637
+ changeRef.current = onModelChange;
1638
+ (0, import_react7.useEffect)(() => {
1639
+ setChatChromeHandlers({
1640
+ modelChange: (value) => changeRef.current?.(value)
1641
+ });
1642
+ }, []);
1643
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1644
+ ChromeSlot,
1645
+ {
1646
+ fragment: buildModelSelectFragment({ models, model }),
1647
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ModelChip, { models, model, onModelChange })
1648
+ }
1649
+ );
1650
+ }
1356
1651
 
1357
- // src/view/context-sources.tsx
1652
+ // src/view/chrome/composer.tsx
1653
+ var import_components6 = require("@pactor-app/ui/components");
1358
1654
  var import_react8 = require("react");
1359
- var import_jsx_runtime6 = require("react/jsx-runtime");
1655
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1656
+ var ComposerBridgeContext = (0, import_react8.createContext)(
1657
+ null
1658
+ );
1659
+ function useComposerBridge() {
1660
+ const bridge = (0, import_react8.useContext)(ComposerBridgeContext);
1661
+ if (!bridge) {
1662
+ throw new Error(
1663
+ "[@pactor-app/chat] ChatComposer* \u5185\u90E8\u7EC4\u4EF6\u4EC5\u53EF\u5728 Composer chrome \u7247\u6BB5\u5185\u6E32\u67D3"
1664
+ );
1665
+ }
1666
+ return bridge;
1667
+ }
1668
+ function formatSize(bytes) {
1669
+ if (bytes < 1024) {
1670
+ return `${bytes} B`;
1671
+ }
1672
+ if (bytes < 1024 * 1024) {
1673
+ return `${(bytes / 1024).toFixed(1)} KB`;
1674
+ }
1675
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1676
+ }
1677
+ function ComposerAttachments({
1678
+ attachments,
1679
+ onRemove,
1680
+ onClick
1681
+ }) {
1682
+ if (attachments.length === 0) {
1683
+ return null;
1684
+ }
1685
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1686
+ const isImage = attachment.mimeType.startsWith("image/");
1687
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1688
+ "div",
1689
+ {
1690
+ className: "pactor-chat-composer__attachment",
1691
+ "data-attachment-id": attachment.id,
1692
+ onClick: () => onClick?.(attachment),
1693
+ children: [
1694
+ isImage && attachment.url ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "pactor-chat-composer__attachment-thumb", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: attachment.url, alt: attachment.name }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "pactor-chat-composer__attachment-info", children: [
1695
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "file-text", size: 14 }),
1696
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1697
+ attachment.size !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1698
+ ] }),
1699
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1700
+ "button",
1701
+ {
1702
+ type: "button",
1703
+ className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1704
+ title: "\u79FB\u9664\u9644\u4EF6",
1705
+ "aria-label": "\u79FB\u9664\u9644\u4EF6",
1706
+ onClick: (event) => {
1707
+ event.stopPropagation();
1708
+ onRemove(attachment.id);
1709
+ },
1710
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "x", size: 12 })
1711
+ }
1712
+ )
1713
+ ]
1714
+ },
1715
+ attachment.id
1716
+ );
1717
+ }) });
1718
+ }
1719
+ function ComposerContextChips({
1720
+ chips,
1721
+ onRemove
1722
+ }) {
1723
+ if (chips.length === 0) {
1724
+ return null;
1725
+ }
1726
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-composer__contexts", children: chips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1727
+ "span",
1728
+ {
1729
+ className: "pactor-chat-context-chip",
1730
+ "data-context-id": chip.id,
1731
+ children: [
1732
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1733
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1734
+ "button",
1735
+ {
1736
+ type: "button",
1737
+ className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1738
+ title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1739
+ "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1740
+ onClick: () => onRemove(chip),
1741
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "x", size: 12 })
1742
+ }
1743
+ )
1744
+ ]
1745
+ },
1746
+ `${chip.trigger ?? ""}:${chip.id}`
1747
+ )) });
1748
+ }
1749
+ function ChatComposerHeader() {
1750
+ const bridge = useComposerBridge();
1751
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1752
+ bridge.attachments !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1753
+ ComposerAttachments,
1754
+ {
1755
+ attachments: bridge.attachments,
1756
+ onRemove: bridge.removeAttachment,
1757
+ onClick: bridge.attachmentClick
1758
+ }
1759
+ ),
1760
+ bridge.contextChips !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1761
+ ComposerContextChips,
1762
+ {
1763
+ chips: bridge.contextChips,
1764
+ onRemove: bridge.removeChip
1765
+ }
1766
+ )
1767
+ ] });
1768
+ }
1769
+ function ChatComposerToolbarStart() {
1770
+ const bridge = useComposerBridge();
1771
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1772
+ bridge.toolbarStart,
1773
+ bridge.showPlus && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1774
+ "button",
1775
+ {
1776
+ type: "button",
1777
+ className: "pactor-chat-icon-button",
1778
+ title: "\u66F4\u591A\u529F\u80FD",
1779
+ "aria-label": "\u66F4\u591A\u529F\u80FD",
1780
+ onClick: (event) => bridge.plusClick(event.currentTarget.getBoundingClientRect()),
1781
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "plus", size: 14 })
1782
+ }
1783
+ ),
1784
+ bridge.showAttach && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1785
+ "button",
1786
+ {
1787
+ type: "button",
1788
+ className: "pactor-chat-icon-button",
1789
+ title: "\u6DFB\u52A0\u9644\u4EF6",
1790
+ "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1791
+ onClick: bridge.openFilePicker,
1792
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "paperclip", size: 14 })
1793
+ }
1794
+ )
1795
+ ] });
1796
+ }
1797
+ function ChatComposerToolbarEnd() {
1798
+ const bridge = useComposerBridge();
1799
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1800
+ bridge.modelChipEnabled && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1801
+ ModelSelect,
1802
+ {
1803
+ models: bridge.models,
1804
+ model: bridge.model,
1805
+ onModelChange: bridge.modelChange
1806
+ }
1807
+ ),
1808
+ bridge.toolbarEnd,
1809
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1810
+ "button",
1811
+ {
1812
+ type: "button",
1813
+ className: `pactor-chat-composer__button${bridge.streaming ? " pactor-chat-composer__button--stop" : ""}`,
1814
+ title: bridge.streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1815
+ "aria-label": bridge.streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1816
+ disabled: bridge.streaming ? false : bridge.disabled || !bridge.text.trim(),
1817
+ onClick: bridge.streaming ? bridge.stop : bridge.send,
1818
+ children: bridge.streaming ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "square", size: 14, className: "pactor-chat-icon-fill" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_components6.Icon, { name: "arrow-up", size: 16 })
1819
+ }
1820
+ )
1821
+ ] });
1822
+ }
1823
+ function ChatComposerFooter() {
1824
+ const bridge = useComposerBridge();
1825
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: bridge.composerFooter });
1826
+ }
1827
+ var COMPOSER_INTERNAL_COMPONENTS = {
1828
+ ChatComposerHeader,
1829
+ ChatComposerToolbarStart,
1830
+ ChatComposerToolbarEnd,
1831
+ ChatComposerFooter
1832
+ };
1833
+ function buildComposerFragment(props) {
1834
+ const hideToolbar = props.hideToolbar === true;
1835
+ return {
1836
+ requires: [
1837
+ "ChatInput",
1838
+ "ChatComposerHeader",
1839
+ ...hideToolbar ? [] : ["ChatComposerToolbarStart", "ChatComposerToolbarEnd"],
1840
+ "ChatComposerFooter"
1841
+ ],
1842
+ node: {
1843
+ type: "ChatInput",
1844
+ props: {
1845
+ placeholder: props.placeholder,
1846
+ disabled: props.disabled,
1847
+ rows: 1,
1848
+ hideSend: true,
1849
+ header: { type: "ChatComposerHeader" },
1850
+ ...hideToolbar ? {} : {
1851
+ toolbarStart: { type: "ChatComposerToolbarStart" },
1852
+ toolbarEnd: { type: "ChatComposerToolbarEnd" }
1853
+ },
1854
+ ...props.hasFooter === true ? { footer: { type: "ChatComposerFooter" } } : {}
1855
+ },
1856
+ events: {
1857
+ onSubmit: [
1858
+ { action: "chat.composerSubmit", params: { value: "${event}" } }
1859
+ ]
1860
+ }
1861
+ }
1862
+ };
1863
+ }
1864
+
1865
+ // src/view/context-sources.tsx
1866
+ var import_react9 = require("react");
1867
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1360
1868
  function sourceCacheKey(source) {
1361
1869
  return `${source.service ?? ""}:${JSON.stringify(source.params ?? {})}`;
1362
1870
  }
@@ -1391,8 +1899,8 @@ function ContextSourceList({
1391
1899
  cache,
1392
1900
  onSelect
1393
1901
  }) {
1394
- const [resolutions, setResolutions] = (0, import_react8.useState)([]);
1395
- (0, import_react8.useEffect)(() => {
1902
+ const [resolutions, setResolutions] = (0, import_react9.useState)([]);
1903
+ (0, import_react9.useEffect)(() => {
1396
1904
  let cancelled = false;
1397
1905
  const initial = sources.map(
1398
1906
  (source) => source.items ? { status: "ready", items: source.items } : { status: "loading" }
@@ -1437,12 +1945,12 @@ function ContextSourceList({
1437
1945
  totalMatched += items.length;
1438
1946
  return { source, resolution, items };
1439
1947
  });
1440
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "pactor-chat-source-list", children: [
1441
- groups.map(({ source, resolution, items }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "pactor-chat-source-list__group", children: [
1442
- sources.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1443
- resolution.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1444
- resolution.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1445
- items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1948
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-source-list", children: [
1949
+ groups.map(({ source, resolution, items }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-source-list__group", children: [
1950
+ sources.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1951
+ resolution.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1952
+ resolution.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1953
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1446
1954
  "button",
1447
1955
  {
1448
1956
  type: "button",
@@ -1453,22 +1961,13 @@ function ContextSourceList({
1453
1961
  item.id
1454
1962
  ))
1455
1963
  ] }, `${source.trigger}:${source.label}`)),
1456
- totalMatched === 0 && groups.every(({ resolution }) => resolution.status === "ready") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u65E0\u5339\u914D\u9879" })
1964
+ totalMatched === 0 && groups.every(({ resolution }) => resolution.status === "ready") && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u65E0\u5339\u914D\u9879" })
1457
1965
  ] });
1458
1966
  }
1459
1967
 
1460
1968
  // src/view/Composer.tsx
1461
- var import_jsx_runtime7 = require("react/jsx-runtime");
1969
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1462
1970
  var MAX_INPUT_HEIGHT = 200;
1463
- function formatSize(bytes) {
1464
- if (bytes < 1024) {
1465
- return `${bytes} B`;
1466
- }
1467
- if (bytes < 1024 * 1024) {
1468
- return `${(bytes / 1024).toFixed(1)} KB`;
1469
- }
1470
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1471
- }
1472
1971
  function detectMention(text, caret) {
1473
1972
  const beforeCaret = text.slice(0, caret);
1474
1973
  const match = /(?:^|\s)@([^\s@]*)$/.exec(beforeCaret);
@@ -1497,28 +1996,30 @@ function Composer({
1497
1996
  contextSources,
1498
1997
  contextChips,
1499
1998
  onContextChipsChange,
1500
- callService
1999
+ callService,
2000
+ composerFooter,
2001
+ hideToolbar = false
1501
2002
  }) {
1502
- const [text, setText] = (0, import_react9.useState)("");
1503
- const [dragOver, setDragOver] = (0, import_react9.useState)(false);
1504
- const inputRef = (0, import_react9.useRef)(null);
1505
- const fileInputRef = (0, import_react9.useRef)(null);
1506
- const plusButtonRef = (0, import_react9.useRef)(null);
1507
- const composingRef = (0, import_react9.useRef)(false);
2003
+ const [text, setText] = (0, import_react10.useState)("");
2004
+ const [dragOver, setDragOver] = (0, import_react10.useState)(false);
2005
+ const inputRef = (0, import_react10.useRef)(null);
2006
+ const fileInputRef = (0, import_react10.useRef)(null);
2007
+ const plusButtonRef = (0, import_react10.useRef)(null);
2008
+ const composingRef = (0, import_react10.useRef)(false);
1508
2009
  const modelChipEnabled = models !== void 0 && models.length > 0;
1509
- const mentionSources = (0, import_react9.useMemo)(
2010
+ const mentionSources = (0, import_react10.useMemo)(
1510
2011
  () => contextSources?.filter((source) => source.trigger === "@") ?? [],
1511
2012
  [contextSources]
1512
2013
  );
1513
- const plusSources = (0, import_react9.useMemo)(
2014
+ const plusSources = (0, import_react10.useMemo)(
1514
2015
  () => contextSources?.filter((source) => source.trigger === "+") ?? [],
1515
2016
  [contextSources]
1516
2017
  );
1517
2018
  const builtinMentionEnabled = onMentionTrigger === void 0 && mentionSources.length > 0;
1518
2019
  const builtinPlusEnabled = onPlusMenu === void 0 && plusSources.length > 0;
1519
- const [mentionState, setMentionState] = (0, import_react9.useState)(null);
1520
- const [plusAnchor, setPlusAnchor] = (0, import_react9.useState)(null);
1521
- const sourceCacheRef = (0, import_react9.useRef)(null);
2020
+ const [mentionState, setMentionState] = (0, import_react10.useState)(null);
2021
+ const [plusAnchor, setPlusAnchor] = (0, import_react10.useState)(null);
2022
+ const sourceCacheRef = (0, import_react10.useRef)(null);
1522
2023
  if (sourceCacheRef.current === null) {
1523
2024
  sourceCacheRef.current = /* @__PURE__ */ new Map();
1524
2025
  }
@@ -1537,7 +2038,7 @@ function Composer({
1537
2038
  setMentionState(null);
1538
2039
  setPlusAnchor(null);
1539
2040
  };
1540
- (0, import_react9.useLayoutEffect)(() => {
2041
+ (0, import_react10.useLayoutEffect)(() => {
1541
2042
  const input = inputRef.current;
1542
2043
  if (input) {
1543
2044
  input.style.height = "auto";
@@ -1552,105 +2053,147 @@ function Composer({
1552
2053
  onSend?.(value);
1553
2054
  setText("");
1554
2055
  };
1555
- const handleChange = (event) => {
1556
- const value = event.target.value;
2056
+ const sendRef = (0, import_react10.useRef)(send);
2057
+ sendRef.current = send;
2058
+ (0, import_react10.useLayoutEffect)(() => {
2059
+ setChatChromeHandlers({ composerSubmit: () => sendRef.current() });
2060
+ }, []);
2061
+ const syncText = (value, caret, rect) => {
1557
2062
  setText(value);
1558
2063
  if (onMentionTrigger || builtinMentionEnabled) {
1559
- const caret = event.target.selectionStart ?? value.length;
1560
2064
  const query = detectMention(value, caret);
1561
2065
  if (onMentionTrigger) {
1562
2066
  if (query !== null) {
1563
- onMentionTrigger(query, event.target.getBoundingClientRect());
2067
+ onMentionTrigger(query, rect);
1564
2068
  }
1565
2069
  } else {
1566
- setMentionState(
1567
- query !== null ? { query, anchorRect: event.target.getBoundingClientRect() } : null
1568
- );
2070
+ setMentionState(query !== null ? { query, anchorRect: rect } : null);
2071
+ }
2072
+ }
2073
+ };
2074
+ const handleChange = (event) => {
2075
+ syncText(
2076
+ event.target.value,
2077
+ event.target.selectionStart ?? event.target.value.length,
2078
+ event.target.getBoundingClientRect()
2079
+ );
2080
+ };
2081
+ const handleBubbleChange = (event) => {
2082
+ const target = event.target;
2083
+ if (!(target instanceof HTMLTextAreaElement)) {
2084
+ return;
2085
+ }
2086
+ syncText(
2087
+ target.value,
2088
+ target.selectionStart ?? target.value.length,
2089
+ target.getBoundingClientRect()
2090
+ );
2091
+ };
2092
+ const handlePasteFiles = (clipboardData) => {
2093
+ const files = Array.from(clipboardData.files);
2094
+ if (files.length > 0 && onAttachFiles) {
2095
+ onAttachFiles(files);
2096
+ return true;
2097
+ }
2098
+ return false;
2099
+ };
2100
+ const dragHandlers = {
2101
+ onDragOver: (event) => {
2102
+ event.preventDefault();
2103
+ setDragOver(true);
2104
+ },
2105
+ onDragLeave: () => setDragOver(false),
2106
+ onDrop: (event) => {
2107
+ event.preventDefault();
2108
+ setDragOver(false);
2109
+ const files = collectDropFiles(event);
2110
+ if (files.length > 0 && onAttachFiles) {
2111
+ onAttachFiles(files);
1569
2112
  }
1570
2113
  }
1571
2114
  };
1572
- const showToolbar = toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled;
1573
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2115
+ const showToolbar = !hideToolbar && (toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled);
2116
+ const fileInputNode = onAttachFiles ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2117
+ "input",
2118
+ {
2119
+ ref: fileInputRef,
2120
+ type: "file",
2121
+ multiple: true,
2122
+ hidden: true,
2123
+ onChange: (event) => {
2124
+ const files = event.target.files ? Array.from(event.target.files) : [];
2125
+ if (files.length > 0) {
2126
+ onAttachFiles(files);
2127
+ }
2128
+ event.target.value = "";
2129
+ }
2130
+ }
2131
+ ) : null;
2132
+ const popoverNodes = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2133
+ builtinMentionEnabled && mentionState && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2134
+ AnchoredPopover,
2135
+ {
2136
+ anchorRect: mentionState.anchorRect,
2137
+ onClose: () => setMentionState(null),
2138
+ role: "listbox",
2139
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2140
+ ContextSourceList,
2141
+ {
2142
+ sources: mentionSources,
2143
+ query: mentionState.query,
2144
+ callService,
2145
+ cache: sourceCacheRef.current,
2146
+ onSelect: handleSelectContext
2147
+ }
2148
+ )
2149
+ }
2150
+ ),
2151
+ builtinPlusEnabled && plusAnchor && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2152
+ AnchoredPopover,
2153
+ {
2154
+ anchorRect: plusAnchor,
2155
+ onClose: () => setPlusAnchor(null),
2156
+ role: "listbox",
2157
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2158
+ ContextSourceList,
2159
+ {
2160
+ sources: plusSources,
2161
+ query: "",
2162
+ callService,
2163
+ cache: sourceCacheRef.current,
2164
+ onSelect: handleSelectContext
2165
+ }
2166
+ )
2167
+ }
2168
+ )
2169
+ ] });
2170
+ const fallback = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1574
2171
  "div",
1575
2172
  {
1576
2173
  className: `pactor-chat-composer${dragOver ? " pactor-chat-composer--dragover" : ""}`,
1577
- onDragOver: (event) => {
1578
- event.preventDefault();
1579
- setDragOver(true);
1580
- },
1581
- onDragLeave: () => setDragOver(false),
1582
- onDrop: (event) => {
1583
- event.preventDefault();
1584
- setDragOver(false);
1585
- const files = collectDropFiles(event);
1586
- if (files.length > 0 && onAttachFiles) {
1587
- onAttachFiles(files);
1588
- }
1589
- },
2174
+ ...dragHandlers,
1590
2175
  children: [
1591
- attachments !== void 0 && attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1592
- const isImage = attachment.mimeType.startsWith("image/");
1593
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1594
- "div",
1595
- {
1596
- className: "pactor-chat-composer__attachment",
1597
- "data-attachment-id": attachment.id,
1598
- onClick: () => onAttachmentClick?.(attachment),
1599
- children: [
1600
- isImage && attachment.url ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-thumb", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: attachment.url, alt: attachment.name }) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "pactor-chat-composer__attachment-info", children: [
1601
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.FileText, { size: 14 }),
1602
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1603
- attachment.size !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1604
- ] }),
1605
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1606
- "button",
1607
- {
1608
- type: "button",
1609
- className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1610
- title: "\u79FB\u9664\u9644\u4EF6",
1611
- "aria-label": "\u79FB\u9664\u9644\u4EF6",
1612
- onClick: (event) => {
1613
- event.stopPropagation();
1614
- onAttachmentsChange?.(
1615
- attachments.filter((item) => item.id !== attachment.id)
1616
- );
1617
- },
1618
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.X, { size: 12 })
1619
- }
1620
- )
1621
- ]
1622
- },
1623
- attachment.id
1624
- );
1625
- }) }),
1626
- contextChips !== void 0 && contextChips.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pactor-chat-composer__contexts", children: contextChips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1627
- "span",
2176
+ attachments !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2177
+ ComposerAttachments,
1628
2178
  {
1629
- className: "pactor-chat-context-chip",
1630
- "data-context-id": chip.id,
1631
- children: [
1632
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1633
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1634
- "button",
1635
- {
1636
- type: "button",
1637
- className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1638
- title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1639
- "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1640
- onClick: () => onContextChipsChange?.(
1641
- contextChips.filter(
1642
- (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1643
- )
1644
- ),
1645
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.X, { size: 12 })
1646
- }
2179
+ attachments,
2180
+ onRemove: (id) => onAttachmentsChange?.(attachments.filter((a) => a.id !== id)),
2181
+ onClick: onAttachmentClick
2182
+ }
2183
+ ),
2184
+ contextChips !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2185
+ ComposerContextChips,
2186
+ {
2187
+ chips: contextChips,
2188
+ onRemove: (chip) => onContextChipsChange?.(
2189
+ contextChips.filter(
2190
+ (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1647
2191
  )
1648
- ]
1649
- },
1650
- `${chip.trigger ?? ""}:${chip.id}`
1651
- )) }),
1652
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pactor-chat-composer__row", children: [
1653
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2192
+ )
2193
+ }
2194
+ ),
2195
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "pactor-chat-composer__row", children: [
2196
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1654
2197
  "textarea",
1655
2198
  {
1656
2199
  ref: inputRef,
@@ -1667,10 +2210,8 @@ function Composer({
1667
2210
  composingRef.current = false;
1668
2211
  },
1669
2212
  onPaste: (event) => {
1670
- const files = Array.from(event.clipboardData.files);
1671
- if (files.length > 0 && onAttachFiles) {
2213
+ if (handlePasteFiles(event.clipboardData)) {
1672
2214
  event.preventDefault();
1673
- onAttachFiles(files);
1674
2215
  }
1675
2216
  },
1676
2217
  onKeyDown: (event) => {
@@ -1684,7 +2225,7 @@ function Composer({
1684
2225
  }
1685
2226
  }
1686
2227
  ),
1687
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2228
+ hideToolbar ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1688
2229
  "button",
1689
2230
  {
1690
2231
  type: "button",
@@ -1693,14 +2234,14 @@ function Composer({
1693
2234
  "aria-label": streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1694
2235
  disabled: streaming ? false : disabled || !text.trim(),
1695
2236
  onClick: streaming ? onStop : send,
1696
- children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Square, { size: 14, fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.ArrowUp, { size: 16 })
2237
+ children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_components7.Icon, { name: "square", size: 14, className: "pactor-chat-icon-fill" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_components7.Icon, { name: "arrow-up", size: 16 })
1697
2238
  }
1698
2239
  )
1699
2240
  ] }),
1700
- showToolbar && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pactor-chat-composer__toolbar", children: [
2241
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "pactor-chat-composer__toolbar", children: [
1701
2242
  toolbarStart,
1702
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__toolbar-spacer" }),
1703
- (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2243
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "pactor-chat-composer__toolbar-spacer" }),
2244
+ (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1704
2245
  "button",
1705
2246
  {
1706
2247
  ref: plusButtonRef,
@@ -1716,10 +2257,10 @@ function Composer({
1716
2257
  setPlusAnchor(rect);
1717
2258
  }
1718
2259
  },
1719
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Plus, { size: 14 })
2260
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_components7.Icon, { name: "plus", size: 14 })
1720
2261
  }
1721
2262
  ),
1722
- onAttachFiles && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2263
+ onAttachFiles && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1723
2264
  "button",
1724
2265
  {
1725
2266
  type: "button",
@@ -1727,11 +2268,11 @@ function Composer({
1727
2268
  title: "\u6DFB\u52A0\u9644\u4EF6",
1728
2269
  "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1729
2270
  onClick: () => fileInputRef.current?.click(),
1730
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Paperclip, { size: 14 })
2271
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_components7.Icon, { name: "paperclip", size: 14 })
1731
2272
  }
1732
2273
  ),
1733
- modelChipEnabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1734
- ModelChip,
2274
+ modelChipEnabled && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2275
+ ModelSelect,
1735
2276
  {
1736
2277
  models,
1737
2278
  model,
@@ -1740,104 +2281,206 @@ function Composer({
1740
2281
  ),
1741
2282
  toolbarEnd
1742
2283
  ] }),
1743
- onAttachFiles && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1744
- "input",
1745
- {
1746
- ref: fileInputRef,
1747
- type: "file",
1748
- multiple: true,
1749
- hidden: true,
1750
- onChange: (event) => {
1751
- const files = event.target.files ? Array.from(event.target.files) : [];
1752
- if (files.length > 0) {
1753
- onAttachFiles(files);
1754
- }
1755
- event.target.value = "";
1756
- }
1757
- }
1758
- ),
1759
- builtinMentionEnabled && mentionState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1760
- AnchoredPopover,
1761
- {
1762
- anchorRect: mentionState.anchorRect,
1763
- onClose: () => setMentionState(null),
1764
- role: "listbox",
1765
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1766
- ContextSourceList,
1767
- {
1768
- sources: mentionSources,
1769
- query: mentionState.query,
1770
- callService,
1771
- cache: sourceCacheRef.current,
1772
- onSelect: handleSelectContext
1773
- }
1774
- )
1775
- }
1776
- ),
1777
- builtinPlusEnabled && plusAnchor && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1778
- AnchoredPopover,
1779
- {
1780
- anchorRect: plusAnchor,
1781
- onClose: () => setPlusAnchor(null),
1782
- role: "listbox",
1783
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1784
- ContextSourceList,
1785
- {
1786
- sources: plusSources,
1787
- query: "",
1788
- callService,
1789
- cache: sourceCacheRef.current,
1790
- onSelect: handleSelectContext
1791
- }
1792
- )
1793
- }
1794
- )
2284
+ composerFooter !== void 0 && composerFooter !== null ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "pactor-chat-composer__footer", children: composerFooter }) : null,
2285
+ fileInputNode,
2286
+ popoverNodes
1795
2287
  ]
1796
2288
  }
1797
2289
  );
2290
+ const bridge = {
2291
+ text,
2292
+ streaming,
2293
+ disabled,
2294
+ attachments,
2295
+ contextChips,
2296
+ toolbarStart,
2297
+ toolbarEnd,
2298
+ composerFooter,
2299
+ models,
2300
+ model,
2301
+ showPlus: onPlusMenu !== void 0 || builtinPlusEnabled,
2302
+ showAttach: onAttachFiles !== void 0,
2303
+ modelChipEnabled,
2304
+ send,
2305
+ stop: onStop,
2306
+ openFilePicker: () => fileInputRef.current?.click(),
2307
+ plusClick: (rect) => {
2308
+ if (onPlusMenu) {
2309
+ onPlusMenu(rect);
2310
+ } else {
2311
+ setPlusAnchor(rect);
2312
+ }
2313
+ },
2314
+ removeAttachment: (id) => onAttachmentsChange?.((attachments ?? []).filter((a) => a.id !== id)),
2315
+ attachmentClick: onAttachmentClick,
2316
+ removeChip: (chip) => onContextChipsChange?.(
2317
+ (contextChips ?? []).filter(
2318
+ (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
2319
+ )
2320
+ ),
2321
+ modelChange: onModelChange
2322
+ };
2323
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2324
+ ChromeSlot,
2325
+ {
2326
+ fragment: buildComposerFragment({
2327
+ placeholder,
2328
+ disabled,
2329
+ hasFooter: composerFooter !== void 0 && composerFooter !== null,
2330
+ hideToolbar
2331
+ }),
2332
+ components: COMPOSER_INTERNAL_COMPONENTS,
2333
+ fallback,
2334
+ wrap: (children) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ComposerBridgeContext.Provider, { value: bridge, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2335
+ "div",
2336
+ {
2337
+ className: `pactor-chat-composer pactor-chat-composer--dsl${dragOver ? " pactor-chat-composer--dragover" : ""}`,
2338
+ ...dragHandlers,
2339
+ onChange: handleBubbleChange,
2340
+ onPaste: (event) => {
2341
+ if (handlePasteFiles(event.clipboardData)) {
2342
+ event.preventDefault();
2343
+ }
2344
+ },
2345
+ children: [
2346
+ children,
2347
+ fileInputNode,
2348
+ popoverNodes
2349
+ ]
2350
+ }
2351
+ ) })
2352
+ }
2353
+ );
1798
2354
  }
1799
2355
 
1800
- // src/view/EmptyState.tsx
1801
- var import_jsx_runtime8 = require("react/jsx-runtime");
1802
- var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
2356
+ // src/view/chrome/empty-state.tsx
1803
2357
  function suggestionLabel(suggestion) {
1804
2358
  return typeof suggestion === "string" ? suggestion : suggestion.label;
1805
2359
  }
1806
2360
  function suggestionPrompt(suggestion) {
1807
2361
  return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
1808
2362
  }
1809
- function EmptyState({
1810
- greeting = DEFAULT_CHAT_GREETING,
2363
+ function buildEmptyStateFragment(props) {
2364
+ const children = [
2365
+ {
2366
+ type: "Typography",
2367
+ props: {
2368
+ variant: "h3",
2369
+ text: props.greeting,
2370
+ className: "pactor-chat-empty__greeting"
2371
+ }
2372
+ }
2373
+ ];
2374
+ if (props.subtitle) {
2375
+ children.push({
2376
+ type: "Text",
2377
+ props: {
2378
+ type: "secondary",
2379
+ text: props.subtitle,
2380
+ className: "pactor-chat-empty__subtitle"
2381
+ }
2382
+ });
2383
+ }
2384
+ if (props.suggestions && props.suggestions.length > 0) {
2385
+ children.push({
2386
+ type: "Flex",
2387
+ props: {
2388
+ gap: "small",
2389
+ justify: "center",
2390
+ className: "pactor-chat-empty__suggestions"
2391
+ },
2392
+ children: props.suggestions.map((suggestion) => ({
2393
+ type: "Button",
2394
+ props: {
2395
+ text: suggestionLabel(suggestion),
2396
+ variant: "default",
2397
+ size: "middle",
2398
+ className: "pactor-chat-empty__suggestion"
2399
+ },
2400
+ events: {
2401
+ onClick: [
2402
+ {
2403
+ action: "chat.sendSuggestion",
2404
+ params: { text: suggestionPrompt(suggestion) }
2405
+ }
2406
+ ]
2407
+ }
2408
+ }))
2409
+ });
2410
+ }
2411
+ return {
2412
+ requires: ["Flex", "Typography", "Text", "Button"],
2413
+ node: {
2414
+ type: "Flex",
2415
+ props: { direction: "vertical", align: "center", gap: "middle" },
2416
+ children
2417
+ }
2418
+ };
2419
+ }
2420
+
2421
+ // src/view/EmptyState.tsx
2422
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2423
+ var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
2424
+ function suggestionLabel2(suggestion) {
2425
+ return typeof suggestion === "string" ? suggestion : suggestion.label;
2426
+ }
2427
+ function suggestionPrompt2(suggestion) {
2428
+ return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
2429
+ }
2430
+ function EmptyStateFallback({
1811
2431
  subtitle,
1812
2432
  suggestions,
1813
- onSuggestionClick
2433
+ onSuggestionClick,
2434
+ greeting
1814
2435
  }) {
1815
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "pactor-chat-empty", children: [
1816
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__greeting", children: greeting }),
1817
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
1818
- suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__suggestions", children: suggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2436
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2437
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "pactor-chat-empty__greeting", children: greeting }),
2438
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
2439
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "pactor-chat-empty__suggestions", children: suggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1819
2440
  "button",
1820
2441
  {
1821
2442
  type: "button",
1822
2443
  className: "pactor-chat-empty__suggestion",
1823
- onClick: () => onSuggestionClick?.(suggestionPrompt(suggestion)),
1824
- children: suggestionLabel(suggestion)
2444
+ onClick: () => onSuggestionClick?.(suggestionPrompt2(suggestion)),
2445
+ children: suggestionLabel2(suggestion)
1825
2446
  },
1826
- suggestionLabel(suggestion)
2447
+ suggestionLabel2(suggestion)
1827
2448
  )) })
1828
2449
  ] });
1829
2450
  }
2451
+ function EmptyState({
2452
+ greeting = DEFAULT_CHAT_GREETING,
2453
+ subtitle,
2454
+ suggestions,
2455
+ onSuggestionClick
2456
+ }) {
2457
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "pactor-chat-empty", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2458
+ ChromeSlot,
2459
+ {
2460
+ fragment: buildEmptyStateFragment({ greeting, subtitle, suggestions }),
2461
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2462
+ EmptyStateFallback,
2463
+ {
2464
+ greeting,
2465
+ subtitle,
2466
+ suggestions,
2467
+ onSuggestionClick
2468
+ }
2469
+ )
2470
+ }
2471
+ ) });
2472
+ }
1830
2473
 
1831
2474
  // src/view/MessageBubble.tsx
1832
- var import_react12 = require("react");
2475
+ var import_react13 = require("react");
1833
2476
 
1834
2477
  // src/view/ActivityTrace.tsx
1835
- var import_lucide_react6 = require("lucide-react");
2478
+ var import_components8 = require("@pactor-app/ui/components");
1836
2479
  var import_markdown3 = require("@pactor-app/markdown");
1837
- var import_react10 = require("react");
1838
- var import_jsx_runtime9 = require("react/jsx-runtime");
2480
+ var import_react11 = require("react");
2481
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1839
2482
  function usePinnedCollapse(autoOpen) {
1840
- const [state, setState] = (0, import_react10.useState)("auto");
2483
+ const [state, setState] = (0, import_react11.useState)("auto");
1841
2484
  const open = state === "auto" ? autoOpen : state === "pinned-open";
1842
2485
  const toggle = () => {
1843
2486
  setState(open ? "pinned-closed" : "pinned-open");
@@ -1863,8 +2506,8 @@ function statusText(message, mode, toolCount) {
1863
2506
  }
1864
2507
  function useElapsedSeconds(startedAt, active) {
1865
2508
  const compute = () => Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1866
- const [elapsed, setElapsed] = (0, import_react10.useState)(compute);
1867
- (0, import_react10.useEffect)(() => {
2509
+ const [elapsed, setElapsed] = (0, import_react11.useState)(compute);
2510
+ (0, import_react11.useEffect)(() => {
1868
2511
  if (!active) {
1869
2512
  setElapsed(compute());
1870
2513
  return;
@@ -1878,15 +2521,15 @@ function useElapsedSeconds(startedAt, active) {
1878
2521
  }
1879
2522
  function TraceStatusIcon({ status }) {
1880
2523
  if (status === "running") {
1881
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" });
2524
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "loading", size: 13, className: "pactor-chat-spin" });
1882
2525
  }
1883
2526
  if (status === "success") {
1884
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.CheckCircle2, { size: 13 });
2527
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "check-circle", size: 13 });
1885
2528
  }
1886
2529
  if (status === "failed") {
1887
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.XCircle, { size: 13 });
2530
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "x-circle", size: 13 });
1888
2531
  }
1889
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Clock, { size: 13 });
2532
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "clock", size: 13 });
1890
2533
  }
1891
2534
  function summarizeArgs(args) {
1892
2535
  if (args === void 0) {
@@ -1915,16 +2558,16 @@ function formatDetail(value) {
1915
2558
  }
1916
2559
  }
1917
2560
  function TraceRow({ entry }) {
1918
- const [open, setOpen] = (0, import_react10.useState)(false);
2561
+ const [open, setOpen] = (0, import_react11.useState)(false);
1919
2562
  const summary = summarizeArgs(entry.args);
1920
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1921
2564
  "div",
1922
2565
  {
1923
2566
  className: "pactor-chat-trace__entry",
1924
2567
  "data-status": entry.status,
1925
2568
  "data-open": open,
1926
2569
  children: [
1927
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2570
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1928
2571
  "button",
1929
2572
  {
1930
2573
  type: "button",
@@ -1932,16 +2575,16 @@ function TraceRow({ entry }) {
1932
2575
  "aria-expanded": open,
1933
2576
  onClick: () => setOpen((value) => !value),
1934
2577
  children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) }),
1936
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TraceStatusIcon, { status: entry.status }) }),
1937
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
1938
- summary && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-args", children: summary })
2578
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "chevron-right", size: 12 }) }),
2579
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TraceStatusIcon, { status: entry.status }) }),
2580
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
2581
+ summary && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__entry-args", children: summary })
1939
2582
  ]
1940
2583
  }
1941
2584
  ),
1942
- open && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-trace__entry-body", children: [
1943
- entry.args !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
1944
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: entry.status === "failed" ? entry.error ?? "\u6267\u884C\u5931\u8D25" : formatDetail(entry.result) })
2585
+ open && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-trace__entry-body", children: [
2586
+ entry.args !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
2587
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: entry.status === "failed" ? entry.error ?? "\u6267\u884C\u5931\u8D25" : formatDetail(entry.result) })
1945
2588
  ] })
1946
2589
  ]
1947
2590
  }
@@ -1953,14 +2596,14 @@ function ThinkingCard({
1953
2596
  }) {
1954
2597
  const partStreaming = part.status === "streaming";
1955
2598
  const { open, toggle } = usePinnedCollapse(partStreaming && !answerPhase);
1956
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2599
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1957
2600
  "div",
1958
2601
  {
1959
2602
  className: "pactor-chat-thinking",
1960
2603
  "data-open": open,
1961
2604
  "data-streaming": partStreaming,
1962
2605
  children: [
1963
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2606
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1964
2607
  "button",
1965
2608
  {
1966
2609
  type: "button",
@@ -1968,13 +2611,13 @@ function ThinkingCard({
1968
2611
  "aria-expanded": open,
1969
2612
  onClick: toggle,
1970
2613
  children: [
1971
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) }),
1972
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Brain, { size: 13 }) }),
1973
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u601D\u8003\u8FC7\u7A0B" })
2614
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "chevron-right", size: 12 }) }),
2615
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "loading", size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "brain", size: 13 }) }),
2616
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: "\u601D\u8003\u8FC7\u7A0B" })
1974
2617
  ]
1975
2618
  }
1976
2619
  ),
1977
- open && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-thinking__body", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_markdown3.MarkdownRenderer, { content: part.content }) })
2620
+ open && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "pactor-chat-thinking__body", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_markdown3.MarkdownRenderer, { content: part.content }) })
1978
2621
  ]
1979
2622
  }
1980
2623
  );
@@ -1988,7 +2631,7 @@ function ActivityTrace({ message }) {
1988
2631
  );
1989
2632
  const answerPhase = mode === "responding" || !streaming;
1990
2633
  const { open, toggle } = usePinnedCollapse(!answerPhase);
1991
- const mountTimeRef = (0, import_react10.useRef)(0);
2634
+ const mountTimeRef = (0, import_react11.useRef)(0);
1992
2635
  if (mountTimeRef.current === 0) {
1993
2636
  mountTimeRef.current = Date.now();
1994
2637
  }
@@ -1996,23 +2639,23 @@ function ActivityTrace({ message }) {
1996
2639
  message.createdAt ?? mountTimeRef.current,
1997
2640
  streaming
1998
2641
  );
1999
- const header = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2000
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__status-icon", children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.CheckCircle2, { size: 13 }) }),
2001
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2642
+ const header = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__status-icon", children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "loading", size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "check-circle", size: 13 }) }),
2644
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2002
2645
  "span",
2003
2646
  {
2004
2647
  className: `pactor-chat-trace__status-text${streaming ? " pactor-chat-trace__status-text--breathing" : ""}`,
2005
2648
  children: statusText(message, mode, entries.length)
2006
2649
  }
2007
2650
  ),
2008
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "pactor-chat-trace__elapsed", children: [
2651
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "pactor-chat-trace__elapsed", children: [
2009
2652
  elapsed,
2010
2653
  "s"
2011
2654
  ] }),
2012
- entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) })
2655
+ entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components8.Icon, { name: "chevron-right", size: 12 }) })
2013
2656
  ] });
2014
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-trace", "data-open": open, children: [
2015
- entries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2657
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-trace", "data-open": open, children: [
2658
+ entries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2016
2659
  "button",
2017
2660
  {
2018
2661
  type: "button",
@@ -2021,8 +2664,8 @@ function ActivityTrace({ message }) {
2021
2664
  onClick: toggle,
2022
2665
  children: header
2023
2666
  }
2024
- ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-trace__header", children: header }),
2025
- thinkingParts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2667
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "pactor-chat-trace__header", children: header }),
2668
+ thinkingParts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2026
2669
  ThinkingCard,
2027
2670
  {
2028
2671
  part,
@@ -2030,25 +2673,66 @@ function ActivityTrace({ message }) {
2030
2673
  },
2031
2674
  part.id ?? index
2032
2675
  )),
2033
- open && entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TraceRow, { entry }, entry.callIndex)) })
2676
+ open && entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TraceRow, { entry }, entry.callIndex)) })
2034
2677
  ] });
2035
2678
  }
2036
2679
 
2037
2680
  // src/view/MessageActions.tsx
2038
- var import_lucide_react7 = require("lucide-react");
2039
- var import_react11 = require("react");
2040
- var import_jsx_runtime10 = require("react/jsx-runtime");
2041
- var MESSAGE_ACTION_ICONS = {
2042
- copy: import_lucide_react7.Copy,
2043
- check: import_lucide_react7.Check,
2044
- "rotate-ccw": import_lucide_react7.RotateCcw,
2045
- pencil: import_lucide_react7.Pencil,
2046
- quote: import_lucide_react7.Quote,
2047
- "thumbs-up": import_lucide_react7.ThumbsUp,
2048
- "thumbs-down": import_lucide_react7.ThumbsDown,
2049
- share: import_lucide_react7.Share2,
2050
- bookmark: import_lucide_react7.Bookmark
2051
- };
2681
+ var import_components9 = require("@pactor-app/ui/components");
2682
+ var import_react12 = require("react");
2683
+
2684
+ // src/view/chrome/message-actions.tsx
2685
+ function buildMessageActionsFragment(args) {
2686
+ const { actions, copied, token } = args;
2687
+ return {
2688
+ requires: ["Flex", "Tooltip", "Button", "Icon"],
2689
+ node: {
2690
+ type: "Flex",
2691
+ props: { gap: "small" },
2692
+ children: actions.map((action) => {
2693
+ const copiedFeedback = action.builtin === "copy" && copied;
2694
+ const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2695
+ const iconName = copiedFeedback ? "check" : action.icon;
2696
+ return {
2697
+ type: "Tooltip",
2698
+ props: { content: label },
2699
+ children: [
2700
+ {
2701
+ type: "Button",
2702
+ props: {
2703
+ variant: "text",
2704
+ size: "small",
2705
+ className: "pactor-chat-icon-button"
2706
+ },
2707
+ events: {
2708
+ onClick: [
2709
+ {
2710
+ action: "chat.messageAction",
2711
+ params: { token, actionId: action.id }
2712
+ }
2713
+ ]
2714
+ },
2715
+ children: [
2716
+ {
2717
+ type: "Icon",
2718
+ props: {
2719
+ // 契约图标名直通 ui Icon 白名单(spec: chat-icon-convergence;
2720
+ // 未知名由 ui Icon 渲染占位)
2721
+ name: iconName ?? "help-circle",
2722
+ size: 14
2723
+ }
2724
+ }
2725
+ ]
2726
+ }
2727
+ ]
2728
+ };
2729
+ })
2730
+ }
2731
+ };
2732
+ }
2733
+
2734
+ // src/view/MessageActions.tsx
2735
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2052
2736
  var BUILTIN_ACTION_DEFAULTS = {
2053
2737
  copy: {
2054
2738
  label: "\u590D\u5236",
@@ -2121,7 +2805,21 @@ function MessageActions({
2121
2805
  callService,
2122
2806
  onMessageAction
2123
2807
  }) {
2124
- const [copied, setCopied] = (0, import_react11.useState)(false);
2808
+ const [copied, setCopied] = (0, import_react12.useState)(false);
2809
+ const dispatchRef = (0, import_react12.useRef)(
2810
+ () => {
2811
+ }
2812
+ );
2813
+ const visibleActionsRef = (0, import_react12.useRef)([]);
2814
+ (0, import_react12.useEffect)(() => {
2815
+ setMessageActionDispatcher(message.id, (actionId) => {
2816
+ const action = visibleActionsRef.current.find((a) => a.id === actionId);
2817
+ if (action) {
2818
+ dispatchRef.current(action);
2819
+ }
2820
+ });
2821
+ return () => clearMessageActionDispatcher(message.id);
2822
+ }, [message.id]);
2125
2823
  const copy = async () => {
2126
2824
  try {
2127
2825
  await navigator.clipboard.writeText(collectMessageText(message));
@@ -2181,41 +2879,53 @@ function MessageActions({
2181
2879
  }
2182
2880
  onMessageAction?.(action.id, message);
2183
2881
  };
2184
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2882
+ dispatchRef.current = dispatch;
2883
+ visibleActionsRef.current = visibleActions;
2884
+ const buttons = visibleActions.map((action) => {
2885
+ const copiedFeedback = action.builtin === "copy" && copied;
2886
+ const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2887
+ const iconName = copiedFeedback ? "check" : action.icon;
2888
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2889
+ "button",
2890
+ {
2891
+ type: "button",
2892
+ className: "pactor-chat-icon-button",
2893
+ title: label,
2894
+ "aria-label": label,
2895
+ "data-message-action": action.id,
2896
+ onClick: () => dispatch(action),
2897
+ children: iconName !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components9.Icon, { name: iconName, size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components9.Icon, { name: "help-circle", size: 14 })
2898
+ },
2899
+ action.id
2900
+ );
2901
+ });
2902
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2185
2903
  "div",
2186
2904
  {
2187
2905
  className: `pactor-chat-message__actions${forceVisible ? " pactor-chat-message__actions--visible" : ""}`,
2188
- children: visibleActions.map((action) => {
2189
- const copiedFeedback = action.builtin === "copy" && copied;
2190
- const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2191
- const iconName = copiedFeedback ? "check" : action.icon;
2192
- const Icon = iconName !== void 0 ? MESSAGE_ACTION_ICONS[iconName] : void 0;
2193
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2194
- "button",
2195
- {
2196
- type: "button",
2197
- className: "pactor-chat-icon-button",
2198
- title: label,
2199
- "aria-label": label,
2200
- "data-message-action": action.id,
2201
- onClick: () => dispatch(action),
2202
- children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react7.HelpCircle, { size: 14, "data-icon-placeholder": "true" })
2203
- },
2204
- action.id
2205
- );
2206
- })
2906
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2907
+ ChromeSlot,
2908
+ {
2909
+ fragment: buildMessageActionsFragment({
2910
+ actions: visibleActions,
2911
+ copied,
2912
+ token: message.id
2913
+ }),
2914
+ fallback: buttons
2915
+ }
2916
+ )
2207
2917
  }
2208
2918
  );
2209
2919
  }
2210
2920
 
2211
2921
  // src/view/MessageBubble.tsx
2212
- var import_jsx_runtime11 = require("react/jsx-runtime");
2922
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2213
2923
  function MessageEditForm({
2214
2924
  initialText,
2215
2925
  onConfirm,
2216
2926
  onCancel
2217
2927
  }) {
2218
- const [text, setText] = (0, import_react12.useState)(initialText);
2928
+ const [text, setText] = (0, import_react13.useState)(initialText);
2219
2929
  const confirm = () => {
2220
2930
  const value = text.trim();
2221
2931
  if (!value) {
@@ -2223,8 +2933,8 @@ function MessageEditForm({
2223
2933
  }
2224
2934
  onConfirm(value);
2225
2935
  };
2226
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__edit", children: [
2227
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2936
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "pactor-chat-message__edit", children: [
2937
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2228
2938
  "textarea",
2229
2939
  {
2230
2940
  className: "pactor-chat-message__edit-input",
@@ -2242,8 +2952,8 @@ function MessageEditForm({
2242
2952
  }
2243
2953
  }
2244
2954
  ),
2245
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__edit-actions", children: [
2246
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2955
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "pactor-chat-message__edit-actions", children: [
2956
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2247
2957
  "button",
2248
2958
  {
2249
2959
  type: "button",
@@ -2253,7 +2963,7 @@ function MessageEditForm({
2253
2963
  children: "\u53D1\u9001"
2254
2964
  }
2255
2965
  ),
2256
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2966
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2257
2967
  "button",
2258
2968
  {
2259
2969
  type: "button",
@@ -2279,7 +2989,7 @@ function MessageBubble({
2279
2989
  onMessageAction,
2280
2990
  onOpenArtifact
2281
2991
  }) {
2282
- const [editing, setEditing] = (0, import_react12.useState)(false);
2992
+ const [editing, setEditing] = (0, import_react13.useState)(false);
2283
2993
  const bubbleScope = {
2284
2994
  conversation: scope?.conversation ?? {},
2285
2995
  message: { id: message.id },
@@ -2287,21 +2997,21 @@ function MessageBubble({
2287
2997
  };
2288
2998
  const streaming = message.status === "streaming";
2289
2999
  const live = message.status === "pending" || message.status === "streaming";
2290
- const wasLiveRef = (0, import_react12.useRef)(live);
3000
+ const wasLiveRef = (0, import_react13.useRef)(live);
2291
3001
  if (live) {
2292
3002
  wasLiveRef.current = true;
2293
3003
  }
2294
3004
  const showTrace = message.role === "assistant" && (wasLiveRef.current || message.parts.some(
2295
3005
  (part) => part.type === "thinking" || part.type === "tool-call" || part.type === "tool-result"
2296
3006
  ));
2297
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3007
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2298
3008
  "div",
2299
3009
  {
2300
3010
  className: `pactor-chat-message pactor-chat-message--${message.role}`,
2301
3011
  "data-message-id": message.id,
2302
3012
  "data-message-status": message.status,
2303
3013
  children: [
2304
- editing ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3014
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2305
3015
  MessageEditForm,
2306
3016
  {
2307
3017
  initialText: collectMessageText(message),
@@ -2311,9 +3021,9 @@ function MessageBubble({
2311
3021
  },
2312
3022
  onCancel: () => setEditing(false)
2313
3023
  }
2314
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__body", children: [
2315
- showTrace && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ActivityTrace, { message }),
2316
- message.parts.length === 0 && message.status === "pending" && message.role !== "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pactor-chat-message__pending", children: "\u2026" }) : message.parts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3024
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "pactor-chat-message__body", children: [
3025
+ showTrace && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActivityTrace, { message }),
3026
+ message.parts.length === 0 && message.status === "pending" && message.role !== "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "pactor-chat-message__pending", children: "\u2026" }) : message.parts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2317
3027
  PartRenderer,
2318
3028
  {
2319
3029
  part,
@@ -2325,7 +3035,7 @@ function MessageBubble({
2325
3035
  part.id ?? index
2326
3036
  ))
2327
3037
  ] }),
2328
- !editing && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3038
+ !editing && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2329
3039
  MessageActions,
2330
3040
  {
2331
3041
  message,
@@ -2345,7 +3055,7 @@ function MessageBubble({
2345
3055
  }
2346
3056
 
2347
3057
  // src/view/ChatView.tsx
2348
- var import_jsx_runtime12 = require("react/jsx-runtime");
3058
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2349
3059
  function lastContentLength(messages) {
2350
3060
  const last = messages[messages.length - 1];
2351
3061
  if (!last) {
@@ -2379,6 +3089,8 @@ function ChatView({
2379
3089
  onAttachmentClick,
2380
3090
  toolbarStart,
2381
3091
  toolbarEnd,
3092
+ composerFooter,
3093
+ hideToolbar,
2382
3094
  onMentionTrigger,
2383
3095
  onPlusMenu,
2384
3096
  models,
@@ -2389,16 +3101,17 @@ function ChatView({
2389
3101
  onContextsChange,
2390
3102
  callService,
2391
3103
  messageActions,
2392
- onMessageAction
3104
+ onMessageAction,
3105
+ composerBottom
2393
3106
  }) {
2394
- const messages = (0, import_react13.useSyncExternalStore)(
3107
+ const messages = (0, import_react14.useSyncExternalStore)(
2395
3108
  (listener) => store.subscribe(listener),
2396
3109
  () => store.getMessages()
2397
3110
  );
2398
3111
  const isStreaming = messages.some(
2399
3112
  (message) => message.status === "pending" || message.status === "streaming"
2400
3113
  );
2401
- const runtime = (0, import_react13.useMemo)(() => {
3114
+ const runtime = (0, import_react14.useMemo)(() => {
2402
3115
  if (!safeRegistry) {
2403
3116
  return void 0;
2404
3117
  }
@@ -2415,12 +3128,12 @@ function ChatView({
2415
3128
  user
2416
3129
  });
2417
3130
  }, [safeRegistry, capabilities, permission, confirm, user]);
2418
- const scope = (0, import_react13.useMemo)(
3131
+ const scope = (0, import_react14.useMemo)(
2419
3132
  () => ({ conversation: { id: conversationId }, params }),
2420
3133
  [conversationId, params]
2421
3134
  );
2422
- const notifiedArtifactsRef = (0, import_react13.useRef)(/* @__PURE__ */ new Set());
2423
- (0, import_react13.useEffect)(() => {
3135
+ const notifiedArtifactsRef = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
3136
+ (0, import_react14.useEffect)(() => {
2424
3137
  if (!onArtifact) {
2425
3138
  return;
2426
3139
  }
@@ -2439,7 +3152,7 @@ function ChatView({
2439
3152
  });
2440
3153
  }
2441
3154
  }, [messages, onArtifact]);
2442
- const abortRef = (0, import_react13.useRef)(null);
3155
+ const abortRef = (0, import_react14.useRef)(null);
2443
3156
  const { containerRef, isFollowing, scrollToBottom } = useChatAutoScroll({
2444
3157
  messageCount: messages.length,
2445
3158
  lastContentLength: lastContentLength(messages),
@@ -2450,7 +3163,7 @@ function ChatView({
2450
3163
  model,
2451
3164
  onModelChange
2452
3165
  });
2453
- const [contextChips, setContextChips] = (0, import_react13.useState)([]);
3166
+ const [contextChips, setContextChips] = (0, import_react14.useState)([]);
2454
3167
  const handleContextChipsChange = (chips) => {
2455
3168
  setContextChips(chips);
2456
3169
  onContextsChange?.(chips);
@@ -2482,6 +3195,16 @@ function ChatView({
2482
3195
  abortRef.current?.abort();
2483
3196
  abortRef.current = null;
2484
3197
  };
3198
+ const handleSendRef = (0, import_react14.useRef)(handleSend);
3199
+ handleSendRef.current = handleSend;
3200
+ const onOpenArtifactRef = (0, import_react14.useRef)(onOpenArtifact);
3201
+ onOpenArtifactRef.current = onOpenArtifact;
3202
+ (0, import_react14.useEffect)(() => {
3203
+ setChatChromeHandlers({
3204
+ sendSuggestion: (text) => handleSendRef.current(text),
3205
+ openArtifact: (artifact) => onOpenArtifactRef.current?.(artifact)
3206
+ });
3207
+ }, []);
2485
3208
  const handleRegenerate = () => {
2486
3209
  if (!transport) {
2487
3210
  return;
@@ -2499,7 +3222,7 @@ function ChatView({
2499
3222
  const lastMessage = messages[messages.length - 1];
2500
3223
  const regenerableMessageId = lastMessage?.role === "assistant" && lastMessage.status === "completed" ? lastMessage.id : void 0;
2501
3224
  const isEmpty = messages.length === 0;
2502
- const composerNode = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3225
+ const composerNode = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2503
3226
  Composer,
2504
3227
  {
2505
3228
  onSend: handleSend,
@@ -2511,6 +3234,8 @@ function ChatView({
2511
3234
  onAttachmentClick,
2512
3235
  toolbarStart,
2513
3236
  toolbarEnd,
3237
+ composerFooter,
3238
+ hideToolbar,
2514
3239
  onMentionTrigger,
2515
3240
  onPlusMenu,
2516
3241
  models: modelPlacement === "header" ? void 0 : models,
@@ -2522,14 +3247,18 @@ function ChatView({
2522
3247
  callService
2523
3248
  }
2524
3249
  );
2525
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3250
+ const composerDock = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "pactor-chat-view__composer-dock", children: [
3251
+ composerNode,
3252
+ composerBottom !== void 0 && composerBottom !== null ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "pactor-chat-view__composer-bottom", children: composerBottom }) : null
3253
+ ] });
3254
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2526
3255
  "div",
2527
3256
  {
2528
3257
  className: `pactor-chat-view${isEmpty ? " pactor-chat-view--empty" : ""}${className ? ` ${className}` : ""}`,
2529
3258
  children: [
2530
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-messages", ref: containerRef, children: [
2531
- isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-view__empty-stack", children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3259
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "pactor-chat-messages", ref: containerRef, children: [
3260
+ isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "pactor-chat-view__empty-stack", children: [
3261
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2533
3262
  EmptyState,
2534
3263
  {
2535
3264
  greeting,
@@ -2538,8 +3267,8 @@ function ChatView({
2538
3267
  onSuggestionClick: handleSend
2539
3268
  }
2540
3269
  ),
2541
- composerNode
2542
- ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3270
+ composerDock
3271
+ ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2543
3272
  MessageBubble,
2544
3273
  {
2545
3274
  message,
@@ -2556,28 +3285,28 @@ function ChatView({
2556
3285
  },
2557
3286
  message.id
2558
3287
  )),
2559
- !isFollowing && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3288
+ !isFollowing && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2560
3289
  "button",
2561
3290
  {
2562
3291
  type: "button",
2563
3292
  className: "pactor-chat-scroll-bottom",
2564
3293
  onClick: scrollToBottom,
2565
3294
  children: [
2566
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react8.ArrowDown, { size: 13 }),
3295
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_components10.Icon, { name: "arrow-down", size: 13 }),
2567
3296
  "\u56DE\u5230\u5E95\u90E8"
2568
3297
  ]
2569
3298
  }
2570
3299
  )
2571
3300
  ] }),
2572
- !isEmpty && composerNode
3301
+ !isEmpty && composerDock
2573
3302
  ]
2574
3303
  }
2575
3304
  );
2576
3305
  }
2577
3306
 
2578
3307
  // src/view/WorkspaceHeader.tsx
2579
- var import_lucide_react9 = require("lucide-react");
2580
- var import_react14 = require("react");
3308
+ var import_components11 = require("@pactor-app/ui/components");
3309
+ var import_react15 = require("react");
2581
3310
 
2582
3311
  // src/export-markdown.ts
2583
3312
  var ROLE_LABELS = {
@@ -2609,8 +3338,52 @@ function downloadMessagesAsMarkdown(title, messages) {
2609
3338
  URL.revokeObjectURL(url);
2610
3339
  }
2611
3340
 
3341
+ // src/view/chrome/header-actions.tsx
3342
+ function buildHeaderActionsFragment(props) {
3343
+ const meta = {
3344
+ export: { label: "\u5BFC\u51FA Markdown", icon: "download" },
3345
+ theme: {
3346
+ label: props.theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
3347
+ icon: props.theme === "dark" ? "sun" : "moon"
3348
+ },
3349
+ clear: { label: "\u6E05\u7A7A\u4F1A\u8BDD", icon: "trash2" }
3350
+ };
3351
+ return {
3352
+ requires: ["Flex", "Tooltip", "Button", "Icon"],
3353
+ node: {
3354
+ type: "Flex",
3355
+ props: { gap: "small", align: "center" },
3356
+ children: props.actions.map((action) => ({
3357
+ type: "Tooltip",
3358
+ props: { content: meta[action].label },
3359
+ children: [
3360
+ {
3361
+ type: "Button",
3362
+ props: {
3363
+ variant: "text",
3364
+ size: "small",
3365
+ className: "pactor-chat-icon-button"
3366
+ },
3367
+ events: {
3368
+ onClick: [
3369
+ { action: "chat.headerAction", params: { action } }
3370
+ ]
3371
+ },
3372
+ children: [
3373
+ {
3374
+ type: "Icon",
3375
+ props: { name: meta[action].icon, size: 15 }
3376
+ }
3377
+ ]
3378
+ }
3379
+ ]
3380
+ }))
3381
+ }
3382
+ };
3383
+ }
3384
+
2612
3385
  // src/view/WorkspaceHeader.tsx
2613
- var import_jsx_runtime13 = require("react/jsx-runtime");
3386
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2614
3387
  function readCurrentTheme() {
2615
3388
  if (typeof document === "undefined") {
2616
3389
  return "light";
@@ -2630,10 +3403,10 @@ function WorkspaceHeader({
2630
3403
  onTitleChange
2631
3404
  }) {
2632
3405
  const hasActions = headerActions !== void 0 && headerActions.length > 0;
2633
- const [overrideTitle, setOverrideTitle] = (0, import_react14.useState)(null);
2634
- const [editing, setEditing] = (0, import_react14.useState)(false);
2635
- const [draft, setDraft] = (0, import_react14.useState)("");
2636
- const prevTitleRef = (0, import_react14.useRef)(title);
3406
+ const [overrideTitle, setOverrideTitle] = (0, import_react15.useState)(null);
3407
+ const [editing, setEditing] = (0, import_react15.useState)(false);
3408
+ const [draft, setDraft] = (0, import_react15.useState)("");
3409
+ const prevTitleRef = (0, import_react15.useRef)(title);
2637
3410
  if (prevTitleRef.current !== title) {
2638
3411
  prevTitleRef.current = title;
2639
3412
  if (overrideTitle !== null) {
@@ -2641,7 +3414,7 @@ function WorkspaceHeader({
2641
3414
  }
2642
3415
  }
2643
3416
  const displayTitle = overrideTitle ?? title;
2644
- const [theme, setTheme] = (0, import_react14.useState)(readCurrentTheme);
3417
+ const [theme, setTheme] = (0, import_react15.useState)(readCurrentTheme);
2645
3418
  const commitTitle = () => {
2646
3419
  setEditing(false);
2647
3420
  const next = draft.trim();
@@ -2668,8 +3441,24 @@ function WorkspaceHeader({
2668
3441
  store.clear();
2669
3442
  onClear?.();
2670
3443
  };
3444
+ const handlersRef = (0, import_react15.useRef)({ handleExport, handleTheme, handleClear });
3445
+ handlersRef.current = { handleExport, handleTheme, handleClear };
3446
+ (0, import_react15.useEffect)(() => {
3447
+ setChatChromeHandlers({
3448
+ headerAction: (name) => {
3449
+ const handlers2 = handlersRef.current;
3450
+ if (name === "export") {
3451
+ handlers2.handleExport();
3452
+ } else if (name === "theme") {
3453
+ handlers2.handleTheme();
3454
+ } else if (name === "clear") {
3455
+ handlers2.handleClear();
3456
+ }
3457
+ }
3458
+ });
3459
+ }, []);
2671
3460
  const actionButtons = {
2672
- export: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3461
+ export: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2673
3462
  "button",
2674
3463
  {
2675
3464
  type: "button",
@@ -2677,11 +3466,11 @@ function WorkspaceHeader({
2677
3466
  title: "\u5BFC\u51FA Markdown",
2678
3467
  "aria-label": "\u5BFC\u51FA Markdown",
2679
3468
  onClick: handleExport,
2680
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Download, { size: 15 })
3469
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_components11.Icon, { name: "download", size: 15 })
2681
3470
  },
2682
3471
  "export"
2683
3472
  ),
2684
- theme: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3473
+ theme: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2685
3474
  "button",
2686
3475
  {
2687
3476
  type: "button",
@@ -2689,11 +3478,11 @@ function WorkspaceHeader({
2689
3478
  title: theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
2690
3479
  "aria-label": "\u5207\u6362\u660E\u6697\u4E3B\u9898",
2691
3480
  onClick: handleTheme,
2692
- children: theme === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Sun, { size: 15 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Moon, { size: 15 })
3481
+ children: theme === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_components11.Icon, { name: "sun", size: 15 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_components11.Icon, { name: "moon", size: 15 })
2693
3482
  },
2694
3483
  "theme"
2695
3484
  ),
2696
- clear: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3485
+ clear: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2697
3486
  "button",
2698
3487
  {
2699
3488
  type: "button",
@@ -2701,12 +3490,12 @@ function WorkspaceHeader({
2701
3490
  title: "\u6E05\u7A7A\u4F1A\u8BDD",
2702
3491
  "aria-label": "\u6E05\u7A7A\u4F1A\u8BDD",
2703
3492
  onClick: handleClear,
2704
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Trash2, { size: 15 })
3493
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_components11.Icon, { name: "trash", size: 15 })
2705
3494
  },
2706
3495
  "clear"
2707
3496
  )
2708
3497
  };
2709
- const titleNode = editableTitle ? editing ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3498
+ const titleNode = editableTitle ? editing ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2710
3499
  "input",
2711
3500
  {
2712
3501
  className: "pactor-chat-workspace__title-input",
@@ -2723,7 +3512,7 @@ function WorkspaceHeader({
2723
3512
  }
2724
3513
  }
2725
3514
  }
2726
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3515
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2727
3516
  "button",
2728
3517
  {
2729
3518
  type: "button",
@@ -2735,29 +3524,38 @@ function WorkspaceHeader({
2735
3524
  },
2736
3525
  children: displayTitle
2737
3526
  }
2738
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__title", children: title });
2739
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("header", { className: "pactor-chat-workspace__header", children: [
3527
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "pactor-chat-workspace__title", children: title });
3528
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "pactor-chat-workspace__header", children: [
2740
3529
  headerStart !== void 0 ? (
2741
3530
  // header 位模型 chip(决策 D2):与 title 组成左端组,chip 在前
2742
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "pactor-chat-workspace__header-left", children: [
3531
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pactor-chat-workspace__header-left", children: [
2743
3532
  headerStart,
2744
3533
  titleNode
2745
3534
  ] })
2746
3535
  ) : titleNode,
2747
- hasActions ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "pactor-chat-workspace__header-right", children: [
2748
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-actions", children: headerActions.map((action) => actionButtons[action]) }),
2749
- headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2750
- ] }) : headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
3536
+ hasActions ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pactor-chat-workspace__header-right", children: [
3537
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "pactor-chat-workspace__header-actions", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3538
+ ChromeSlot,
3539
+ {
3540
+ fragment: buildHeaderActionsFragment({
3541
+ actions: headerActions,
3542
+ theme
3543
+ }),
3544
+ fallback: headerActions.map((action) => actionButtons[action])
3545
+ }
3546
+ ) }),
3547
+ headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
3548
+ ] }) : headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2751
3549
  ] });
2752
3550
  }
2753
3551
 
2754
3552
  // src/chat-composition.tsx
2755
- var import_jsx_runtime14 = require("react/jsx-runtime");
3553
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2756
3554
  function DslNodeSlotInner({ node }) {
2757
- const { renderChildren } = (0, import_runtime4.useRenderer)();
3555
+ const { renderChildren } = (0, import_runtime5.useRenderer)();
2758
3556
  return renderChildren([node]);
2759
3557
  }
2760
- var DslSlotGuard = class extends import_react15.Component {
3558
+ var DslSlotGuard = class extends import_react16.Component {
2761
3559
  constructor() {
2762
3560
  super(...arguments);
2763
3561
  this.state = { failed: false };
@@ -2775,17 +3573,17 @@ var DslSlotGuard = class extends import_react15.Component {
2775
3573
  }
2776
3574
  };
2777
3575
  function DslNodeSlot({ node }) {
2778
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslSlotGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslNodeSlotInner, { node }) });
3576
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DslSlotGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DslNodeSlotInner, { node }) });
2779
3577
  }
2780
3578
  function isDslNodeValue(value) {
2781
- return typeof value === "object" && value !== null && !Array.isArray(value) && !(0, import_react15.isValidElement)(value) && typeof value.type === "string";
3579
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(0, import_react16.isValidElement)(value) && typeof value.type === "string";
2782
3580
  }
2783
3581
  function resolveSlot(slot) {
2784
3582
  if (slot === void 0 || slot === null) {
2785
3583
  return void 0;
2786
3584
  }
2787
3585
  if (isDslNodeValue(slot)) {
2788
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslNodeSlot, { node: slot });
3586
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DslNodeSlot, { node: slot });
2789
3587
  }
2790
3588
  return slot;
2791
3589
  }
@@ -2815,7 +3613,11 @@ function ChatComposition({
2815
3613
  messageActions,
2816
3614
  onMessageAction,
2817
3615
  sidebar,
3616
+ toolbarStart,
2818
3617
  toolbarEnd,
3618
+ composerFooter,
3619
+ composerBottom,
3620
+ hideToolbar,
2819
3621
  className,
2820
3622
  onSend,
2821
3623
  artifact,
@@ -2825,18 +3627,18 @@ function ChatComposition({
2825
3627
  onArtifact,
2826
3628
  onOpenArtifact
2827
3629
  }) {
2828
- const storeRef = (0, import_react15.useRef)(null);
3630
+ const storeRef = (0, import_react16.useRef)(null);
2829
3631
  if (storeRef.current === null) {
2830
3632
  storeRef.current = services.store ?? services.storeFactory?.() ?? new ConversationStore();
2831
3633
  }
2832
3634
  const store = storeRef.current;
2833
- const artifactStoreRef = (0, import_react15.useRef)(null);
3635
+ const artifactStoreRef = (0, import_react16.useRef)(null);
2834
3636
  if (artifactStoreRef.current === null) {
2835
3637
  artifactStoreRef.current = services.artifactStore ?? new ArtifactStore();
2836
3638
  }
2837
3639
  const artifactStore = artifactStoreRef.current;
2838
3640
  const panelControlled = panelOpen !== void 0 || artifact !== void 0;
2839
- const storeArtifact = (0, import_react15.useSyncExternalStore)(
3641
+ const storeArtifact = (0, import_react16.useSyncExternalStore)(
2840
3642
  (listener) => artifactStore.subscribe(listener),
2841
3643
  () => artifactStore.get()
2842
3644
  );
@@ -2854,7 +3656,7 @@ function ChatComposition({
2854
3656
  artifactStore.open(next);
2855
3657
  }
2856
3658
  };
2857
- const [attachments, setAttachments] = (0, import_react15.useState)([]);
3659
+ const [attachments, setAttachments] = (0, import_react16.useState)([]);
2858
3660
  const handleAttachFiles = services.onAttachFiles ? (files) => {
2859
3661
  void Promise.resolve(services.onAttachFiles(files)).then(
2860
3662
  (created) => {
@@ -2874,24 +3676,38 @@ function ChatComposition({
2874
3676
  model,
2875
3677
  onModelChange
2876
3678
  });
2877
- const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2878
- ModelChip,
3679
+ const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3680
+ ModelSelect,
2879
3681
  {
2880
3682
  models,
2881
3683
  model: currentModel,
2882
3684
  onModelChange: handleModelChange
2883
3685
  }
2884
3686
  ) : void 0;
2885
- const mergedToolbarEnd = services.toolbarEnd !== void 0 || toolbarEnd !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
3687
+ const mergedToolbarEnd = services.toolbarEnd !== void 0 || toolbarEnd !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2886
3688
  services.toolbarEnd,
2887
3689
  toolbarEnd
2888
3690
  ] }) : void 0;
2889
- const workspace = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3691
+ const mergedToolbarStart = services.toolbarStart !== void 0 || toolbarStart !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3692
+ services.toolbarStart,
3693
+ toolbarStart
3694
+ ] }) : void 0;
3695
+ const mergedComposerFooter = services.composerFooter !== void 0 || composerFooter !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3696
+ services.composerFooter,
3697
+ composerFooter
3698
+ ] }) : void 0;
3699
+ const mergedComposerBottom = services.composerBottom !== void 0 || composerBottom !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3700
+ services.composerBottom,
3701
+ composerBottom
3702
+ ] }) : void 0;
3703
+ const hasHeaderContent = header !== void 0 && header !== null ? true : title !== void 0 || headerExtra !== void 0 && headerExtra !== null || headerActions !== void 0 && headerActions.length > 0 || headerModelChip !== void 0;
3704
+ const renderHeader = showHeader !== false && hasHeaderContent;
3705
+ const workspace = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2890
3706
  "div",
2891
3707
  {
2892
3708
  className: `pactor-chat-workspace${className ? ` ${className}` : ""}`,
2893
3709
  children: [
2894
- showHeader ? header ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3710
+ renderHeader ? header ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2895
3711
  WorkspaceHeader,
2896
3712
  {
2897
3713
  title,
@@ -2906,7 +3722,7 @@ function ChatComposition({
2906
3722
  onTitleChange
2907
3723
  }
2908
3724
  ) : null,
2909
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3725
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2910
3726
  ChatView,
2911
3727
  {
2912
3728
  store,
@@ -2926,8 +3742,11 @@ function ChatComposition({
2926
3742
  onAttachmentsChange: setAttachments,
2927
3743
  onAttachFiles: handleAttachFiles,
2928
3744
  onAttachmentClick: services.onAttachmentClick,
2929
- toolbarStart: services.toolbarStart,
3745
+ toolbarStart: mergedToolbarStart,
2930
3746
  toolbarEnd: mergedToolbarEnd,
3747
+ composerFooter: mergedComposerFooter,
3748
+ composerBottom: mergedComposerBottom,
3749
+ hideToolbar,
2931
3750
  onMentionTrigger: services.onMentionTrigger,
2932
3751
  onPlusMenu: services.onPlusMenu,
2933
3752
  onArtifact,
@@ -2946,15 +3765,15 @@ function ChatComposition({
2946
3765
  ]
2947
3766
  }
2948
3767
  );
2949
- const main = sidebar ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AppShell, { sidebar, children: workspace }) : workspace;
3768
+ const main = sidebar ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AppShell, { sidebar, children: workspace }) : workspace;
2950
3769
  const panelFeatureUsed = artifact !== void 0 || panelOpen !== void 0 || inspector !== void 0 || onPanelClose !== void 0 || onArtifact !== void 0 || onOpenArtifact !== void 0 || services.artifactStore !== void 0;
2951
3770
  if (!panelFeatureUsed && !effectivePanelOpen) {
2952
3771
  return main;
2953
3772
  }
2954
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "pactor-chat-inspector-host", children: [
3773
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "pactor-chat-inspector-host", children: [
2955
3774
  main,
2956
- effectivePanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2957
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3775
+ effectivePanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3776
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2958
3777
  "div",
2959
3778
  {
2960
3779
  className: "pactor-chat-inspector__scrim",
@@ -2962,7 +3781,7 @@ function ChatComposition({
2962
3781
  onClick: handlePanelClose
2963
3782
  }
2964
3783
  ),
2965
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3784
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2966
3785
  ArtifactPanel,
2967
3786
  {
2968
3787
  artifact: effectiveArtifact,
@@ -2977,21 +3796,21 @@ function ChatComposition({
2977
3796
  }
2978
3797
 
2979
3798
  // src/chat-services.tsx
2980
- var import_react16 = require("react");
2981
- var import_jsx_runtime15 = require("react/jsx-runtime");
2982
- var ChatServicesContext = (0, import_react16.createContext)(null);
3799
+ var import_react17 = require("react");
3800
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3801
+ var ChatServicesContext = (0, import_react17.createContext)(null);
2983
3802
  function ChatServicesProvider({
2984
3803
  value,
2985
3804
  children
2986
3805
  }) {
2987
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatServicesContext.Provider, { value, children });
3806
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatServicesContext.Provider, { value, children });
2988
3807
  }
2989
3808
  function useChatServices() {
2990
- return (0, import_react16.useContext)(ChatServicesContext);
3809
+ return (0, import_react17.useContext)(ChatServicesContext);
2991
3810
  }
2992
3811
 
2993
3812
  // src/chat-workspace.tsx
2994
- var import_jsx_runtime16 = require("react/jsx-runtime");
3813
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2995
3814
  function createChatWorkspace(services) {
2996
3815
  function ChatWorkspace({
2997
3816
  title,
@@ -3001,7 +3820,11 @@ function createChatWorkspace(services) {
3001
3820
  conversationId,
3002
3821
  params,
3003
3822
  sidebar,
3823
+ toolbarStart,
3004
3824
  toolbarEnd,
3825
+ composerFooter,
3826
+ composerBottom,
3827
+ hideToolbar,
3005
3828
  artifact,
3006
3829
  panelOpen,
3007
3830
  onPanelClose,
@@ -3024,7 +3847,7 @@ function createChatWorkspace(services) {
3024
3847
  onMessageAction,
3025
3848
  className
3026
3849
  }) {
3027
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatServicesProvider, { value: services, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3850
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ChatServicesProvider, { value: services, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3028
3851
  ChatComposition,
3029
3852
  {
3030
3853
  services,
@@ -3035,15 +3858,17 @@ function createChatWorkspace(services) {
3035
3858
  conversationId,
3036
3859
  params,
3037
3860
  className,
3038
- showHeader: title !== void 0 || services.headerExtra !== void 0 || // header 位模型 chip(决策 D2):仅配置 models 也渲染极简头部
3039
- modelPlacement === "header" && models !== void 0 && models.length > 0,
3040
3861
  headerExtra: services.headerExtra,
3041
- sidebar: sidebar ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: sidebar }) : void 0,
3042
- toolbarEnd: toolbarEnd ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: toolbarEnd }) : void 0,
3862
+ sidebar: sidebar ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: sidebar }) : void 0,
3863
+ toolbarStart: toolbarStart ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: toolbarStart }) : void 0,
3864
+ toolbarEnd: toolbarEnd ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: toolbarEnd }) : void 0,
3865
+ composerFooter: composerFooter ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: composerFooter }) : void 0,
3866
+ composerBottom: composerBottom ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: composerBottom }) : void 0,
3867
+ hideToolbar,
3043
3868
  artifact,
3044
3869
  panelOpen,
3045
3870
  onPanelClose,
3046
- inspector: inspector ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: inspector }) : void 0,
3871
+ inspector: inspector ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DslNodeSlot, { node: inspector }) : void 0,
3047
3872
  onArtifact,
3048
3873
  onOpenArtifact,
3049
3874
  headerActions,
@@ -3067,11 +3892,37 @@ function createChatWorkspace(services) {
3067
3892
  }
3068
3893
 
3069
3894
  // src/view/Chat.tsx
3070
- var import_lucide_react10 = require("lucide-react");
3071
- var import_react17 = require("react");
3895
+ var import_components12 = require("@pactor-app/ui/components");
3896
+ var import_react18 = require("react");
3897
+
3898
+ // src/view/chrome/menu.tsx
3899
+ function buildSidebarMenuFragment(props) {
3900
+ return {
3901
+ requires: ["Menu"],
3902
+ node: {
3903
+ type: "Menu",
3904
+ props: {
3905
+ mode: "inline",
3906
+ selectedKey: props.activeMenuKey,
3907
+ items: props.menu.map((item) => ({
3908
+ key: item.key,
3909
+ label: item.label,
3910
+ ...item.icon !== void 0 ? { icon: item.icon } : {},
3911
+ ...item.badge !== void 0 ? { badge: item.badge } : {},
3912
+ ...item.dot === true ? { dot: true } : {},
3913
+ ...item.group !== void 0 ? { group: item.group } : {},
3914
+ ...item.disabled === true ? { disabled: true } : {}
3915
+ }))
3916
+ },
3917
+ events: {
3918
+ onSelect: [{ action: "chat.menuSelect", params: { key: "${event}" } }]
3919
+ }
3920
+ }
3921
+ };
3922
+ }
3072
3923
 
3073
3924
  // src/view/ChatShell.tsx
3074
- var import_jsx_runtime17 = require("react/jsx-runtime");
3925
+ var import_jsx_runtime20 = require("react/jsx-runtime");
3075
3926
  function ChatShell({
3076
3927
  menu,
3077
3928
  activeMenuKey,
@@ -3083,7 +3934,7 @@ function ChatShell({
3083
3934
  className,
3084
3935
  onMenuSelect
3085
3936
  }) {
3086
- const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3937
+ const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3087
3938
  ChatSidebarMenu,
3088
3939
  {
3089
3940
  menu,
@@ -3091,7 +3942,7 @@ function ChatShell({
3091
3942
  onMenuSelect
3092
3943
  }
3093
3944
  ) : null);
3094
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3945
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3095
3946
  AppShell,
3096
3947
  {
3097
3948
  sidebar: sidebarContent,
@@ -3108,7 +3959,7 @@ function ChatShellDslAdapter({
3108
3959
  onMenuSelect,
3109
3960
  ...rest
3110
3961
  }) {
3111
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3962
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3112
3963
  ChatShell,
3113
3964
  {
3114
3965
  ...rest,
@@ -3123,39 +3974,45 @@ function registerChatShellComponent(registry) {
3123
3974
  }
3124
3975
 
3125
3976
  // src/view/Chat.tsx
3126
- var import_jsx_runtime18 = require("react/jsx-runtime");
3127
- var MENU_ICONS = {
3128
- plus: import_lucide_react10.Plus,
3129
- chat: import_lucide_react10.MessageSquare,
3130
- history: import_lucide_react10.History,
3131
- settings: import_lucide_react10.Settings,
3132
- star: import_lucide_react10.Star,
3133
- search: import_lucide_react10.Search,
3134
- user: import_lucide_react10.User,
3135
- folder: import_lucide_react10.Folder
3136
- };
3977
+ var import_jsx_runtime21 = require("react/jsx-runtime");
3137
3978
  function ChatSidebarMenu({
3138
3979
  menu,
3139
3980
  activeMenuKey,
3140
3981
  onMenuSelect
3141
3982
  }) {
3142
3983
  const drawer = useSidebarDrawer();
3143
- let lastGroup;
3144
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
3145
- const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
3146
- lastGroup = item.group;
3147
- const Icon = item.icon !== void 0 ? MENU_ICONS[item.icon] : void 0;
3148
- const active = item.key === activeMenuKey;
3149
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react17.Fragment, { children: [
3150
- groupLabel !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
3151
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3984
+ const menuRef = (0, import_react18.useRef)(menu);
3985
+ menuRef.current = menu;
3986
+ const selectRef = (0, import_react18.useRef)(onMenuSelect);
3987
+ selectRef.current = onMenuSelect;
3988
+ const drawerRef = (0, import_react18.useRef)(drawer);
3989
+ drawerRef.current = drawer;
3990
+ (0, import_react18.useEffect)(() => {
3991
+ setChatChromeHandlers({
3992
+ menuSelect: (key) => {
3993
+ const item = menuRef.current.find((entry) => entry.key === key);
3994
+ if (item === void 0 || item.disabled === true) {
3995
+ return;
3996
+ }
3997
+ selectRef.current?.(key, item);
3998
+ drawerRef.current?.close();
3999
+ }
4000
+ });
4001
+ }, []);
4002
+ const fallback = (() => {
4003
+ let lastGroup;
4004
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
4005
+ const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
4006
+ lastGroup = item.group;
4007
+ const active = item.key === activeMenuKey;
4008
+ const collapsed = drawer?.collapsed === true;
4009
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3152
4010
  "button",
3153
4011
  {
3154
4012
  type: "button",
3155
4013
  className: `pactor-chat-menu__item${active ? " pactor-chat-menu__item--active" : ""}`,
3156
4014
  disabled: item.disabled === true,
3157
4015
  "aria-current": active ? "true" : void 0,
3158
- title: drawer?.collapsed ? item.label : void 0,
3159
4016
  onClick: () => {
3160
4017
  if (item.disabled) {
3161
4018
  return;
@@ -3164,21 +4021,26 @@ function ChatSidebarMenu({
3164
4021
  drawer?.close();
3165
4022
  },
3166
4023
  children: [
3167
- item.icon !== void 0 ? Icon ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Icon, { size: 15, strokeWidth: 1.7 }) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3168
- "span",
3169
- {
3170
- className: "pactor-chat-menu__icon-placeholder",
3171
- "aria-hidden": true
3172
- }
3173
- ) : null,
3174
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__label", children: item.label }),
3175
- item.dot === true ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
3176
- item.badge !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__badge", children: item.badge }) : null
4024
+ item.icon !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_components12.Icon, { name: item.icon, size: 15, strokeWidth: 1.7 }) }) : null,
4025
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "pactor-chat-menu__label", children: item.label }),
4026
+ item.dot === true ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
4027
+ item.badge !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "pactor-chat-menu__badge", children: item.badge }) : null
3177
4028
  ]
3178
4029
  }
3179
- )
3180
- ] }, item.key);
3181
- }) });
4030
+ );
4031
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react18.Fragment, { children: [
4032
+ groupLabel !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
4033
+ collapsed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_components12.RailTooltip, { label: item.label, children: button }) : button
4034
+ ] }, item.key);
4035
+ }) });
4036
+ })();
4037
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4038
+ ChromeSlot,
4039
+ {
4040
+ fragment: buildSidebarMenuFragment({ menu, activeMenuKey }),
4041
+ fallback
4042
+ }
4043
+ );
3182
4044
  }
3183
4045
  function Chat({
3184
4046
  title,
@@ -3191,6 +4053,11 @@ function Chat({
3191
4053
  menu,
3192
4054
  activeMenuKey,
3193
4055
  sidebar,
4056
+ toolbarStart,
4057
+ toolbarEnd,
4058
+ composerFooter,
4059
+ composerBottom,
4060
+ hideToolbar,
3194
4061
  headerExtra,
3195
4062
  header,
3196
4063
  headerActions,
@@ -3218,7 +4085,7 @@ function Chat({
3218
4085
  onMenuSelect
3219
4086
  }) {
3220
4087
  const services = useChatServices();
3221
- (0, import_react17.useEffect)(() => {
4088
+ (0, import_react18.useEffect)(() => {
3222
4089
  if (!services) {
3223
4090
  console.warn(
3224
4091
  "[@pactor-app/chat] Chat \u672A\u68C0\u6D4B\u5230 ChatServicesProvider\uFF1A\u8BF7\u5728\u5E94\u7528\u6839\u90E8\u4EE5 <ChatServicesProvider> \u6CE8\u5165 transport \u7B49\u670D\u52A1"
@@ -3226,10 +4093,10 @@ function Chat({
3226
4093
  }
3227
4094
  }, [services]);
3228
4095
  if (!services) {
3229
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "pactor-chat-notice", role: "note", children: "\u672A\u914D\u7F6E\u804A\u5929\u670D\u52A1\uFF1A\u8BF7\u5728 Chat \u5916\u5C42\u4F7F\u7528 ChatServicesProvider \u6CE8\u5165 transport \u7B49\u670D\u52A1\u540E\u91CD\u8BD5\u3002" });
4096
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "pactor-chat-notice", role: "note", children: "\u672A\u914D\u7F6E\u804A\u5929\u670D\u52A1\uFF1A\u8BF7\u5728 Chat \u5916\u5C42\u4F7F\u7528 ChatServicesProvider \u6CE8\u5165 transport \u7B49\u670D\u52A1\u540E\u91CD\u8BD5\u3002" });
3230
4097
  }
3231
4098
  const sidebarSlot = resolveSlot(sidebar);
3232
- const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4099
+ const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3233
4100
  ChatSidebarMenu,
3234
4101
  {
3235
4102
  menu,
@@ -3237,7 +4104,7 @@ function Chat({
3237
4104
  onMenuSelect
3238
4105
  }
3239
4106
  ) : void 0);
3240
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4107
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3241
4108
  ChatComposition,
3242
4109
  {
3243
4110
  services,
@@ -3248,9 +4115,14 @@ function Chat({
3248
4115
  conversationId,
3249
4116
  params,
3250
4117
  className,
3251
- showHeader: showHeader !== false,
4118
+ showHeader,
3252
4119
  header: resolveSlot(header),
3253
4120
  headerExtra: resolveSlot(headerExtra) ?? services.headerExtra,
4121
+ toolbarStart: resolveSlot(toolbarStart),
4122
+ toolbarEnd: resolveSlot(toolbarEnd),
4123
+ composerFooter: resolveSlot(composerFooter),
4124
+ composerBottom: resolveSlot(composerBottom),
4125
+ hideToolbar,
3254
4126
  headerActions,
3255
4127
  editableTitle,
3256
4128
  onExport,
@@ -3292,7 +4164,7 @@ function ChatDslAdapter({
3292
4164
  onTitleChange,
3293
4165
  ...rest
3294
4166
  }) {
3295
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4167
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3296
4168
  Chat,
3297
4169
  {
3298
4170
  ...rest,
@@ -3317,13 +4189,18 @@ function registerChatComponent(registry) {
3317
4189
  }
3318
4190
  registerChatShellComponent(registry);
3319
4191
  }
4192
+ function createChatComponentRegistry() {
4193
+ const registry = (0, import_components12.createComponentRegistry)();
4194
+ registerChatComponent(registry);
4195
+ return registry;
4196
+ }
3320
4197
 
3321
4198
  // src/view/ChatLayout.tsx
3322
- var import_jsx_runtime19 = require("react/jsx-runtime");
4199
+ var import_jsx_runtime22 = require("react/jsx-runtime");
3323
4200
  function ChatLayout({ title, children }) {
3324
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "pactor-chat-layout", children: [
3325
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
3326
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("main", { className: "pactor-chat-layout__main", children })
4201
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "pactor-chat-layout", children: [
4202
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
4203
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("main", { className: "pactor-chat-layout__main", children })
3327
4204
  ] });
3328
4205
  }
3329
4206
  var CHAT_LAYOUT_TYPE = "chat";
@@ -3353,14 +4230,13 @@ function registerChatLayout(layoutRegistry) {
3353
4230
  ConversationStore,
3354
4231
  DEFAULT_CHAT_GREETING,
3355
4232
  EmptyState,
3356
- MENU_ICONS,
3357
- MESSAGE_ACTION_ICONS,
3358
4233
  MessageActions,
3359
4234
  MessageBubble,
3360
4235
  PartRenderer,
3361
4236
  applyChatEvent,
3362
4237
  buildChatScope,
3363
4238
  collectMessageText,
4239
+ createChatComponentRegistry,
3364
4240
  createChatId,
3365
4241
  createChatRuntime,
3366
4242
  createChatWorkspace,