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