@pactor-app/chat 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -792,12 +792,12 @@ function useChatAutoScroll({
792
792
  }
793
793
 
794
794
  // src/chat-composition.tsx
795
- import { useRenderer as useRenderer3 } from "@pactor-app/runtime";
795
+ import { useRenderer as useRenderer4 } from "@pactor-app/runtime";
796
796
  import {
797
- Component as Component2,
797
+ Component as Component3,
798
798
  isValidElement,
799
- useRef as useRef11,
800
- useState as useState15,
799
+ useRef as useRef12,
800
+ useState as useState14,
801
801
  useSyncExternalStore as useSyncExternalStore2
802
802
  } from "react";
803
803
 
@@ -824,132 +824,283 @@ function useChatModel({
824
824
  }
825
825
 
826
826
  // src/view/AppShell.tsx
827
- import { Menu, PanelLeft } from "lucide-react";
828
827
  import {
829
- createContext,
830
- useCallback as useCallback2,
831
- useContext,
832
- useEffect as useEffect2,
833
- useState as useState4
834
- } from "react";
835
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
836
- var SidebarDrawerContext = createContext(null);
828
+ Sidebar as UiSidebar,
829
+ useSidebarDrawer as useUiSidebarDrawer
830
+ } from "@pactor-app/ui/components";
831
+ import { jsx } from "react/jsx-runtime";
837
832
  function useSidebarDrawer() {
838
- return useContext(SidebarDrawerContext);
833
+ return useUiSidebarDrawer();
839
834
  }
840
- var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
841
- function useIsMobileViewport() {
842
- const [isMobile, setIsMobile] = useState4(
843
- () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
844
- );
845
- useEffect2(() => {
846
- if (typeof window.matchMedia !== "function") {
847
- return;
848
- }
849
- const media = window.matchMedia(MOBILE_MEDIA_QUERY);
850
- const onChange = (event) => {
851
- setIsMobile(event.matches);
852
- };
853
- setIsMobile(media.matches);
854
- media.addEventListener("change", onChange);
855
- return () => media.removeEventListener("change", onChange);
856
- }, []);
857
- return isMobile;
858
- }
859
- function AppShell({ sidebar, brand, locationKey, sidebarFooter, collapsible = false, children, className }) {
860
- const [drawerOpen, setDrawerOpen] = useState4(false);
861
- const isMobile = useIsMobileViewport();
862
- const close = useCallback2(() => setDrawerOpen(false), []);
863
- const [collapsed, setCollapsed] = useState4(false);
864
- const collapsedEffective = collapsible && collapsed && !isMobile;
865
- const framed = brand !== void 0 || sidebarFooter !== void 0;
866
- const [trackedLocationKey, setTrackedLocationKey] = useState4(locationKey);
867
- if (trackedLocationKey !== locationKey) {
868
- setTrackedLocationKey(locationKey);
869
- setDrawerOpen(false);
870
- }
871
- useEffect2(() => {
872
- if (!drawerOpen) {
873
- return;
835
+ function AppShell({
836
+ sidebar,
837
+ brand,
838
+ locationKey,
839
+ sidebarFooter,
840
+ collapsible = false,
841
+ children,
842
+ className
843
+ }) {
844
+ return /* @__PURE__ */ jsx(
845
+ UiSidebar,
846
+ {
847
+ content: sidebar,
848
+ brand,
849
+ locationKey,
850
+ sidebarFooter,
851
+ collapsible,
852
+ className: `pactor-chat-shell h-full min-h-0${className ? ` ${className}` : ""}`,
853
+ children
874
854
  }
875
- const onKeyDown = (event) => {
876
- if (event.key === "Escape") {
877
- setDrawerOpen(false);
878
- }
879
- };
880
- window.addEventListener("keydown", onKeyDown);
881
- return () => window.removeEventListener("keydown", onKeyDown);
882
- }, [drawerOpen]);
883
- return /* @__PURE__ */ jsx(SidebarDrawerContext.Provider, { value: { close, collapsed: collapsedEffective }, children: /* @__PURE__ */ jsxs("div", { className: `pactor-chat-shell${className ? ` ${className}` : ""}`, children: [
884
- drawerOpen ? /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__scrim", "aria-hidden": true, onClick: close }) : null,
885
- /* @__PURE__ */ jsx(
886
- "div",
887
- {
888
- className: `pactor-chat-shell__sidebar${framed ? " pactor-chat-shell__sidebar--framed" : ""}${collapsedEffective ? " pactor-chat-shell__sidebar--collapsed" : ""}`,
889
- "data-open": drawerOpen ? "true" : "false",
890
- inert: isMobile && !drawerOpen ? true : void 0,
891
- children: framed ? /* @__PURE__ */ jsxs(Fragment, { children: [
892
- brand !== void 0 && // 桌面品牌行(决策 D3):brand 左、折叠钮右;移动端由
893
- // CSS 隐藏(品牌维持顶栏现状)
894
- /* @__PURE__ */ jsxs("div", { className: "pactor-chat-shell__sidebar-header", children: [
895
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__brand", children: brand }),
896
- collapsible && !isMobile && /* @__PURE__ */ jsx(
897
- "button",
898
- {
899
- type: "button",
900
- className: "pactor-chat-icon-button pactor-chat-shell__collapse",
901
- "aria-label": collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
902
- "aria-expanded": !collapsedEffective,
903
- title: collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
904
- onClick: () => setCollapsed((value) => !value),
905
- children: /* @__PURE__ */ jsx(PanelLeft, { size: 16, strokeWidth: 1.7 })
906
- }
907
- )
908
- ] }),
909
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__sidebar-body", children: sidebar }),
910
- sidebarFooter !== void 0 && /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__sidebar-footer", children: sidebarFooter })
911
- ] }) : sidebar
912
- }
913
- ),
914
- /* @__PURE__ */ jsxs("main", { className: "pactor-chat-shell__main", children: [
915
- /* @__PURE__ */ jsxs("div", { className: "pactor-chat-shell__topbar", children: [
916
- /* @__PURE__ */ jsx(
917
- "button",
918
- {
919
- type: "button",
920
- className: "pactor-chat-icon-button pactor-chat-shell__menu",
921
- "aria-label": "\u6253\u5F00\u5BFC\u822A",
922
- "aria-expanded": drawerOpen,
923
- onClick: () => setDrawerOpen(true),
924
- children: /* @__PURE__ */ jsx(Menu, { size: 18, strokeWidth: 1.7 })
925
- }
926
- ),
927
- brand
928
- ] }),
929
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__content", children })
930
- ] })
931
- ] }) });
855
+ );
932
856
  }
933
857
 
934
858
  // src/view/ArtifactPanel.tsx
935
- import { X } from "lucide-react";
859
+ import { Icon as Icon3 } from "@pactor-app/ui/components";
936
860
  import { Registry as Registry2 } from "@pactor-app/core";
937
861
  import { MarkdownRenderer as MarkdownRenderer2 } from "@pactor-app/markdown";
938
- import { DslRenderer as DslRenderer2, useRenderer as useRenderer2 } from "@pactor-app/runtime";
862
+ import { DslRenderer as DslRenderer2, useRenderer as useRenderer3 } from "@pactor-app/runtime";
939
863
  import {
940
- Component,
864
+ Component as Component2,
865
+ useEffect as useEffect2,
941
866
  useMemo,
942
867
  useRef as useRef3,
943
- useState as useState5
868
+ useState as useState4
944
869
  } from "react";
945
870
 
946
871
  // src/view/PartRenderer.tsx
947
- import { CircleAlert, PanelRightOpen } from "lucide-react";
872
+ import { Icon } from "@pactor-app/ui/components";
948
873
  import { MarkdownRenderer } from "@pactor-app/markdown";
949
- import { DslRenderer, useRenderer } from "@pactor-app/runtime";
950
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
874
+ import { DslRenderer, useRenderer as useRenderer2 } from "@pactor-app/runtime";
875
+
876
+ // src/view/chrome/artifact-card.tsx
877
+ function buildArtifactCardFragment(artifact) {
878
+ return {
879
+ requires: ["Card", "Flex", "Text", "Badge", "Button"],
880
+ node: {
881
+ type: "Card",
882
+ props: { className: "pactor-chat-artifact-card" },
883
+ children: [
884
+ {
885
+ type: "Flex",
886
+ props: { justify: "space-between", align: "center", gap: "small" },
887
+ children: [
888
+ {
889
+ type: "Text",
890
+ props: {
891
+ text: artifact.title,
892
+ strong: true,
893
+ className: "pactor-chat-artifact-card__title"
894
+ }
895
+ },
896
+ {
897
+ type: "Badge",
898
+ props: {
899
+ text: artifact.kind,
900
+ variant: "secondary",
901
+ className: "pactor-chat-artifact-card__kind"
902
+ }
903
+ },
904
+ {
905
+ type: "Button",
906
+ props: {
907
+ text: "\u5728\u9762\u677F\u4E2D\u6253\u5F00",
908
+ variant: "default",
909
+ size: "small",
910
+ className: "pactor-chat-artifact-card__open"
911
+ },
912
+ events: {
913
+ onClick: [
914
+ {
915
+ action: "chat.openArtifact",
916
+ params: { artifact }
917
+ }
918
+ ]
919
+ }
920
+ }
921
+ ]
922
+ }
923
+ ]
924
+ }
925
+ };
926
+ }
927
+
928
+ // src/view/chrome/ChromeSlot.tsx
929
+ import { useRenderer } from "@pactor-app/runtime";
930
+ import { Component } from "react";
931
+
932
+ // src/view/chrome/actions.ts
933
+ var handlers = {};
934
+ function setChatChromeHandlers(next) {
935
+ handlers = { ...handlers, ...next };
936
+ }
937
+ var messageActionDispatchers = /* @__PURE__ */ new Map();
938
+ function setMessageActionDispatcher(token, dispatch) {
939
+ messageActionDispatchers.set(token, dispatch);
940
+ }
941
+ function clearMessageActionDispatcher(token) {
942
+ messageActionDispatchers.delete(token);
943
+ }
944
+ var CHROME_ACTION_HANDLERS = {
945
+ "chat.sendSuggestion": (action) => {
946
+ const text = action.params?.["text"];
947
+ if (typeof text === "string") {
948
+ handlers.sendSuggestion?.(text);
949
+ }
950
+ return { ok: true };
951
+ },
952
+ "chat.openArtifact": (action) => {
953
+ const artifact = action.params?.["artifact"];
954
+ if (artifact) {
955
+ handlers.openArtifact?.(artifact);
956
+ }
957
+ return { ok: true };
958
+ },
959
+ "chat.messageAction": (action) => {
960
+ const token = action.params?.["token"];
961
+ const actionId = action.params?.["actionId"];
962
+ if (typeof token === "string" && typeof actionId === "string") {
963
+ messageActionDispatchers.get(token)?.(actionId);
964
+ }
965
+ return { ok: true };
966
+ },
967
+ "chat.composerSubmit": (action) => {
968
+ const value = action.params?.["value"];
969
+ if (typeof value === "string") {
970
+ handlers.composerSubmit?.(value);
971
+ }
972
+ return { ok: true };
973
+ },
974
+ "chat.menuSelect": (action) => {
975
+ const key = action.params?.["key"];
976
+ if (typeof key === "string") {
977
+ handlers.menuSelect?.(key);
978
+ }
979
+ return { ok: true };
980
+ },
981
+ "chat.modelChange": (action) => {
982
+ const value = action.params?.["value"];
983
+ if (typeof value === "string") {
984
+ handlers.modelChange?.(value);
985
+ }
986
+ return { ok: true };
987
+ },
988
+ "chat.headerAction": (action) => {
989
+ const name = action.params?.["action"];
990
+ if (typeof name === "string") {
991
+ handlers.headerAction?.(name);
992
+ }
993
+ return { ok: true };
994
+ },
995
+ "chat.panelClose": () => {
996
+ handlers.panelClose?.();
997
+ return { ok: true };
998
+ }
999
+ };
1000
+ function ensureChatChromeActions(actions) {
1001
+ for (const [name, handler] of Object.entries(CHROME_ACTION_HANDLERS)) {
1002
+ if (!actions.has(name)) {
1003
+ actions.register(name, handler);
1004
+ }
1005
+ }
1006
+ }
1007
+ function invokeChatPanelClose() {
1008
+ handlers.panelClose?.();
1009
+ }
1010
+
1011
+ // src/view/chrome/ChromeSlot.tsx
1012
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
1013
+ var warnedMissing = /* @__PURE__ */ new Set();
1014
+ function warnMissingOnce(key, detail) {
1015
+ if (!warnedMissing.has(key)) {
1016
+ warnedMissing.add(key);
1017
+ console.warn(`[@pactor-app/chat] ${detail}`);
1018
+ }
1019
+ }
1020
+ function ChromeSlotInner({
1021
+ fragment,
1022
+ fallback,
1023
+ components,
1024
+ wrap
1025
+ }) {
1026
+ let renderer;
1027
+ try {
1028
+ renderer = useRenderer();
1029
+ } catch {
1030
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
1031
+ }
1032
+ ensureChatChromeActions(renderer.context.actions);
1033
+ if (components) {
1034
+ for (const [name, component] of Object.entries(components)) {
1035
+ const existing = renderer.context.components.get(name);
1036
+ if (existing !== void 0 && existing !== component) {
1037
+ warnMissingOnce(
1038
+ `conflict:${name}`,
1039
+ `[@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`
1040
+ );
1041
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
1042
+ }
1043
+ if (existing === void 0) {
1044
+ renderer.context.components.register(
1045
+ name,
1046
+ component
1047
+ );
1048
+ }
1049
+ }
1050
+ }
1051
+ const missing = fragment.requires.filter(
1052
+ (type) => !renderer.context.components.has(type)
1053
+ );
1054
+ if (missing.length > 0) {
1055
+ warnMissingOnce(
1056
+ `${fragment.node.type}:${missing.join(",")}`,
1057
+ `[@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`
1058
+ );
1059
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
1060
+ }
1061
+ const rendered = /* @__PURE__ */ jsx2("div", { className: "pactor-chat-chrome", children: renderer.renderChildren([fragment.node]) });
1062
+ return wrap ? /* @__PURE__ */ jsx2(Fragment, { children: wrap(rendered) }) : rendered;
1063
+ }
1064
+ var ChromeSlotGuard = class extends Component {
1065
+ constructor() {
1066
+ super(...arguments);
1067
+ this.state = { failed: false };
1068
+ }
1069
+ static getDerivedStateFromError() {
1070
+ return { failed: true };
1071
+ }
1072
+ render() {
1073
+ return this.state.failed ? this.props.fallback : this.props.children;
1074
+ }
1075
+ };
1076
+ function ChromeSlot(props) {
1077
+ return /* @__PURE__ */ jsx2(ChromeSlotGuard, { fallback: props.fallback, children: /* @__PURE__ */ jsx2(ChromeSlotInner, { ...props }) });
1078
+ }
1079
+
1080
+ // src/view/chrome/feedback.tsx
1081
+ function buildErrorFragment(message) {
1082
+ return {
1083
+ requires: ["Alert"],
1084
+ node: {
1085
+ type: "Alert",
1086
+ props: { variant: "error", message }
1087
+ }
1088
+ };
1089
+ }
1090
+ function buildStatusFragment(text) {
1091
+ return {
1092
+ requires: ["Text"],
1093
+ node: {
1094
+ type: "Text",
1095
+ props: { type: "secondary", text }
1096
+ }
1097
+ };
1098
+ }
1099
+
1100
+ // src/view/PartRenderer.tsx
1101
+ import { Fragment as Fragment2, jsx as jsx3, jsxs } from "react/jsx-runtime";
951
1102
  function ChatDenied({ deniedType }) {
952
- return /* @__PURE__ */ jsxs2("div", { className: "pactor-chat-denied", "data-pactor-chat-denied": deniedType, children: [
1103
+ return /* @__PURE__ */ jsxs("div", { className: "pactor-chat-denied", "data-pactor-chat-denied": deniedType, children: [
953
1104
  "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728 Chat \u4E2D\u4F7F\u7528",
954
1105
  deniedType ? `\uFF1A${deniedType}` : ""
955
1106
  ] });
@@ -958,64 +1109,90 @@ function ChatDslNode({
958
1109
  node,
959
1110
  scope
960
1111
  }) {
961
- const renderer = useRenderer();
1112
+ const renderer = useRenderer2();
962
1113
  const extraScope = scope ? buildChatScope(scope) : void 0;
963
- return /* @__PURE__ */ jsx2(Fragment2, { children: renderer.renderChildren([node], extraScope) });
1114
+ return /* @__PURE__ */ jsx3(Fragment2, { children: renderer.renderChildren([node], extraScope) });
964
1115
  }
965
1116
  function SmoothText({ content, streaming }) {
966
1117
  const shown = useSmoothStreamText(content, streaming);
967
- return /* @__PURE__ */ jsx2("div", { className: "pactor-chat-text", children: shown });
1118
+ return /* @__PURE__ */ jsx3("div", { className: "pactor-chat-text", children: shown });
968
1119
  }
969
1120
  function SmoothMarkdown({ content, streaming }) {
970
1121
  const shown = useSmoothStreamText(content, streaming);
971
- return /* @__PURE__ */ jsx2(MarkdownRenderer, { content: shown });
1122
+ return /* @__PURE__ */ jsx3(MarkdownRenderer, { content: shown });
972
1123
  }
973
1124
  function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact }) {
974
1125
  switch (part.type) {
975
1126
  case "text":
976
- return /* @__PURE__ */ jsx2(SmoothText, { content: part.content, streaming });
1127
+ return /* @__PURE__ */ jsx3(SmoothText, { content: part.content, streaming });
977
1128
  case "markdown":
978
- return /* @__PURE__ */ jsx2(SmoothMarkdown, { content: part.content, streaming });
1129
+ return /* @__PURE__ */ jsx3(SmoothMarkdown, { content: part.content, streaming });
979
1130
  case "thinking":
980
1131
  case "tool-call":
981
1132
  case "tool-result":
982
1133
  return null;
983
1134
  case "dsl": {
984
1135
  if (!part.schema || !runtime) {
985
- return /* @__PURE__ */ jsx2("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" });
1136
+ return /* @__PURE__ */ jsx3(
1137
+ ChromeSlot,
1138
+ {
1139
+ fragment: buildStatusFragment(
1140
+ part.schema ? "DSL \u6E32\u67D3\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "\u5185\u5BB9\u751F\u6210\u4E2D\u2026"
1141
+ ),
1142
+ fallback: /* @__PURE__ */ jsx3("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" })
1143
+ }
1144
+ );
986
1145
  }
987
1146
  const guarded = guardChatDsl(part.schema, runtime.context.components);
988
- return /* @__PURE__ */ jsx2("div", { className: "pactor-chat-dsl", children: /* @__PURE__ */ jsx2(DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx2(ChatDslNode, { node: guarded, scope }) }) });
1147
+ return /* @__PURE__ */ jsx3("div", { className: "pactor-chat-dsl", children: /* @__PURE__ */ jsx3(DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx3(ChatDslNode, { node: guarded, scope }) }) });
989
1148
  }
990
1149
  case "error":
991
- return /* @__PURE__ */ jsxs2("div", { className: "pactor-chat-error", role: "alert", children: [
992
- /* @__PURE__ */ jsx2(CircleAlert, { size: 14 }),
993
- /* @__PURE__ */ jsx2("span", { children: part.message })
994
- ] });
1150
+ return /* @__PURE__ */ jsx3(
1151
+ ChromeSlot,
1152
+ {
1153
+ fragment: buildErrorFragment(part.message),
1154
+ fallback: /* @__PURE__ */ jsxs("div", { className: "pactor-chat-error", role: "alert", children: [
1155
+ /* @__PURE__ */ jsx3(Icon, { name: "error", size: 14 }),
1156
+ /* @__PURE__ */ jsx3("span", { children: part.message })
1157
+ ] })
1158
+ }
1159
+ );
995
1160
  case "status":
996
- return /* @__PURE__ */ jsx2("div", { className: "pactor-chat-status", children: part.text });
1161
+ return /* @__PURE__ */ jsx3(
1162
+ ChromeSlot,
1163
+ {
1164
+ fragment: buildStatusFragment(part.text),
1165
+ fallback: /* @__PURE__ */ jsx3("div", { className: "pactor-chat-status", children: part.text })
1166
+ }
1167
+ );
997
1168
  case "artifact":
998
- return /* @__PURE__ */ jsxs2(
999
- "div",
1169
+ return /* @__PURE__ */ jsx3(
1170
+ ChromeSlot,
1000
1171
  {
1001
- className: "pactor-chat-artifact-card",
1002
- "data-artifact-kind": part.artifact.kind,
1003
- children: [
1004
- /* @__PURE__ */ jsx2("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
1005
- /* @__PURE__ */ jsx2("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
1006
- /* @__PURE__ */ jsxs2(
1007
- "button",
1008
- {
1009
- type: "button",
1010
- className: "pactor-chat-artifact-card__open",
1011
- onClick: () => onOpenArtifact?.(part.artifact),
1012
- children: [
1013
- /* @__PURE__ */ jsx2(PanelRightOpen, { size: 13, strokeWidth: 1.7 }),
1014
- "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1015
- ]
1016
- }
1017
- )
1018
- ]
1172
+ fragment: buildArtifactCardFragment(part.artifact),
1173
+ fallback: /* @__PURE__ */ jsxs(
1174
+ "div",
1175
+ {
1176
+ className: "pactor-chat-artifact-card",
1177
+ "data-artifact-kind": part.artifact.kind,
1178
+ children: [
1179
+ /* @__PURE__ */ jsx3("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
1180
+ /* @__PURE__ */ jsx3("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
1181
+ /* @__PURE__ */ jsxs(
1182
+ "button",
1183
+ {
1184
+ type: "button",
1185
+ className: "pactor-chat-artifact-card__open",
1186
+ onClick: () => onOpenArtifact?.(part.artifact),
1187
+ children: [
1188
+ /* @__PURE__ */ jsx3(Icon, { name: "panel-right-open", size: 13, strokeWidth: 1.7 }),
1189
+ "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1190
+ ]
1191
+ }
1192
+ )
1193
+ ]
1194
+ }
1195
+ )
1019
1196
  }
1020
1197
  );
1021
1198
  default:
@@ -1023,8 +1200,65 @@ function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact
1023
1200
  }
1024
1201
  }
1025
1202
 
1203
+ // src/view/chrome/panel-header.tsx
1204
+ import { Icon as Icon2 } from "@pactor-app/ui/components";
1205
+ import { jsx as jsx4 } from "react/jsx-runtime";
1206
+ function ChatPanelCloseButton() {
1207
+ return /* @__PURE__ */ jsx4(
1208
+ "button",
1209
+ {
1210
+ type: "button",
1211
+ className: "pactor-chat-icon-button pactor-chat-inspector__close",
1212
+ title: "\u5173\u95ED\u9762\u677F",
1213
+ "aria-label": "\u5173\u95ED\u9762\u677F",
1214
+ onClick: () => invokeChatPanelClose(),
1215
+ children: /* @__PURE__ */ jsx4(Icon2, { name: "x", size: 16 })
1216
+ }
1217
+ );
1218
+ }
1219
+ var PANEL_HEADER_INTERNAL_COMPONENTS = { ChatPanelCloseButton };
1220
+ function buildPanelHeaderFragment(props) {
1221
+ return {
1222
+ requires: ["Flex", "Text", "Badge", "ChatPanelCloseButton"],
1223
+ node: {
1224
+ type: "Flex",
1225
+ props: {
1226
+ justify: "space-between",
1227
+ align: "center",
1228
+ gap: "small",
1229
+ className: "pactor-chat-inspector__header-dsl"
1230
+ },
1231
+ children: [
1232
+ {
1233
+ type: "Flex",
1234
+ props: { align: "center", gap: "small" },
1235
+ children: [
1236
+ {
1237
+ type: "Text",
1238
+ props: {
1239
+ text: props.title,
1240
+ strong: true,
1241
+ className: "pactor-chat-inspector__title"
1242
+ }
1243
+ },
1244
+ {
1245
+ type: "Badge",
1246
+ props: {
1247
+ text: props.kind,
1248
+ variant: "secondary",
1249
+ className: "pactor-chat-inspector__kind"
1250
+ }
1251
+ }
1252
+ ]
1253
+ },
1254
+ { type: "ChatPanelCloseButton" }
1255
+ ]
1256
+ }
1257
+ };
1258
+ }
1259
+
1026
1260
  // src/view/ArtifactPanel.tsx
1027
- import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1261
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1028
1262
  function useArtifactRuntime(safeRegistry, user) {
1029
1263
  return useMemo(() => {
1030
1264
  if (!safeRegistry) {
@@ -1038,7 +1272,7 @@ function useArtifactRuntime(safeRegistry, user) {
1038
1272
  return createChatRuntime({ safeRegistry: components, user });
1039
1273
  }, [safeRegistry, user]);
1040
1274
  }
1041
- var ArtifactDslGuard = class extends Component {
1275
+ var ArtifactDslGuard = class extends Component2 {
1042
1276
  constructor() {
1043
1277
  super(...arguments);
1044
1278
  this.state = { failed: false };
@@ -1050,12 +1284,12 @@ var ArtifactDslGuard = class extends Component {
1050
1284
  console.warn("[@pactor-app/chat] DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
1051
1285
  }
1052
1286
  render() {
1053
- return this.state.failed ? /* @__PURE__ */ jsx3("div", { className: "pactor-chat-denied", children: "DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A" }) : this.props.children;
1287
+ return this.state.failed ? /* @__PURE__ */ jsx5("div", { className: "pactor-chat-denied", children: "DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A" }) : this.props.children;
1054
1288
  }
1055
1289
  };
1056
1290
  function ArtifactDslNode({ node }) {
1057
- const { renderChildren } = useRenderer2();
1058
- return /* @__PURE__ */ jsx3(Fragment3, { children: renderChildren([node]) });
1291
+ const { renderChildren } = useRenderer3();
1292
+ return /* @__PURE__ */ jsx5(Fragment3, { children: renderChildren([node]) });
1059
1293
  }
1060
1294
  function ArtifactDslContent({
1061
1295
  content,
@@ -1063,10 +1297,10 @@ function ArtifactDslContent({
1063
1297
  }) {
1064
1298
  const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
1065
1299
  if (!node || !runtime) {
1066
- return /* @__PURE__ */ jsx3("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" });
1300
+ return /* @__PURE__ */ jsx5("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" });
1067
1301
  }
1068
1302
  const guarded = guardChatDsl(node, runtime.context.components);
1069
- return /* @__PURE__ */ jsx3(DslRenderer2, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx3(ArtifactDslGuard, { children: /* @__PURE__ */ jsx3(ArtifactDslNode, { node: guarded }) }) });
1303
+ return /* @__PURE__ */ jsx5(DslRenderer2, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx5(ArtifactDslGuard, { children: /* @__PURE__ */ jsx5(ArtifactDslNode, { node: guarded }) }) });
1070
1304
  }
1071
1305
  function ArtifactContent({
1072
1306
  artifact,
@@ -1074,16 +1308,16 @@ function ArtifactContent({
1074
1308
  }) {
1075
1309
  switch (artifact.kind) {
1076
1310
  case "markdown":
1077
- return /* @__PURE__ */ jsx3(MarkdownRenderer2, { content: String(artifact.content) });
1311
+ return /* @__PURE__ */ jsx5(MarkdownRenderer2, { content: String(artifact.content) });
1078
1312
  case "code":
1079
- return /* @__PURE__ */ jsxs3("div", { className: "pactor-chat-inspector__code", children: [
1080
- artifact.language ? /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1081
- /* @__PURE__ */ jsx3("pre", { children: /* @__PURE__ */ jsx3("code", { children: String(artifact.content) }) })
1313
+ return /* @__PURE__ */ jsxs2("div", { className: "pactor-chat-inspector__code", children: [
1314
+ artifact.language ? /* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1315
+ /* @__PURE__ */ jsx5("pre", { children: /* @__PURE__ */ jsx5("code", { children: String(artifact.content) }) })
1082
1316
  ] });
1083
1317
  case "text":
1084
- return /* @__PURE__ */ jsx3("pre", { className: "pactor-chat-inspector__text", children: String(artifact.content) });
1318
+ return /* @__PURE__ */ jsx5("pre", { className: "pactor-chat-inspector__text", children: String(artifact.content) });
1085
1319
  case "dsl":
1086
- return /* @__PURE__ */ jsx3(ArtifactDslContent, { content: artifact.content, runtime });
1320
+ return /* @__PURE__ */ jsx5(ArtifactDslContent, { content: artifact.content, runtime });
1087
1321
  default:
1088
1322
  return null;
1089
1323
  }
@@ -1097,8 +1331,13 @@ function ArtifactPanel({
1097
1331
  className
1098
1332
  }) {
1099
1333
  const runtime = useArtifactRuntime(safeRegistry, user);
1334
+ const closeRef = useRef3(onClose);
1335
+ closeRef.current = onClose;
1336
+ useEffect2(() => {
1337
+ setChatChromeHandlers({ panelClose: () => closeRef.current?.() });
1338
+ }, []);
1100
1339
  const panelRef = useRef3(null);
1101
- const [width, setWidth] = useState5(null);
1340
+ const [width, setWidth] = useState4(null);
1102
1341
  const handleResizeStart = (event) => {
1103
1342
  event.preventDefault();
1104
1343
  const panel = panelRef.current;
@@ -1124,7 +1363,7 @@ function ArtifactPanel({
1124
1363
  document.addEventListener("pointermove", onMove);
1125
1364
  document.addEventListener("pointerup", onUp);
1126
1365
  };
1127
- return /* @__PURE__ */ jsxs3(
1366
+ return /* @__PURE__ */ jsxs2(
1128
1367
  "aside",
1129
1368
  {
1130
1369
  ref: panelRef,
@@ -1132,7 +1371,7 @@ function ArtifactPanel({
1132
1371
  "data-kind": artifact?.kind,
1133
1372
  style: width !== null ? { width: `${width}px` } : void 0,
1134
1373
  children: [
1135
- /* @__PURE__ */ jsx3(
1374
+ /* @__PURE__ */ jsx5(
1136
1375
  "div",
1137
1376
  {
1138
1377
  className: "pactor-chat-inspector__resize",
@@ -1142,43 +1381,53 @@ function ArtifactPanel({
1142
1381
  onPointerDown: handleResizeStart
1143
1382
  }
1144
1383
  ),
1145
- /* @__PURE__ */ jsxs3("div", { className: "pactor-chat-inspector__header", children: [
1146
- /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1147
- artifact ? /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1148
- /* @__PURE__ */ jsx3(
1149
- "button",
1150
- {
1151
- type: "button",
1152
- className: "pactor-chat-icon-button pactor-chat-inspector__close",
1153
- "aria-label": "\u5173\u95ED\u9762\u677F",
1154
- onClick: onClose,
1155
- children: /* @__PURE__ */ jsx3(X, { size: 16, strokeWidth: 1.7 })
1156
- }
1157
- )
1158
- ] }),
1159
- /* @__PURE__ */ jsx3("div", { className: "pactor-chat-inspector__body", children: inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ jsx3(ArtifactContent, { artifact, runtime }) : null })
1384
+ /* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__header", children: /* @__PURE__ */ jsx5(
1385
+ ChromeSlot,
1386
+ {
1387
+ fragment: buildPanelHeaderFragment({
1388
+ title: artifact?.title ?? "\u4EA7\u51FA\u7269",
1389
+ kind: artifact?.kind ?? "text"
1390
+ }),
1391
+ components: PANEL_HEADER_INTERNAL_COMPONENTS,
1392
+ fallback: /* @__PURE__ */ jsxs2(Fragment3, { children: [
1393
+ /* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1394
+ artifact ? /* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1395
+ /* @__PURE__ */ jsx5(
1396
+ "button",
1397
+ {
1398
+ type: "button",
1399
+ className: "pactor-chat-icon-button pactor-chat-inspector__close",
1400
+ "aria-label": "\u5173\u95ED\u9762\u677F",
1401
+ onClick: onClose,
1402
+ children: /* @__PURE__ */ jsx5(Icon3, { name: "x", size: 16, strokeWidth: 1.7 })
1403
+ }
1404
+ )
1405
+ ] })
1406
+ }
1407
+ ) }),
1408
+ /* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__body", children: inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ jsx5(ArtifactContent, { artifact, runtime }) : null })
1160
1409
  ]
1161
1410
  }
1162
1411
  );
1163
1412
  }
1164
1413
 
1165
1414
  // src/view/ChatView.tsx
1166
- import { ArrowDown } from "lucide-react";
1415
+ import { Icon as Icon9 } from "@pactor-app/ui/components";
1167
1416
  import { Registry as Registry3 } from "@pactor-app/core";
1168
- import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef9, useState as useState13, useSyncExternalStore } from "react";
1417
+ import { useEffect as useEffect8, useMemo as useMemo3, useRef as useRef10, useState as useState12, useSyncExternalStore } from "react";
1169
1418
 
1170
1419
  // src/view/Composer.tsx
1171
- import { ArrowUp, FileText, Paperclip, Plus, Square, X as X2 } from "lucide-react";
1172
- import { useLayoutEffect as useLayoutEffect3, useMemo as useMemo2, useRef as useRef6, useState as useState9 } from "react";
1420
+ import { Icon as Icon6 } from "@pactor-app/ui/components";
1421
+ import { useLayoutEffect as useLayoutEffect3, useMemo as useMemo2, useRef as useRef6, useState as useState8 } from "react";
1173
1422
 
1174
1423
  // src/view/AnchoredPopover.tsx
1175
1424
  import {
1176
1425
  useEffect as useEffect3,
1177
1426
  useLayoutEffect as useLayoutEffect2,
1178
1427
  useRef as useRef4,
1179
- useState as useState6
1428
+ useState as useState5
1180
1429
  } from "react";
1181
- import { jsx as jsx4 } from "react/jsx-runtime";
1430
+ import { jsx as jsx6 } from "react/jsx-runtime";
1182
1431
  function AnchoredPopover({
1183
1432
  anchorRect,
1184
1433
  onClose,
@@ -1188,7 +1437,7 @@ function AnchoredPopover({
1188
1437
  sideOffset = 8
1189
1438
  }) {
1190
1439
  const ref = useRef4(null);
1191
- const [position, setPosition] = useState6(null);
1440
+ const [position, setPosition] = useState5(null);
1192
1441
  useLayoutEffect2(() => {
1193
1442
  const element = ref.current;
1194
1443
  if (!element) return;
@@ -1235,7 +1484,7 @@ function AnchoredPopover({
1235
1484
  position?.below ? "pactor-chat-popover--below" : "",
1236
1485
  className ?? ""
1237
1486
  ].filter(Boolean).join(" ");
1238
- return /* @__PURE__ */ jsx4(
1487
+ return /* @__PURE__ */ jsx6(
1239
1488
  "div",
1240
1489
  {
1241
1490
  ref,
@@ -1254,19 +1503,44 @@ function AnchoredPopover({
1254
1503
  }
1255
1504
 
1256
1505
  // src/view/ModelChip.tsx
1257
- import { Check, ChevronsUpDown } from "lucide-react";
1258
- import { useRef as useRef5, useState as useState7 } from "react";
1259
- import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1506
+ import { Icon as Icon4 } from "@pactor-app/ui/components";
1507
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState6 } from "react";
1508
+
1509
+ // src/view/chrome/model-select.tsx
1510
+ function buildModelSelectFragment(props) {
1511
+ return {
1512
+ requires: ["Select"],
1513
+ node: {
1514
+ type: "Select",
1515
+ props: {
1516
+ options: props.models.map((option) => ({
1517
+ value: option.value,
1518
+ label: option.label
1519
+ })),
1520
+ value: props.model,
1521
+ className: "pactor-chat-model-select"
1522
+ },
1523
+ events: {
1524
+ onChange: [
1525
+ { action: "chat.modelChange", params: { value: "${event}" } }
1526
+ ]
1527
+ }
1528
+ }
1529
+ };
1530
+ }
1531
+
1532
+ // src/view/ModelChip.tsx
1533
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1260
1534
  function ModelChip({
1261
1535
  models,
1262
1536
  model,
1263
1537
  onModelChange
1264
1538
  }) {
1265
1539
  const chipRef = useRef5(null);
1266
- const [anchor, setAnchor] = useState7(null);
1540
+ const [anchor, setAnchor] = useState6(null);
1267
1541
  const currentOption = models.find((option) => option.value === model) ?? models[0];
1268
- return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1269
- /* @__PURE__ */ jsxs4(
1542
+ return /* @__PURE__ */ jsxs3(Fragment4, { children: [
1543
+ /* @__PURE__ */ jsxs3(
1270
1544
  "button",
1271
1545
  {
1272
1546
  ref: chipRef,
@@ -1276,18 +1550,18 @@ function ModelChip({
1276
1550
  "aria-label": "\u9009\u62E9\u6A21\u578B",
1277
1551
  onClick: () => setAnchor(chipRef.current.getBoundingClientRect()),
1278
1552
  children: [
1279
- /* @__PURE__ */ jsx5("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1280
- /* @__PURE__ */ jsx5(ChevronsUpDown, { size: 12 })
1553
+ /* @__PURE__ */ jsx7("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1554
+ /* @__PURE__ */ jsx7(Icon4, { name: "chevrons-up-down", size: 12 })
1281
1555
  ]
1282
1556
  }
1283
1557
  ),
1284
- anchor && /* @__PURE__ */ jsx5(
1558
+ anchor && /* @__PURE__ */ jsx7(
1285
1559
  AnchoredPopover,
1286
1560
  {
1287
1561
  anchorRect: anchor,
1288
1562
  onClose: () => setAnchor(null),
1289
1563
  role: "listbox",
1290
- children: models.map((option) => /* @__PURE__ */ jsxs4(
1564
+ children: models.map((option) => /* @__PURE__ */ jsxs3(
1291
1565
  "button",
1292
1566
  {
1293
1567
  type: "button",
@@ -1299,8 +1573,8 @@ function ModelChip({
1299
1573
  setAnchor(null);
1300
1574
  },
1301
1575
  children: [
1302
- /* @__PURE__ */ jsx5("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1303
- option.value === currentOption.value && /* @__PURE__ */ jsx5(Check, { size: 13, className: "pactor-chat-popover__item-check" })
1576
+ /* @__PURE__ */ jsx7("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1577
+ option.value === currentOption.value && /* @__PURE__ */ jsx7(Icon4, { name: "check", size: 13, className: "pactor-chat-popover__item-check" })
1304
1578
  ]
1305
1579
  },
1306
1580
  option.value
@@ -1309,10 +1583,243 @@ function ModelChip({
1309
1583
  )
1310
1584
  ] });
1311
1585
  }
1586
+ function ModelSelect({
1587
+ models,
1588
+ model,
1589
+ onModelChange
1590
+ }) {
1591
+ const changeRef = useRef5(onModelChange);
1592
+ changeRef.current = onModelChange;
1593
+ useEffect4(() => {
1594
+ setChatChromeHandlers({
1595
+ modelChange: (value) => changeRef.current?.(value)
1596
+ });
1597
+ }, []);
1598
+ return /* @__PURE__ */ jsx7(
1599
+ ChromeSlot,
1600
+ {
1601
+ fragment: buildModelSelectFragment({ models, model }),
1602
+ fallback: /* @__PURE__ */ jsx7(ModelChip, { models, model, onModelChange })
1603
+ }
1604
+ );
1605
+ }
1606
+
1607
+ // src/view/chrome/composer.tsx
1608
+ import { Icon as Icon5 } from "@pactor-app/ui/components";
1609
+ import { createContext, useContext } from "react";
1610
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1611
+ var ComposerBridgeContext = createContext(
1612
+ null
1613
+ );
1614
+ function useComposerBridge() {
1615
+ const bridge = useContext(ComposerBridgeContext);
1616
+ if (!bridge) {
1617
+ throw new Error(
1618
+ "[@pactor-app/chat] ChatComposer* \u5185\u90E8\u7EC4\u4EF6\u4EC5\u53EF\u5728 Composer chrome \u7247\u6BB5\u5185\u6E32\u67D3"
1619
+ );
1620
+ }
1621
+ return bridge;
1622
+ }
1623
+ function formatSize(bytes) {
1624
+ if (bytes < 1024) {
1625
+ return `${bytes} B`;
1626
+ }
1627
+ if (bytes < 1024 * 1024) {
1628
+ return `${(bytes / 1024).toFixed(1)} KB`;
1629
+ }
1630
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1631
+ }
1632
+ function ComposerAttachments({
1633
+ attachments,
1634
+ onRemove,
1635
+ onClick
1636
+ }) {
1637
+ if (attachments.length === 0) {
1638
+ return null;
1639
+ }
1640
+ return /* @__PURE__ */ jsx8("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1641
+ const isImage = attachment.mimeType.startsWith("image/");
1642
+ return /* @__PURE__ */ jsxs4(
1643
+ "div",
1644
+ {
1645
+ className: "pactor-chat-composer__attachment",
1646
+ "data-attachment-id": attachment.id,
1647
+ onClick: () => onClick?.(attachment),
1648
+ children: [
1649
+ isImage && attachment.url ? /* @__PURE__ */ jsx8("span", { className: "pactor-chat-composer__attachment-thumb", children: /* @__PURE__ */ jsx8("img", { src: attachment.url, alt: attachment.name }) }) : /* @__PURE__ */ jsxs4("span", { className: "pactor-chat-composer__attachment-info", children: [
1650
+ /* @__PURE__ */ jsx8(Icon5, { name: "file-text", size: 14 }),
1651
+ /* @__PURE__ */ jsx8("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1652
+ attachment.size !== void 0 && /* @__PURE__ */ jsx8("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1653
+ ] }),
1654
+ /* @__PURE__ */ jsx8(
1655
+ "button",
1656
+ {
1657
+ type: "button",
1658
+ className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1659
+ title: "\u79FB\u9664\u9644\u4EF6",
1660
+ "aria-label": "\u79FB\u9664\u9644\u4EF6",
1661
+ onClick: (event) => {
1662
+ event.stopPropagation();
1663
+ onRemove(attachment.id);
1664
+ },
1665
+ children: /* @__PURE__ */ jsx8(Icon5, { name: "x", size: 12 })
1666
+ }
1667
+ )
1668
+ ]
1669
+ },
1670
+ attachment.id
1671
+ );
1672
+ }) });
1673
+ }
1674
+ function ComposerContextChips({
1675
+ chips,
1676
+ onRemove
1677
+ }) {
1678
+ if (chips.length === 0) {
1679
+ return null;
1680
+ }
1681
+ return /* @__PURE__ */ jsx8("div", { className: "pactor-chat-composer__contexts", children: chips.map((chip) => /* @__PURE__ */ jsxs4(
1682
+ "span",
1683
+ {
1684
+ className: "pactor-chat-context-chip",
1685
+ "data-context-id": chip.id,
1686
+ children: [
1687
+ /* @__PURE__ */ jsx8("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1688
+ /* @__PURE__ */ jsx8(
1689
+ "button",
1690
+ {
1691
+ type: "button",
1692
+ className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1693
+ title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1694
+ "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1695
+ onClick: () => onRemove(chip),
1696
+ children: /* @__PURE__ */ jsx8(Icon5, { name: "x", size: 12 })
1697
+ }
1698
+ )
1699
+ ]
1700
+ },
1701
+ `${chip.trigger ?? ""}:${chip.id}`
1702
+ )) });
1703
+ }
1704
+ function ChatComposerHeader() {
1705
+ const bridge = useComposerBridge();
1706
+ return /* @__PURE__ */ jsxs4(Fragment5, { children: [
1707
+ bridge.attachments !== void 0 && /* @__PURE__ */ jsx8(
1708
+ ComposerAttachments,
1709
+ {
1710
+ attachments: bridge.attachments,
1711
+ onRemove: bridge.removeAttachment,
1712
+ onClick: bridge.attachmentClick
1713
+ }
1714
+ ),
1715
+ bridge.contextChips !== void 0 && /* @__PURE__ */ jsx8(
1716
+ ComposerContextChips,
1717
+ {
1718
+ chips: bridge.contextChips,
1719
+ onRemove: bridge.removeChip
1720
+ }
1721
+ )
1722
+ ] });
1723
+ }
1724
+ function ChatComposerToolbarStart() {
1725
+ const bridge = useComposerBridge();
1726
+ return /* @__PURE__ */ jsxs4(Fragment5, { children: [
1727
+ bridge.toolbarStart,
1728
+ bridge.showPlus && /* @__PURE__ */ jsx8(
1729
+ "button",
1730
+ {
1731
+ type: "button",
1732
+ className: "pactor-chat-icon-button",
1733
+ title: "\u66F4\u591A\u529F\u80FD",
1734
+ "aria-label": "\u66F4\u591A\u529F\u80FD",
1735
+ onClick: (event) => bridge.plusClick(event.currentTarget.getBoundingClientRect()),
1736
+ children: /* @__PURE__ */ jsx8(Icon5, { name: "plus", size: 14 })
1737
+ }
1738
+ ),
1739
+ bridge.showAttach && /* @__PURE__ */ jsx8(
1740
+ "button",
1741
+ {
1742
+ type: "button",
1743
+ className: "pactor-chat-icon-button",
1744
+ title: "\u6DFB\u52A0\u9644\u4EF6",
1745
+ "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1746
+ onClick: bridge.openFilePicker,
1747
+ children: /* @__PURE__ */ jsx8(Icon5, { name: "paperclip", size: 14 })
1748
+ }
1749
+ )
1750
+ ] });
1751
+ }
1752
+ function ChatComposerToolbarEnd() {
1753
+ const bridge = useComposerBridge();
1754
+ return /* @__PURE__ */ jsxs4(Fragment5, { children: [
1755
+ bridge.modelChipEnabled && /* @__PURE__ */ jsx8(
1756
+ ModelSelect,
1757
+ {
1758
+ models: bridge.models,
1759
+ model: bridge.model,
1760
+ onModelChange: bridge.modelChange
1761
+ }
1762
+ ),
1763
+ bridge.toolbarEnd,
1764
+ /* @__PURE__ */ jsx8(
1765
+ "button",
1766
+ {
1767
+ type: "button",
1768
+ className: `pactor-chat-composer__button${bridge.streaming ? " pactor-chat-composer__button--stop" : ""}`,
1769
+ title: bridge.streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1770
+ "aria-label": bridge.streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1771
+ disabled: bridge.streaming ? false : bridge.disabled || !bridge.text.trim(),
1772
+ onClick: bridge.streaming ? bridge.stop : bridge.send,
1773
+ children: bridge.streaming ? /* @__PURE__ */ jsx8(Icon5, { name: "square", size: 14, className: "pactor-chat-icon-fill" }) : /* @__PURE__ */ jsx8(Icon5, { name: "arrow-up", size: 16 })
1774
+ }
1775
+ )
1776
+ ] });
1777
+ }
1778
+ function ChatComposerFooter() {
1779
+ const bridge = useComposerBridge();
1780
+ return /* @__PURE__ */ jsx8(Fragment5, { children: bridge.composerFooter });
1781
+ }
1782
+ var COMPOSER_INTERNAL_COMPONENTS = {
1783
+ ChatComposerHeader,
1784
+ ChatComposerToolbarStart,
1785
+ ChatComposerToolbarEnd,
1786
+ ChatComposerFooter
1787
+ };
1788
+ function buildComposerFragment(props) {
1789
+ const hideToolbar = props.hideToolbar === true;
1790
+ return {
1791
+ requires: [
1792
+ "ChatInput",
1793
+ "ChatComposerHeader",
1794
+ ...hideToolbar ? [] : ["ChatComposerToolbarStart", "ChatComposerToolbarEnd"],
1795
+ "ChatComposerFooter"
1796
+ ],
1797
+ node: {
1798
+ type: "ChatInput",
1799
+ props: {
1800
+ placeholder: props.placeholder,
1801
+ disabled: props.disabled,
1802
+ rows: 1,
1803
+ hideSend: true,
1804
+ header: { type: "ChatComposerHeader" },
1805
+ ...hideToolbar ? {} : {
1806
+ toolbarStart: { type: "ChatComposerToolbarStart" },
1807
+ toolbarEnd: { type: "ChatComposerToolbarEnd" }
1808
+ },
1809
+ ...props.hasFooter === true ? { footer: { type: "ChatComposerFooter" } } : {}
1810
+ },
1811
+ events: {
1812
+ onSubmit: [
1813
+ { action: "chat.composerSubmit", params: { value: "${event}" } }
1814
+ ]
1815
+ }
1816
+ }
1817
+ };
1818
+ }
1312
1819
 
1313
1820
  // src/view/context-sources.tsx
1314
- import { useEffect as useEffect4, useState as useState8 } from "react";
1315
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1821
+ import { useEffect as useEffect5, useState as useState7 } from "react";
1822
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1316
1823
  function sourceCacheKey(source) {
1317
1824
  return `${source.service ?? ""}:${JSON.stringify(source.params ?? {})}`;
1318
1825
  }
@@ -1347,8 +1854,8 @@ function ContextSourceList({
1347
1854
  cache,
1348
1855
  onSelect
1349
1856
  }) {
1350
- const [resolutions, setResolutions] = useState8([]);
1351
- useEffect4(() => {
1857
+ const [resolutions, setResolutions] = useState7([]);
1858
+ useEffect5(() => {
1352
1859
  let cancelled = false;
1353
1860
  const initial = sources.map(
1354
1861
  (source) => source.items ? { status: "ready", items: source.items } : { status: "loading" }
@@ -1395,10 +1902,10 @@ function ContextSourceList({
1395
1902
  });
1396
1903
  return /* @__PURE__ */ jsxs5("div", { className: "pactor-chat-source-list", children: [
1397
1904
  groups.map(({ source, resolution, items }) => /* @__PURE__ */ jsxs5("div", { className: "pactor-chat-source-list__group", children: [
1398
- sources.length > 1 && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1399
- resolution.status === "loading" && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1400
- resolution.status === "error" && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1401
- items.map((item) => /* @__PURE__ */ jsx6(
1905
+ sources.length > 1 && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1906
+ resolution.status === "loading" && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1907
+ resolution.status === "error" && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1908
+ items.map((item) => /* @__PURE__ */ jsx9(
1402
1909
  "button",
1403
1910
  {
1404
1911
  type: "button",
@@ -1409,22 +1916,13 @@ function ContextSourceList({
1409
1916
  item.id
1410
1917
  ))
1411
1918
  ] }, `${source.trigger}:${source.label}`)),
1412
- totalMatched === 0 && groups.every(({ resolution }) => resolution.status === "ready") && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__hint", children: "\u65E0\u5339\u914D\u9879" })
1919
+ totalMatched === 0 && groups.every(({ resolution }) => resolution.status === "ready") && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-source-list__hint", children: "\u65E0\u5339\u914D\u9879" })
1413
1920
  ] });
1414
1921
  }
1415
1922
 
1416
1923
  // src/view/Composer.tsx
1417
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1924
+ import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1418
1925
  var MAX_INPUT_HEIGHT = 200;
1419
- function formatSize(bytes) {
1420
- if (bytes < 1024) {
1421
- return `${bytes} B`;
1422
- }
1423
- if (bytes < 1024 * 1024) {
1424
- return `${(bytes / 1024).toFixed(1)} KB`;
1425
- }
1426
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1427
- }
1428
1926
  function detectMention(text, caret) {
1429
1927
  const beforeCaret = text.slice(0, caret);
1430
1928
  const match = /(?:^|\s)@([^\s@]*)$/.exec(beforeCaret);
@@ -1453,10 +1951,12 @@ function Composer({
1453
1951
  contextSources,
1454
1952
  contextChips,
1455
1953
  onContextChipsChange,
1456
- callService
1954
+ callService,
1955
+ composerFooter,
1956
+ hideToolbar = false
1457
1957
  }) {
1458
- const [text, setText] = useState9("");
1459
- const [dragOver, setDragOver] = useState9(false);
1958
+ const [text, setText] = useState8("");
1959
+ const [dragOver, setDragOver] = useState8(false);
1460
1960
  const inputRef = useRef6(null);
1461
1961
  const fileInputRef = useRef6(null);
1462
1962
  const plusButtonRef = useRef6(null);
@@ -1472,8 +1972,8 @@ function Composer({
1472
1972
  );
1473
1973
  const builtinMentionEnabled = onMentionTrigger === void 0 && mentionSources.length > 0;
1474
1974
  const builtinPlusEnabled = onPlusMenu === void 0 && plusSources.length > 0;
1475
- const [mentionState, setMentionState] = useState9(null);
1476
- const [plusAnchor, setPlusAnchor] = useState9(null);
1975
+ const [mentionState, setMentionState] = useState8(null);
1976
+ const [plusAnchor, setPlusAnchor] = useState8(null);
1477
1977
  const sourceCacheRef = useRef6(null);
1478
1978
  if (sourceCacheRef.current === null) {
1479
1979
  sourceCacheRef.current = /* @__PURE__ */ new Map();
@@ -1508,105 +2008,147 @@ function Composer({
1508
2008
  onSend?.(value);
1509
2009
  setText("");
1510
2010
  };
1511
- const handleChange = (event) => {
1512
- const value = event.target.value;
2011
+ const sendRef = useRef6(send);
2012
+ sendRef.current = send;
2013
+ useLayoutEffect3(() => {
2014
+ setChatChromeHandlers({ composerSubmit: () => sendRef.current() });
2015
+ }, []);
2016
+ const syncText = (value, caret, rect) => {
1513
2017
  setText(value);
1514
2018
  if (onMentionTrigger || builtinMentionEnabled) {
1515
- const caret = event.target.selectionStart ?? value.length;
1516
2019
  const query = detectMention(value, caret);
1517
2020
  if (onMentionTrigger) {
1518
2021
  if (query !== null) {
1519
- onMentionTrigger(query, event.target.getBoundingClientRect());
2022
+ onMentionTrigger(query, rect);
1520
2023
  }
1521
2024
  } else {
1522
- setMentionState(
1523
- query !== null ? { query, anchorRect: event.target.getBoundingClientRect() } : null
1524
- );
2025
+ setMentionState(query !== null ? { query, anchorRect: rect } : null);
2026
+ }
2027
+ }
2028
+ };
2029
+ const handleChange = (event) => {
2030
+ syncText(
2031
+ event.target.value,
2032
+ event.target.selectionStart ?? event.target.value.length,
2033
+ event.target.getBoundingClientRect()
2034
+ );
2035
+ };
2036
+ const handleBubbleChange = (event) => {
2037
+ const target = event.target;
2038
+ if (!(target instanceof HTMLTextAreaElement)) {
2039
+ return;
2040
+ }
2041
+ syncText(
2042
+ target.value,
2043
+ target.selectionStart ?? target.value.length,
2044
+ target.getBoundingClientRect()
2045
+ );
2046
+ };
2047
+ const handlePasteFiles = (clipboardData) => {
2048
+ const files = Array.from(clipboardData.files);
2049
+ if (files.length > 0 && onAttachFiles) {
2050
+ onAttachFiles(files);
2051
+ return true;
2052
+ }
2053
+ return false;
2054
+ };
2055
+ const dragHandlers = {
2056
+ onDragOver: (event) => {
2057
+ event.preventDefault();
2058
+ setDragOver(true);
2059
+ },
2060
+ onDragLeave: () => setDragOver(false),
2061
+ onDrop: (event) => {
2062
+ event.preventDefault();
2063
+ setDragOver(false);
2064
+ const files = collectDropFiles(event);
2065
+ if (files.length > 0 && onAttachFiles) {
2066
+ onAttachFiles(files);
1525
2067
  }
1526
2068
  }
1527
2069
  };
1528
- const showToolbar = toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled;
1529
- return /* @__PURE__ */ jsxs6(
1530
- "div",
2070
+ const showToolbar = !hideToolbar && (toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled);
2071
+ const fileInputNode = onAttachFiles ? /* @__PURE__ */ jsx10(
2072
+ "input",
1531
2073
  {
1532
- className: `pactor-chat-composer${dragOver ? " pactor-chat-composer--dragover" : ""}`,
1533
- onDragOver: (event) => {
1534
- event.preventDefault();
1535
- setDragOver(true);
1536
- },
1537
- onDragLeave: () => setDragOver(false),
1538
- onDrop: (event) => {
1539
- event.preventDefault();
1540
- setDragOver(false);
1541
- const files = collectDropFiles(event);
1542
- if (files.length > 0 && onAttachFiles) {
2074
+ ref: fileInputRef,
2075
+ type: "file",
2076
+ multiple: true,
2077
+ hidden: true,
2078
+ onChange: (event) => {
2079
+ const files = event.target.files ? Array.from(event.target.files) : [];
2080
+ if (files.length > 0) {
1543
2081
  onAttachFiles(files);
1544
2082
  }
1545
- },
2083
+ event.target.value = "";
2084
+ }
2085
+ }
2086
+ ) : null;
2087
+ const popoverNodes = /* @__PURE__ */ jsxs6(Fragment6, { children: [
2088
+ builtinMentionEnabled && mentionState && /* @__PURE__ */ jsx10(
2089
+ AnchoredPopover,
2090
+ {
2091
+ anchorRect: mentionState.anchorRect,
2092
+ onClose: () => setMentionState(null),
2093
+ role: "listbox",
2094
+ children: /* @__PURE__ */ jsx10(
2095
+ ContextSourceList,
2096
+ {
2097
+ sources: mentionSources,
2098
+ query: mentionState.query,
2099
+ callService,
2100
+ cache: sourceCacheRef.current,
2101
+ onSelect: handleSelectContext
2102
+ }
2103
+ )
2104
+ }
2105
+ ),
2106
+ builtinPlusEnabled && plusAnchor && /* @__PURE__ */ jsx10(
2107
+ AnchoredPopover,
2108
+ {
2109
+ anchorRect: plusAnchor,
2110
+ onClose: () => setPlusAnchor(null),
2111
+ role: "listbox",
2112
+ children: /* @__PURE__ */ jsx10(
2113
+ ContextSourceList,
2114
+ {
2115
+ sources: plusSources,
2116
+ query: "",
2117
+ callService,
2118
+ cache: sourceCacheRef.current,
2119
+ onSelect: handleSelectContext
2120
+ }
2121
+ )
2122
+ }
2123
+ )
2124
+ ] });
2125
+ const fallback = /* @__PURE__ */ jsxs6(
2126
+ "div",
2127
+ {
2128
+ className: `pactor-chat-composer${dragOver ? " pactor-chat-composer--dragover" : ""}`,
2129
+ ...dragHandlers,
1546
2130
  children: [
1547
- attachments !== void 0 && attachments.length > 0 && /* @__PURE__ */ jsx7("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1548
- const isImage = attachment.mimeType.startsWith("image/");
1549
- return /* @__PURE__ */ jsxs6(
1550
- "div",
1551
- {
1552
- className: "pactor-chat-composer__attachment",
1553
- "data-attachment-id": attachment.id,
1554
- onClick: () => onAttachmentClick?.(attachment),
1555
- children: [
1556
- isImage && attachment.url ? /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__attachment-thumb", children: /* @__PURE__ */ jsx7("img", { src: attachment.url, alt: attachment.name }) }) : /* @__PURE__ */ jsxs6("span", { className: "pactor-chat-composer__attachment-info", children: [
1557
- /* @__PURE__ */ jsx7(FileText, { size: 14 }),
1558
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1559
- attachment.size !== void 0 && /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1560
- ] }),
1561
- /* @__PURE__ */ jsx7(
1562
- "button",
1563
- {
1564
- type: "button",
1565
- className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1566
- title: "\u79FB\u9664\u9644\u4EF6",
1567
- "aria-label": "\u79FB\u9664\u9644\u4EF6",
1568
- onClick: (event) => {
1569
- event.stopPropagation();
1570
- onAttachmentsChange?.(
1571
- attachments.filter((item) => item.id !== attachment.id)
1572
- );
1573
- },
1574
- children: /* @__PURE__ */ jsx7(X2, { size: 12 })
1575
- }
1576
- )
1577
- ]
1578
- },
1579
- attachment.id
1580
- );
1581
- }) }),
1582
- contextChips !== void 0 && contextChips.length > 0 && /* @__PURE__ */ jsx7("div", { className: "pactor-chat-composer__contexts", children: contextChips.map((chip) => /* @__PURE__ */ jsxs6(
1583
- "span",
2131
+ attachments !== void 0 && /* @__PURE__ */ jsx10(
2132
+ ComposerAttachments,
1584
2133
  {
1585
- className: "pactor-chat-context-chip",
1586
- "data-context-id": chip.id,
1587
- children: [
1588
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1589
- /* @__PURE__ */ jsx7(
1590
- "button",
1591
- {
1592
- type: "button",
1593
- className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1594
- title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1595
- "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1596
- onClick: () => onContextChipsChange?.(
1597
- contextChips.filter(
1598
- (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1599
- )
1600
- ),
1601
- children: /* @__PURE__ */ jsx7(X2, { size: 12 })
1602
- }
2134
+ attachments,
2135
+ onRemove: (id) => onAttachmentsChange?.(attachments.filter((a) => a.id !== id)),
2136
+ onClick: onAttachmentClick
2137
+ }
2138
+ ),
2139
+ contextChips !== void 0 && /* @__PURE__ */ jsx10(
2140
+ ComposerContextChips,
2141
+ {
2142
+ chips: contextChips,
2143
+ onRemove: (chip) => onContextChipsChange?.(
2144
+ contextChips.filter(
2145
+ (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1603
2146
  )
1604
- ]
1605
- },
1606
- `${chip.trigger ?? ""}:${chip.id}`
1607
- )) }),
2147
+ )
2148
+ }
2149
+ ),
1608
2150
  /* @__PURE__ */ jsxs6("div", { className: "pactor-chat-composer__row", children: [
1609
- /* @__PURE__ */ jsx7(
2151
+ /* @__PURE__ */ jsx10(
1610
2152
  "textarea",
1611
2153
  {
1612
2154
  ref: inputRef,
@@ -1623,10 +2165,8 @@ function Composer({
1623
2165
  composingRef.current = false;
1624
2166
  },
1625
2167
  onPaste: (event) => {
1626
- const files = Array.from(event.clipboardData.files);
1627
- if (files.length > 0 && onAttachFiles) {
2168
+ if (handlePasteFiles(event.clipboardData)) {
1628
2169
  event.preventDefault();
1629
- onAttachFiles(files);
1630
2170
  }
1631
2171
  },
1632
2172
  onKeyDown: (event) => {
@@ -1640,7 +2180,7 @@ function Composer({
1640
2180
  }
1641
2181
  }
1642
2182
  ),
1643
- /* @__PURE__ */ jsx7(
2183
+ hideToolbar ? null : /* @__PURE__ */ jsx10(
1644
2184
  "button",
1645
2185
  {
1646
2186
  type: "button",
@@ -1649,14 +2189,14 @@ function Composer({
1649
2189
  "aria-label": streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1650
2190
  disabled: streaming ? false : disabled || !text.trim(),
1651
2191
  onClick: streaming ? onStop : send,
1652
- children: streaming ? /* @__PURE__ */ jsx7(Square, { size: 14, fill: "currentColor" }) : /* @__PURE__ */ jsx7(ArrowUp, { size: 16 })
2192
+ children: streaming ? /* @__PURE__ */ jsx10(Icon6, { name: "square", size: 14, className: "pactor-chat-icon-fill" }) : /* @__PURE__ */ jsx10(Icon6, { name: "arrow-up", size: 16 })
1653
2193
  }
1654
2194
  )
1655
2195
  ] }),
1656
2196
  showToolbar && /* @__PURE__ */ jsxs6("div", { className: "pactor-chat-composer__toolbar", children: [
1657
2197
  toolbarStart,
1658
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__toolbar-spacer" }),
1659
- (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ jsx7(
2198
+ /* @__PURE__ */ jsx10("span", { className: "pactor-chat-composer__toolbar-spacer" }),
2199
+ (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ jsx10(
1660
2200
  "button",
1661
2201
  {
1662
2202
  ref: plusButtonRef,
@@ -1672,10 +2212,10 @@ function Composer({
1672
2212
  setPlusAnchor(rect);
1673
2213
  }
1674
2214
  },
1675
- children: /* @__PURE__ */ jsx7(Plus, { size: 14 })
2215
+ children: /* @__PURE__ */ jsx10(Icon6, { name: "plus", size: 14 })
1676
2216
  }
1677
2217
  ),
1678
- onAttachFiles && /* @__PURE__ */ jsx7(
2218
+ onAttachFiles && /* @__PURE__ */ jsx10(
1679
2219
  "button",
1680
2220
  {
1681
2221
  type: "button",
@@ -1683,11 +2223,11 @@ function Composer({
1683
2223
  title: "\u6DFB\u52A0\u9644\u4EF6",
1684
2224
  "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1685
2225
  onClick: () => fileInputRef.current?.click(),
1686
- children: /* @__PURE__ */ jsx7(Paperclip, { size: 14 })
2226
+ children: /* @__PURE__ */ jsx10(Icon6, { name: "paperclip", size: 14 })
1687
2227
  }
1688
2228
  ),
1689
- modelChipEnabled && /* @__PURE__ */ jsx7(
1690
- ModelChip,
2229
+ modelChipEnabled && /* @__PURE__ */ jsx10(
2230
+ ModelSelect,
1691
2231
  {
1692
2232
  models,
1693
2233
  model,
@@ -1696,111 +2236,206 @@ function Composer({
1696
2236
  ),
1697
2237
  toolbarEnd
1698
2238
  ] }),
1699
- onAttachFiles && /* @__PURE__ */ jsx7(
1700
- "input",
1701
- {
1702
- ref: fileInputRef,
1703
- type: "file",
1704
- multiple: true,
1705
- hidden: true,
1706
- onChange: (event) => {
1707
- const files = event.target.files ? Array.from(event.target.files) : [];
1708
- if (files.length > 0) {
1709
- onAttachFiles(files);
1710
- }
1711
- event.target.value = "";
1712
- }
1713
- }
1714
- ),
1715
- builtinMentionEnabled && mentionState && /* @__PURE__ */ jsx7(
1716
- AnchoredPopover,
1717
- {
1718
- anchorRect: mentionState.anchorRect,
1719
- onClose: () => setMentionState(null),
1720
- role: "listbox",
1721
- children: /* @__PURE__ */ jsx7(
1722
- ContextSourceList,
1723
- {
1724
- sources: mentionSources,
1725
- query: mentionState.query,
1726
- callService,
1727
- cache: sourceCacheRef.current,
1728
- onSelect: handleSelectContext
1729
- }
1730
- )
1731
- }
1732
- ),
1733
- builtinPlusEnabled && plusAnchor && /* @__PURE__ */ jsx7(
1734
- AnchoredPopover,
1735
- {
1736
- anchorRect: plusAnchor,
1737
- onClose: () => setPlusAnchor(null),
1738
- role: "listbox",
1739
- children: /* @__PURE__ */ jsx7(
1740
- ContextSourceList,
1741
- {
1742
- sources: plusSources,
1743
- query: "",
1744
- callService,
1745
- cache: sourceCacheRef.current,
1746
- onSelect: handleSelectContext
1747
- }
1748
- )
1749
- }
1750
- )
2239
+ composerFooter !== void 0 && composerFooter !== null ? /* @__PURE__ */ jsx10("div", { className: "pactor-chat-composer__footer", children: composerFooter }) : null,
2240
+ fileInputNode,
2241
+ popoverNodes
1751
2242
  ]
1752
2243
  }
1753
2244
  );
2245
+ const bridge = {
2246
+ text,
2247
+ streaming,
2248
+ disabled,
2249
+ attachments,
2250
+ contextChips,
2251
+ toolbarStart,
2252
+ toolbarEnd,
2253
+ composerFooter,
2254
+ models,
2255
+ model,
2256
+ showPlus: onPlusMenu !== void 0 || builtinPlusEnabled,
2257
+ showAttach: onAttachFiles !== void 0,
2258
+ modelChipEnabled,
2259
+ send,
2260
+ stop: onStop,
2261
+ openFilePicker: () => fileInputRef.current?.click(),
2262
+ plusClick: (rect) => {
2263
+ if (onPlusMenu) {
2264
+ onPlusMenu(rect);
2265
+ } else {
2266
+ setPlusAnchor(rect);
2267
+ }
2268
+ },
2269
+ removeAttachment: (id) => onAttachmentsChange?.((attachments ?? []).filter((a) => a.id !== id)),
2270
+ attachmentClick: onAttachmentClick,
2271
+ removeChip: (chip) => onContextChipsChange?.(
2272
+ (contextChips ?? []).filter(
2273
+ (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
2274
+ )
2275
+ ),
2276
+ modelChange: onModelChange
2277
+ };
2278
+ return /* @__PURE__ */ jsx10(
2279
+ ChromeSlot,
2280
+ {
2281
+ fragment: buildComposerFragment({
2282
+ placeholder,
2283
+ disabled,
2284
+ hasFooter: composerFooter !== void 0 && composerFooter !== null,
2285
+ hideToolbar
2286
+ }),
2287
+ components: COMPOSER_INTERNAL_COMPONENTS,
2288
+ fallback,
2289
+ wrap: (children) => /* @__PURE__ */ jsx10(ComposerBridgeContext.Provider, { value: bridge, children: /* @__PURE__ */ jsxs6(
2290
+ "div",
2291
+ {
2292
+ className: `pactor-chat-composer pactor-chat-composer--dsl${dragOver ? " pactor-chat-composer--dragover" : ""}`,
2293
+ ...dragHandlers,
2294
+ onChange: handleBubbleChange,
2295
+ onPaste: (event) => {
2296
+ if (handlePasteFiles(event.clipboardData)) {
2297
+ event.preventDefault();
2298
+ }
2299
+ },
2300
+ children: [
2301
+ children,
2302
+ fileInputNode,
2303
+ popoverNodes
2304
+ ]
2305
+ }
2306
+ ) })
2307
+ }
2308
+ );
1754
2309
  }
1755
2310
 
1756
- // src/view/EmptyState.tsx
1757
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1758
- var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
2311
+ // src/view/chrome/empty-state.tsx
1759
2312
  function suggestionLabel(suggestion) {
1760
2313
  return typeof suggestion === "string" ? suggestion : suggestion.label;
1761
2314
  }
1762
2315
  function suggestionPrompt(suggestion) {
1763
2316
  return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
1764
2317
  }
1765
- function EmptyState({
1766
- greeting = DEFAULT_CHAT_GREETING,
2318
+ function buildEmptyStateFragment(props) {
2319
+ const children = [
2320
+ {
2321
+ type: "Typography",
2322
+ props: {
2323
+ variant: "h3",
2324
+ text: props.greeting,
2325
+ className: "pactor-chat-empty__greeting"
2326
+ }
2327
+ }
2328
+ ];
2329
+ if (props.subtitle) {
2330
+ children.push({
2331
+ type: "Text",
2332
+ props: {
2333
+ type: "secondary",
2334
+ text: props.subtitle,
2335
+ className: "pactor-chat-empty__subtitle"
2336
+ }
2337
+ });
2338
+ }
2339
+ if (props.suggestions && props.suggestions.length > 0) {
2340
+ children.push({
2341
+ type: "Flex",
2342
+ props: {
2343
+ gap: "small",
2344
+ justify: "center",
2345
+ className: "pactor-chat-empty__suggestions"
2346
+ },
2347
+ children: props.suggestions.map((suggestion) => ({
2348
+ type: "Button",
2349
+ props: {
2350
+ text: suggestionLabel(suggestion),
2351
+ variant: "default",
2352
+ size: "middle",
2353
+ className: "pactor-chat-empty__suggestion"
2354
+ },
2355
+ events: {
2356
+ onClick: [
2357
+ {
2358
+ action: "chat.sendSuggestion",
2359
+ params: { text: suggestionPrompt(suggestion) }
2360
+ }
2361
+ ]
2362
+ }
2363
+ }))
2364
+ });
2365
+ }
2366
+ return {
2367
+ requires: ["Flex", "Typography", "Text", "Button"],
2368
+ node: {
2369
+ type: "Flex",
2370
+ props: { direction: "vertical", align: "center", gap: "middle" },
2371
+ children
2372
+ }
2373
+ };
2374
+ }
2375
+
2376
+ // src/view/EmptyState.tsx
2377
+ import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2378
+ var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
2379
+ function suggestionLabel2(suggestion) {
2380
+ return typeof suggestion === "string" ? suggestion : suggestion.label;
2381
+ }
2382
+ function suggestionPrompt2(suggestion) {
2383
+ return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
2384
+ }
2385
+ function EmptyStateFallback({
1767
2386
  subtitle,
1768
2387
  suggestions,
1769
- onSuggestionClick
2388
+ onSuggestionClick,
2389
+ greeting
1770
2390
  }) {
1771
- return /* @__PURE__ */ jsxs7("div", { className: "pactor-chat-empty", children: [
1772
- /* @__PURE__ */ jsx8("div", { className: "pactor-chat-empty__greeting", children: greeting }),
1773
- subtitle && /* @__PURE__ */ jsx8("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
1774
- suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx8("div", { className: "pactor-chat-empty__suggestions", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx8(
2391
+ return /* @__PURE__ */ jsxs7(Fragment7, { children: [
2392
+ /* @__PURE__ */ jsx11("div", { className: "pactor-chat-empty__greeting", children: greeting }),
2393
+ subtitle && /* @__PURE__ */ jsx11("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
2394
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx11("div", { className: "pactor-chat-empty__suggestions", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx11(
1775
2395
  "button",
1776
2396
  {
1777
2397
  type: "button",
1778
2398
  className: "pactor-chat-empty__suggestion",
1779
- onClick: () => onSuggestionClick?.(suggestionPrompt(suggestion)),
1780
- children: suggestionLabel(suggestion)
2399
+ onClick: () => onSuggestionClick?.(suggestionPrompt2(suggestion)),
2400
+ children: suggestionLabel2(suggestion)
1781
2401
  },
1782
- suggestionLabel(suggestion)
2402
+ suggestionLabel2(suggestion)
1783
2403
  )) })
1784
2404
  ] });
1785
2405
  }
2406
+ function EmptyState({
2407
+ greeting = DEFAULT_CHAT_GREETING,
2408
+ subtitle,
2409
+ suggestions,
2410
+ onSuggestionClick
2411
+ }) {
2412
+ return /* @__PURE__ */ jsx11("div", { className: "pactor-chat-empty", children: /* @__PURE__ */ jsx11(
2413
+ ChromeSlot,
2414
+ {
2415
+ fragment: buildEmptyStateFragment({ greeting, subtitle, suggestions }),
2416
+ fallback: /* @__PURE__ */ jsx11(
2417
+ EmptyStateFallback,
2418
+ {
2419
+ greeting,
2420
+ subtitle,
2421
+ suggestions,
2422
+ onSuggestionClick
2423
+ }
2424
+ )
2425
+ }
2426
+ ) });
2427
+ }
1786
2428
 
1787
2429
  // src/view/MessageBubble.tsx
1788
- import { useRef as useRef8, useState as useState12 } from "react";
2430
+ import { useRef as useRef9, useState as useState11 } from "react";
1789
2431
 
1790
2432
  // src/view/ActivityTrace.tsx
1791
- import {
1792
- Brain,
1793
- CheckCircle2,
1794
- ChevronRight,
1795
- Clock,
1796
- Loader2,
1797
- XCircle
1798
- } from "lucide-react";
2433
+ import { Icon as Icon7 } from "@pactor-app/ui/components";
1799
2434
  import { MarkdownRenderer as MarkdownRenderer3 } from "@pactor-app/markdown";
1800
- import { useEffect as useEffect5, useRef as useRef7, useState as useState10 } from "react";
1801
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2435
+ import { useEffect as useEffect6, useRef as useRef7, useState as useState9 } from "react";
2436
+ import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1802
2437
  function usePinnedCollapse(autoOpen) {
1803
- const [state, setState] = useState10("auto");
2438
+ const [state, setState] = useState9("auto");
1804
2439
  const open = state === "auto" ? autoOpen : state === "pinned-open";
1805
2440
  const toggle = () => {
1806
2441
  setState(open ? "pinned-closed" : "pinned-open");
@@ -1826,8 +2461,8 @@ function statusText(message, mode, toolCount) {
1826
2461
  }
1827
2462
  function useElapsedSeconds(startedAt, active) {
1828
2463
  const compute = () => Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1829
- const [elapsed, setElapsed] = useState10(compute);
1830
- useEffect5(() => {
2464
+ const [elapsed, setElapsed] = useState9(compute);
2465
+ useEffect6(() => {
1831
2466
  if (!active) {
1832
2467
  setElapsed(compute());
1833
2468
  return;
@@ -1841,15 +2476,15 @@ function useElapsedSeconds(startedAt, active) {
1841
2476
  }
1842
2477
  function TraceStatusIcon({ status }) {
1843
2478
  if (status === "running") {
1844
- return /* @__PURE__ */ jsx9(Loader2, { size: 13, className: "pactor-chat-spin" });
2479
+ return /* @__PURE__ */ jsx12(Icon7, { name: "loading", size: 13, className: "pactor-chat-spin" });
1845
2480
  }
1846
2481
  if (status === "success") {
1847
- return /* @__PURE__ */ jsx9(CheckCircle2, { size: 13 });
2482
+ return /* @__PURE__ */ jsx12(Icon7, { name: "check-circle", size: 13 });
1848
2483
  }
1849
2484
  if (status === "failed") {
1850
- return /* @__PURE__ */ jsx9(XCircle, { size: 13 });
2485
+ return /* @__PURE__ */ jsx12(Icon7, { name: "x-circle", size: 13 });
1851
2486
  }
1852
- return /* @__PURE__ */ jsx9(Clock, { size: 13 });
2487
+ return /* @__PURE__ */ jsx12(Icon7, { name: "clock", size: 13 });
1853
2488
  }
1854
2489
  function summarizeArgs(args) {
1855
2490
  if (args === void 0) {
@@ -1878,7 +2513,7 @@ function formatDetail(value) {
1878
2513
  }
1879
2514
  }
1880
2515
  function TraceRow({ entry }) {
1881
- const [open, setOpen] = useState10(false);
2516
+ const [open, setOpen] = useState9(false);
1882
2517
  const summary = summarizeArgs(entry.args);
1883
2518
  return /* @__PURE__ */ jsxs8(
1884
2519
  "div",
@@ -1895,16 +2530,16 @@ function TraceRow({ entry }) {
1895
2530
  "aria-expanded": open,
1896
2531
  onClick: () => setOpen((value) => !value),
1897
2532
  children: [
1898
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx9(ChevronRight, { size: 12 }) }),
1899
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ jsx9(TraceStatusIcon, { status: entry.status }) }),
1900
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
1901
- summary && /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__entry-args", children: summary })
2533
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx12(Icon7, { name: "chevron-right", size: 12 }) }),
2534
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ jsx12(TraceStatusIcon, { status: entry.status }) }),
2535
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
2536
+ summary && /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__entry-args", children: summary })
1902
2537
  ]
1903
2538
  }
1904
2539
  ),
1905
2540
  open && /* @__PURE__ */ jsxs8("div", { className: "pactor-chat-trace__entry-body", children: [
1906
- entry.args !== void 0 && /* @__PURE__ */ jsx9("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
1907
- /* @__PURE__ */ jsx9("pre", { className: "pactor-chat-trace__entry-detail", children: entry.status === "failed" ? entry.error ?? "\u6267\u884C\u5931\u8D25" : formatDetail(entry.result) })
2541
+ entry.args !== void 0 && /* @__PURE__ */ jsx12("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
2542
+ /* @__PURE__ */ jsx12("pre", { className: "pactor-chat-trace__entry-detail", children: entry.status === "failed" ? entry.error ?? "\u6267\u884C\u5931\u8D25" : formatDetail(entry.result) })
1908
2543
  ] })
1909
2544
  ]
1910
2545
  }
@@ -1931,13 +2566,13 @@ function ThinkingCard({
1931
2566
  "aria-expanded": open,
1932
2567
  onClick: toggle,
1933
2568
  children: [
1934
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx9(ChevronRight, { size: 12 }) }),
1935
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ jsx9(Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ jsx9(Brain, { size: 13 }) }),
1936
- /* @__PURE__ */ jsx9("span", { children: "\u601D\u8003\u8FC7\u7A0B" })
2569
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx12(Icon7, { name: "chevron-right", size: 12 }) }),
2570
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ jsx12(Icon7, { name: "loading", size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ jsx12(Icon7, { name: "brain", size: 13 }) }),
2571
+ /* @__PURE__ */ jsx12("span", { children: "\u601D\u8003\u8FC7\u7A0B" })
1937
2572
  ]
1938
2573
  }
1939
2574
  ),
1940
- open && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-thinking__body", children: /* @__PURE__ */ jsx9(MarkdownRenderer3, { content: part.content }) })
2575
+ open && /* @__PURE__ */ jsx12("div", { className: "pactor-chat-thinking__body", children: /* @__PURE__ */ jsx12(MarkdownRenderer3, { content: part.content }) })
1941
2576
  ]
1942
2577
  }
1943
2578
  );
@@ -1959,9 +2594,9 @@ function ActivityTrace({ message }) {
1959
2594
  message.createdAt ?? mountTimeRef.current,
1960
2595
  streaming
1961
2596
  );
1962
- const header = /* @__PURE__ */ jsxs8(Fragment5, { children: [
1963
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__status-icon", children: streaming ? /* @__PURE__ */ jsx9(Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ jsx9(CheckCircle2, { size: 13 }) }),
1964
- /* @__PURE__ */ jsx9(
2597
+ const header = /* @__PURE__ */ jsxs8(Fragment8, { children: [
2598
+ /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__status-icon", children: streaming ? /* @__PURE__ */ jsx12(Icon7, { name: "loading", size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ jsx12(Icon7, { name: "check-circle", size: 13 }) }),
2599
+ /* @__PURE__ */ jsx12(
1965
2600
  "span",
1966
2601
  {
1967
2602
  className: `pactor-chat-trace__status-text${streaming ? " pactor-chat-trace__status-text--breathing" : ""}`,
@@ -1972,10 +2607,10 @@ function ActivityTrace({ message }) {
1972
2607
  elapsed,
1973
2608
  "s"
1974
2609
  ] }),
1975
- entries.length > 0 && /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx9(ChevronRight, { size: 12 }) })
2610
+ entries.length > 0 && /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx12(Icon7, { name: "chevron-right", size: 12 }) })
1976
2611
  ] });
1977
2612
  return /* @__PURE__ */ jsxs8("div", { className: "pactor-chat-trace", "data-open": open, children: [
1978
- entries.length > 0 ? /* @__PURE__ */ jsx9(
2613
+ entries.length > 0 ? /* @__PURE__ */ jsx12(
1979
2614
  "button",
1980
2615
  {
1981
2616
  type: "button",
@@ -1984,8 +2619,8 @@ function ActivityTrace({ message }) {
1984
2619
  onClick: toggle,
1985
2620
  children: header
1986
2621
  }
1987
- ) : /* @__PURE__ */ jsx9("div", { className: "pactor-chat-trace__header", children: header }),
1988
- thinkingParts.map((part, index) => /* @__PURE__ */ jsx9(
2622
+ ) : /* @__PURE__ */ jsx12("div", { className: "pactor-chat-trace__header", children: header }),
2623
+ thinkingParts.map((part, index) => /* @__PURE__ */ jsx12(
1989
2624
  ThinkingCard,
1990
2625
  {
1991
2626
  part,
@@ -1993,36 +2628,66 @@ function ActivityTrace({ message }) {
1993
2628
  },
1994
2629
  part.id ?? index
1995
2630
  )),
1996
- open && entries.length > 0 && /* @__PURE__ */ jsx9("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ jsx9(TraceRow, { entry }, entry.callIndex)) })
2631
+ open && entries.length > 0 && /* @__PURE__ */ jsx12("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ jsx12(TraceRow, { entry }, entry.callIndex)) })
1997
2632
  ] });
1998
2633
  }
1999
2634
 
2000
2635
  // src/view/MessageActions.tsx
2001
- import {
2002
- Bookmark,
2003
- Check as Check2,
2004
- Copy,
2005
- HelpCircle,
2006
- Pencil,
2007
- Quote,
2008
- RotateCcw,
2009
- Share2,
2010
- ThumbsDown,
2011
- ThumbsUp
2012
- } from "lucide-react";
2013
- import { useState as useState11 } from "react";
2014
- import { jsx as jsx10 } from "react/jsx-runtime";
2015
- var MESSAGE_ACTION_ICONS = {
2016
- copy: Copy,
2017
- check: Check2,
2018
- "rotate-ccw": RotateCcw,
2019
- pencil: Pencil,
2020
- quote: Quote,
2021
- "thumbs-up": ThumbsUp,
2022
- "thumbs-down": ThumbsDown,
2023
- share: Share2,
2024
- bookmark: Bookmark
2025
- };
2636
+ import { Icon as Icon8 } from "@pactor-app/ui/components";
2637
+ import { useEffect as useEffect7, useRef as useRef8, useState as useState10 } from "react";
2638
+
2639
+ // src/view/chrome/message-actions.tsx
2640
+ function buildMessageActionsFragment(args) {
2641
+ const { actions, copied, token } = args;
2642
+ return {
2643
+ requires: ["Flex", "Tooltip", "Button", "Icon"],
2644
+ node: {
2645
+ type: "Flex",
2646
+ props: { gap: "small" },
2647
+ children: actions.map((action) => {
2648
+ const copiedFeedback = action.builtin === "copy" && copied;
2649
+ const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2650
+ const iconName = copiedFeedback ? "check" : action.icon;
2651
+ return {
2652
+ type: "Tooltip",
2653
+ props: { content: label },
2654
+ children: [
2655
+ {
2656
+ type: "Button",
2657
+ props: {
2658
+ variant: "text",
2659
+ size: "small",
2660
+ className: "pactor-chat-icon-button"
2661
+ },
2662
+ events: {
2663
+ onClick: [
2664
+ {
2665
+ action: "chat.messageAction",
2666
+ params: { token, actionId: action.id }
2667
+ }
2668
+ ]
2669
+ },
2670
+ children: [
2671
+ {
2672
+ type: "Icon",
2673
+ props: {
2674
+ // 契约图标名直通 ui Icon 白名单(spec: chat-icon-convergence;
2675
+ // 未知名由 ui Icon 渲染占位)
2676
+ name: iconName ?? "help-circle",
2677
+ size: 14
2678
+ }
2679
+ }
2680
+ ]
2681
+ }
2682
+ ]
2683
+ };
2684
+ })
2685
+ }
2686
+ };
2687
+ }
2688
+
2689
+ // src/view/MessageActions.tsx
2690
+ import { jsx as jsx13 } from "react/jsx-runtime";
2026
2691
  var BUILTIN_ACTION_DEFAULTS = {
2027
2692
  copy: {
2028
2693
  label: "\u590D\u5236",
@@ -2095,7 +2760,21 @@ function MessageActions({
2095
2760
  callService,
2096
2761
  onMessageAction
2097
2762
  }) {
2098
- const [copied, setCopied] = useState11(false);
2763
+ const [copied, setCopied] = useState10(false);
2764
+ const dispatchRef = useRef8(
2765
+ () => {
2766
+ }
2767
+ );
2768
+ const visibleActionsRef = useRef8([]);
2769
+ useEffect7(() => {
2770
+ setMessageActionDispatcher(message.id, (actionId) => {
2771
+ const action = visibleActionsRef.current.find((a) => a.id === actionId);
2772
+ if (action) {
2773
+ dispatchRef.current(action);
2774
+ }
2775
+ });
2776
+ return () => clearMessageActionDispatcher(message.id);
2777
+ }, [message.id]);
2099
2778
  const copy = async () => {
2100
2779
  try {
2101
2780
  await navigator.clipboard.writeText(collectMessageText(message));
@@ -2155,41 +2834,53 @@ function MessageActions({
2155
2834
  }
2156
2835
  onMessageAction?.(action.id, message);
2157
2836
  };
2158
- return /* @__PURE__ */ jsx10(
2837
+ dispatchRef.current = dispatch;
2838
+ visibleActionsRef.current = visibleActions;
2839
+ const buttons = visibleActions.map((action) => {
2840
+ const copiedFeedback = action.builtin === "copy" && copied;
2841
+ const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2842
+ const iconName = copiedFeedback ? "check" : action.icon;
2843
+ return /* @__PURE__ */ jsx13(
2844
+ "button",
2845
+ {
2846
+ type: "button",
2847
+ className: "pactor-chat-icon-button",
2848
+ title: label,
2849
+ "aria-label": label,
2850
+ "data-message-action": action.id,
2851
+ onClick: () => dispatch(action),
2852
+ children: iconName !== void 0 ? /* @__PURE__ */ jsx13(Icon8, { name: iconName, size: 14 }) : /* @__PURE__ */ jsx13(Icon8, { name: "help-circle", size: 14 })
2853
+ },
2854
+ action.id
2855
+ );
2856
+ });
2857
+ return /* @__PURE__ */ jsx13(
2159
2858
  "div",
2160
2859
  {
2161
2860
  className: `pactor-chat-message__actions${forceVisible ? " pactor-chat-message__actions--visible" : ""}`,
2162
- children: visibleActions.map((action) => {
2163
- const copiedFeedback = action.builtin === "copy" && copied;
2164
- const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2165
- const iconName = copiedFeedback ? "check" : action.icon;
2166
- const Icon = iconName !== void 0 ? MESSAGE_ACTION_ICONS[iconName] : void 0;
2167
- return /* @__PURE__ */ jsx10(
2168
- "button",
2169
- {
2170
- type: "button",
2171
- className: "pactor-chat-icon-button",
2172
- title: label,
2173
- "aria-label": label,
2174
- "data-message-action": action.id,
2175
- onClick: () => dispatch(action),
2176
- children: Icon ? /* @__PURE__ */ jsx10(Icon, { size: 14 }) : /* @__PURE__ */ jsx10(HelpCircle, { size: 14, "data-icon-placeholder": "true" })
2177
- },
2178
- action.id
2179
- );
2180
- })
2861
+ children: /* @__PURE__ */ jsx13(
2862
+ ChromeSlot,
2863
+ {
2864
+ fragment: buildMessageActionsFragment({
2865
+ actions: visibleActions,
2866
+ copied,
2867
+ token: message.id
2868
+ }),
2869
+ fallback: buttons
2870
+ }
2871
+ )
2181
2872
  }
2182
2873
  );
2183
2874
  }
2184
2875
 
2185
2876
  // src/view/MessageBubble.tsx
2186
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2877
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2187
2878
  function MessageEditForm({
2188
2879
  initialText,
2189
2880
  onConfirm,
2190
2881
  onCancel
2191
2882
  }) {
2192
- const [text, setText] = useState12(initialText);
2883
+ const [text, setText] = useState11(initialText);
2193
2884
  const confirm = () => {
2194
2885
  const value = text.trim();
2195
2886
  if (!value) {
@@ -2198,7 +2889,7 @@ function MessageEditForm({
2198
2889
  onConfirm(value);
2199
2890
  };
2200
2891
  return /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__edit", children: [
2201
- /* @__PURE__ */ jsx11(
2892
+ /* @__PURE__ */ jsx14(
2202
2893
  "textarea",
2203
2894
  {
2204
2895
  className: "pactor-chat-message__edit-input",
@@ -2217,7 +2908,7 @@ function MessageEditForm({
2217
2908
  }
2218
2909
  ),
2219
2910
  /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__edit-actions", children: [
2220
- /* @__PURE__ */ jsx11(
2911
+ /* @__PURE__ */ jsx14(
2221
2912
  "button",
2222
2913
  {
2223
2914
  type: "button",
@@ -2227,7 +2918,7 @@ function MessageEditForm({
2227
2918
  children: "\u53D1\u9001"
2228
2919
  }
2229
2920
  ),
2230
- /* @__PURE__ */ jsx11(
2921
+ /* @__PURE__ */ jsx14(
2231
2922
  "button",
2232
2923
  {
2233
2924
  type: "button",
@@ -2253,7 +2944,7 @@ function MessageBubble({
2253
2944
  onMessageAction,
2254
2945
  onOpenArtifact
2255
2946
  }) {
2256
- const [editing, setEditing] = useState12(false);
2947
+ const [editing, setEditing] = useState11(false);
2257
2948
  const bubbleScope = {
2258
2949
  conversation: scope?.conversation ?? {},
2259
2950
  message: { id: message.id },
@@ -2261,7 +2952,7 @@ function MessageBubble({
2261
2952
  };
2262
2953
  const streaming = message.status === "streaming";
2263
2954
  const live = message.status === "pending" || message.status === "streaming";
2264
- const wasLiveRef = useRef8(live);
2955
+ const wasLiveRef = useRef9(live);
2265
2956
  if (live) {
2266
2957
  wasLiveRef.current = true;
2267
2958
  }
@@ -2275,7 +2966,7 @@ function MessageBubble({
2275
2966
  "data-message-id": message.id,
2276
2967
  "data-message-status": message.status,
2277
2968
  children: [
2278
- editing ? /* @__PURE__ */ jsx11(
2969
+ editing ? /* @__PURE__ */ jsx14(
2279
2970
  MessageEditForm,
2280
2971
  {
2281
2972
  initialText: collectMessageText(message),
@@ -2286,8 +2977,8 @@ function MessageBubble({
2286
2977
  onCancel: () => setEditing(false)
2287
2978
  }
2288
2979
  ) : /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__body", children: [
2289
- showTrace && /* @__PURE__ */ jsx11(ActivityTrace, { message }),
2290
- message.parts.length === 0 && message.status === "pending" && message.role !== "assistant" ? /* @__PURE__ */ jsx11("span", { className: "pactor-chat-message__pending", children: "\u2026" }) : message.parts.map((part, index) => /* @__PURE__ */ jsx11(
2980
+ showTrace && /* @__PURE__ */ jsx14(ActivityTrace, { message }),
2981
+ message.parts.length === 0 && message.status === "pending" && message.role !== "assistant" ? /* @__PURE__ */ jsx14("span", { className: "pactor-chat-message__pending", children: "\u2026" }) : message.parts.map((part, index) => /* @__PURE__ */ jsx14(
2291
2982
  PartRenderer,
2292
2983
  {
2293
2984
  part,
@@ -2299,7 +2990,7 @@ function MessageBubble({
2299
2990
  part.id ?? index
2300
2991
  ))
2301
2992
  ] }),
2302
- !editing && /* @__PURE__ */ jsx11(
2993
+ !editing && /* @__PURE__ */ jsx14(
2303
2994
  MessageActions,
2304
2995
  {
2305
2996
  message,
@@ -2319,7 +3010,7 @@ function MessageBubble({
2319
3010
  }
2320
3011
 
2321
3012
  // src/view/ChatView.tsx
2322
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3013
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2323
3014
  function lastContentLength(messages) {
2324
3015
  const last = messages[messages.length - 1];
2325
3016
  if (!last) {
@@ -2353,6 +3044,8 @@ function ChatView({
2353
3044
  onAttachmentClick,
2354
3045
  toolbarStart,
2355
3046
  toolbarEnd,
3047
+ composerFooter,
3048
+ hideToolbar,
2356
3049
  onMentionTrigger,
2357
3050
  onPlusMenu,
2358
3051
  models,
@@ -2363,7 +3056,8 @@ function ChatView({
2363
3056
  onContextsChange,
2364
3057
  callService,
2365
3058
  messageActions,
2366
- onMessageAction
3059
+ onMessageAction,
3060
+ composerBottom
2367
3061
  }) {
2368
3062
  const messages = useSyncExternalStore(
2369
3063
  (listener) => store.subscribe(listener),
@@ -2393,8 +3087,8 @@ function ChatView({
2393
3087
  () => ({ conversation: { id: conversationId }, params }),
2394
3088
  [conversationId, params]
2395
3089
  );
2396
- const notifiedArtifactsRef = useRef9(/* @__PURE__ */ new Set());
2397
- useEffect6(() => {
3090
+ const notifiedArtifactsRef = useRef10(/* @__PURE__ */ new Set());
3091
+ useEffect8(() => {
2398
3092
  if (!onArtifact) {
2399
3093
  return;
2400
3094
  }
@@ -2413,7 +3107,7 @@ function ChatView({
2413
3107
  });
2414
3108
  }
2415
3109
  }, [messages, onArtifact]);
2416
- const abortRef = useRef9(null);
3110
+ const abortRef = useRef10(null);
2417
3111
  const { containerRef, isFollowing, scrollToBottom } = useChatAutoScroll({
2418
3112
  messageCount: messages.length,
2419
3113
  lastContentLength: lastContentLength(messages),
@@ -2424,7 +3118,7 @@ function ChatView({
2424
3118
  model,
2425
3119
  onModelChange
2426
3120
  });
2427
- const [contextChips, setContextChips] = useState13([]);
3121
+ const [contextChips, setContextChips] = useState12([]);
2428
3122
  const handleContextChipsChange = (chips) => {
2429
3123
  setContextChips(chips);
2430
3124
  onContextsChange?.(chips);
@@ -2456,6 +3150,16 @@ function ChatView({
2456
3150
  abortRef.current?.abort();
2457
3151
  abortRef.current = null;
2458
3152
  };
3153
+ const handleSendRef = useRef10(handleSend);
3154
+ handleSendRef.current = handleSend;
3155
+ const onOpenArtifactRef = useRef10(onOpenArtifact);
3156
+ onOpenArtifactRef.current = onOpenArtifact;
3157
+ useEffect8(() => {
3158
+ setChatChromeHandlers({
3159
+ sendSuggestion: (text) => handleSendRef.current(text),
3160
+ openArtifact: (artifact) => onOpenArtifactRef.current?.(artifact)
3161
+ });
3162
+ }, []);
2459
3163
  const handleRegenerate = () => {
2460
3164
  if (!transport) {
2461
3165
  return;
@@ -2473,7 +3177,7 @@ function ChatView({
2473
3177
  const lastMessage = messages[messages.length - 1];
2474
3178
  const regenerableMessageId = lastMessage?.role === "assistant" && lastMessage.status === "completed" ? lastMessage.id : void 0;
2475
3179
  const isEmpty = messages.length === 0;
2476
- const composerNode = /* @__PURE__ */ jsx12(
3180
+ const composerNode = /* @__PURE__ */ jsx15(
2477
3181
  Composer,
2478
3182
  {
2479
3183
  onSend: handleSend,
@@ -2485,6 +3189,8 @@ function ChatView({
2485
3189
  onAttachmentClick,
2486
3190
  toolbarStart,
2487
3191
  toolbarEnd,
3192
+ composerFooter,
3193
+ hideToolbar,
2488
3194
  onMentionTrigger,
2489
3195
  onPlusMenu,
2490
3196
  models: modelPlacement === "header" ? void 0 : models,
@@ -2496,6 +3202,10 @@ function ChatView({
2496
3202
  callService
2497
3203
  }
2498
3204
  );
3205
+ const composerDock = /* @__PURE__ */ jsxs10("div", { className: "pactor-chat-view__composer-dock", children: [
3206
+ composerNode,
3207
+ composerBottom !== void 0 && composerBottom !== null ? /* @__PURE__ */ jsx15("div", { className: "pactor-chat-view__composer-bottom", children: composerBottom }) : null
3208
+ ] });
2499
3209
  return /* @__PURE__ */ jsxs10(
2500
3210
  "div",
2501
3211
  {
@@ -2503,7 +3213,7 @@ function ChatView({
2503
3213
  children: [
2504
3214
  /* @__PURE__ */ jsxs10("div", { className: "pactor-chat-messages", ref: containerRef, children: [
2505
3215
  isEmpty ? /* @__PURE__ */ jsxs10("div", { className: "pactor-chat-view__empty-stack", children: [
2506
- /* @__PURE__ */ jsx12(
3216
+ /* @__PURE__ */ jsx15(
2507
3217
  EmptyState,
2508
3218
  {
2509
3219
  greeting,
@@ -2512,8 +3222,8 @@ function ChatView({
2512
3222
  onSuggestionClick: handleSend
2513
3223
  }
2514
3224
  ),
2515
- composerNode
2516
- ] }) : messages.map((message) => /* @__PURE__ */ jsx12(
3225
+ composerDock
3226
+ ] }) : messages.map((message) => /* @__PURE__ */ jsx15(
2517
3227
  MessageBubble,
2518
3228
  {
2519
3229
  message,
@@ -2537,21 +3247,21 @@ function ChatView({
2537
3247
  className: "pactor-chat-scroll-bottom",
2538
3248
  onClick: scrollToBottom,
2539
3249
  children: [
2540
- /* @__PURE__ */ jsx12(ArrowDown, { size: 13 }),
3250
+ /* @__PURE__ */ jsx15(Icon9, { name: "arrow-down", size: 13 }),
2541
3251
  "\u56DE\u5230\u5E95\u90E8"
2542
3252
  ]
2543
3253
  }
2544
3254
  )
2545
3255
  ] }),
2546
- !isEmpty && composerNode
3256
+ !isEmpty && composerDock
2547
3257
  ]
2548
3258
  }
2549
3259
  );
2550
3260
  }
2551
3261
 
2552
3262
  // src/view/WorkspaceHeader.tsx
2553
- import { Download, Moon, Sun, Trash2 } from "lucide-react";
2554
- import { useRef as useRef10, useState as useState14 } from "react";
3263
+ import { Icon as Icon10 } from "@pactor-app/ui/components";
3264
+ import { useEffect as useEffect9, useRef as useRef11, useState as useState13 } from "react";
2555
3265
 
2556
3266
  // src/export-markdown.ts
2557
3267
  var ROLE_LABELS = {
@@ -2583,8 +3293,52 @@ function downloadMessagesAsMarkdown(title, messages) {
2583
3293
  URL.revokeObjectURL(url);
2584
3294
  }
2585
3295
 
3296
+ // src/view/chrome/header-actions.tsx
3297
+ function buildHeaderActionsFragment(props) {
3298
+ const meta = {
3299
+ export: { label: "\u5BFC\u51FA Markdown", icon: "download" },
3300
+ theme: {
3301
+ label: props.theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
3302
+ icon: props.theme === "dark" ? "sun" : "moon"
3303
+ },
3304
+ clear: { label: "\u6E05\u7A7A\u4F1A\u8BDD", icon: "trash2" }
3305
+ };
3306
+ return {
3307
+ requires: ["Flex", "Tooltip", "Button", "Icon"],
3308
+ node: {
3309
+ type: "Flex",
3310
+ props: { gap: "small", align: "center" },
3311
+ children: props.actions.map((action) => ({
3312
+ type: "Tooltip",
3313
+ props: { content: meta[action].label },
3314
+ children: [
3315
+ {
3316
+ type: "Button",
3317
+ props: {
3318
+ variant: "text",
3319
+ size: "small",
3320
+ className: "pactor-chat-icon-button"
3321
+ },
3322
+ events: {
3323
+ onClick: [
3324
+ { action: "chat.headerAction", params: { action } }
3325
+ ]
3326
+ },
3327
+ children: [
3328
+ {
3329
+ type: "Icon",
3330
+ props: { name: meta[action].icon, size: 15 }
3331
+ }
3332
+ ]
3333
+ }
3334
+ ]
3335
+ }))
3336
+ }
3337
+ };
3338
+ }
3339
+
2586
3340
  // src/view/WorkspaceHeader.tsx
2587
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
3341
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
2588
3342
  function readCurrentTheme() {
2589
3343
  if (typeof document === "undefined") {
2590
3344
  return "light";
@@ -2604,10 +3358,10 @@ function WorkspaceHeader({
2604
3358
  onTitleChange
2605
3359
  }) {
2606
3360
  const hasActions = headerActions !== void 0 && headerActions.length > 0;
2607
- const [overrideTitle, setOverrideTitle] = useState14(null);
2608
- const [editing, setEditing] = useState14(false);
2609
- const [draft, setDraft] = useState14("");
2610
- const prevTitleRef = useRef10(title);
3361
+ const [overrideTitle, setOverrideTitle] = useState13(null);
3362
+ const [editing, setEditing] = useState13(false);
3363
+ const [draft, setDraft] = useState13("");
3364
+ const prevTitleRef = useRef11(title);
2611
3365
  if (prevTitleRef.current !== title) {
2612
3366
  prevTitleRef.current = title;
2613
3367
  if (overrideTitle !== null) {
@@ -2615,7 +3369,7 @@ function WorkspaceHeader({
2615
3369
  }
2616
3370
  }
2617
3371
  const displayTitle = overrideTitle ?? title;
2618
- const [theme, setTheme] = useState14(readCurrentTheme);
3372
+ const [theme, setTheme] = useState13(readCurrentTheme);
2619
3373
  const commitTitle = () => {
2620
3374
  setEditing(false);
2621
3375
  const next = draft.trim();
@@ -2642,8 +3396,24 @@ function WorkspaceHeader({
2642
3396
  store.clear();
2643
3397
  onClear?.();
2644
3398
  };
3399
+ const handlersRef = useRef11({ handleExport, handleTheme, handleClear });
3400
+ handlersRef.current = { handleExport, handleTheme, handleClear };
3401
+ useEffect9(() => {
3402
+ setChatChromeHandlers({
3403
+ headerAction: (name) => {
3404
+ const handlers2 = handlersRef.current;
3405
+ if (name === "export") {
3406
+ handlers2.handleExport();
3407
+ } else if (name === "theme") {
3408
+ handlers2.handleTheme();
3409
+ } else if (name === "clear") {
3410
+ handlers2.handleClear();
3411
+ }
3412
+ }
3413
+ });
3414
+ }, []);
2645
3415
  const actionButtons = {
2646
- export: /* @__PURE__ */ jsx13(
3416
+ export: /* @__PURE__ */ jsx16(
2647
3417
  "button",
2648
3418
  {
2649
3419
  type: "button",
@@ -2651,11 +3421,11 @@ function WorkspaceHeader({
2651
3421
  title: "\u5BFC\u51FA Markdown",
2652
3422
  "aria-label": "\u5BFC\u51FA Markdown",
2653
3423
  onClick: handleExport,
2654
- children: /* @__PURE__ */ jsx13(Download, { size: 15 })
3424
+ children: /* @__PURE__ */ jsx16(Icon10, { name: "download", size: 15 })
2655
3425
  },
2656
3426
  "export"
2657
3427
  ),
2658
- theme: /* @__PURE__ */ jsx13(
3428
+ theme: /* @__PURE__ */ jsx16(
2659
3429
  "button",
2660
3430
  {
2661
3431
  type: "button",
@@ -2663,11 +3433,11 @@ function WorkspaceHeader({
2663
3433
  title: theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
2664
3434
  "aria-label": "\u5207\u6362\u660E\u6697\u4E3B\u9898",
2665
3435
  onClick: handleTheme,
2666
- children: theme === "dark" ? /* @__PURE__ */ jsx13(Sun, { size: 15 }) : /* @__PURE__ */ jsx13(Moon, { size: 15 })
3436
+ children: theme === "dark" ? /* @__PURE__ */ jsx16(Icon10, { name: "sun", size: 15 }) : /* @__PURE__ */ jsx16(Icon10, { name: "moon", size: 15 })
2667
3437
  },
2668
3438
  "theme"
2669
3439
  ),
2670
- clear: /* @__PURE__ */ jsx13(
3440
+ clear: /* @__PURE__ */ jsx16(
2671
3441
  "button",
2672
3442
  {
2673
3443
  type: "button",
@@ -2675,12 +3445,12 @@ function WorkspaceHeader({
2675
3445
  title: "\u6E05\u7A7A\u4F1A\u8BDD",
2676
3446
  "aria-label": "\u6E05\u7A7A\u4F1A\u8BDD",
2677
3447
  onClick: handleClear,
2678
- children: /* @__PURE__ */ jsx13(Trash2, { size: 15 })
3448
+ children: /* @__PURE__ */ jsx16(Icon10, { name: "trash", size: 15 })
2679
3449
  },
2680
3450
  "clear"
2681
3451
  )
2682
3452
  };
2683
- const titleNode = editableTitle ? editing ? /* @__PURE__ */ jsx13(
3453
+ const titleNode = editableTitle ? editing ? /* @__PURE__ */ jsx16(
2684
3454
  "input",
2685
3455
  {
2686
3456
  className: "pactor-chat-workspace__title-input",
@@ -2697,7 +3467,7 @@ function WorkspaceHeader({
2697
3467
  }
2698
3468
  }
2699
3469
  }
2700
- ) : /* @__PURE__ */ jsx13(
3470
+ ) : /* @__PURE__ */ jsx16(
2701
3471
  "button",
2702
3472
  {
2703
3473
  type: "button",
@@ -2709,7 +3479,7 @@ function WorkspaceHeader({
2709
3479
  },
2710
3480
  children: displayTitle
2711
3481
  }
2712
- ) : /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__title", children: title });
3482
+ ) : /* @__PURE__ */ jsx16("div", { className: "pactor-chat-workspace__title", children: title });
2713
3483
  return /* @__PURE__ */ jsxs11("header", { className: "pactor-chat-workspace__header", children: [
2714
3484
  headerStart !== void 0 ? (
2715
3485
  // header 位模型 chip(决策 D2):与 title 组成左端组,chip 在前
@@ -2719,19 +3489,28 @@ function WorkspaceHeader({
2719
3489
  ] })
2720
3490
  ) : titleNode,
2721
3491
  hasActions ? /* @__PURE__ */ jsxs11("div", { className: "pactor-chat-workspace__header-right", children: [
2722
- /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__header-actions", children: headerActions.map((action) => actionButtons[action]) }),
2723
- headerExtra !== void 0 ? /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2724
- ] }) : headerExtra !== void 0 ? /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
3492
+ /* @__PURE__ */ jsx16("div", { className: "pactor-chat-workspace__header-actions", children: /* @__PURE__ */ jsx16(
3493
+ ChromeSlot,
3494
+ {
3495
+ fragment: buildHeaderActionsFragment({
3496
+ actions: headerActions,
3497
+ theme
3498
+ }),
3499
+ fallback: headerActions.map((action) => actionButtons[action])
3500
+ }
3501
+ ) }),
3502
+ headerExtra !== void 0 ? /* @__PURE__ */ jsx16("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
3503
+ ] }) : headerExtra !== void 0 ? /* @__PURE__ */ jsx16("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2725
3504
  ] });
2726
3505
  }
2727
3506
 
2728
3507
  // src/chat-composition.tsx
2729
- import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3508
+ import { Fragment as Fragment9, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
2730
3509
  function DslNodeSlotInner({ node }) {
2731
- const { renderChildren } = useRenderer3();
3510
+ const { renderChildren } = useRenderer4();
2732
3511
  return renderChildren([node]);
2733
3512
  }
2734
- var DslSlotGuard = class extends Component2 {
3513
+ var DslSlotGuard = class extends Component3 {
2735
3514
  constructor() {
2736
3515
  super(...arguments);
2737
3516
  this.state = { failed: false };
@@ -2749,7 +3528,7 @@ var DslSlotGuard = class extends Component2 {
2749
3528
  }
2750
3529
  };
2751
3530
  function DslNodeSlot({ node }) {
2752
- return /* @__PURE__ */ jsx14(DslSlotGuard, { children: /* @__PURE__ */ jsx14(DslNodeSlotInner, { node }) });
3531
+ return /* @__PURE__ */ jsx17(DslSlotGuard, { children: /* @__PURE__ */ jsx17(DslNodeSlotInner, { node }) });
2753
3532
  }
2754
3533
  function isDslNodeValue(value) {
2755
3534
  return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value) && typeof value.type === "string";
@@ -2759,7 +3538,7 @@ function resolveSlot(slot) {
2759
3538
  return void 0;
2760
3539
  }
2761
3540
  if (isDslNodeValue(slot)) {
2762
- return /* @__PURE__ */ jsx14(DslNodeSlot, { node: slot });
3541
+ return /* @__PURE__ */ jsx17(DslNodeSlot, { node: slot });
2763
3542
  }
2764
3543
  return slot;
2765
3544
  }
@@ -2789,7 +3568,11 @@ function ChatComposition({
2789
3568
  messageActions,
2790
3569
  onMessageAction,
2791
3570
  sidebar,
3571
+ toolbarStart,
2792
3572
  toolbarEnd,
3573
+ composerFooter,
3574
+ composerBottom,
3575
+ hideToolbar,
2793
3576
  className,
2794
3577
  onSend,
2795
3578
  artifact,
@@ -2799,12 +3582,12 @@ function ChatComposition({
2799
3582
  onArtifact,
2800
3583
  onOpenArtifact
2801
3584
  }) {
2802
- const storeRef = useRef11(null);
3585
+ const storeRef = useRef12(null);
2803
3586
  if (storeRef.current === null) {
2804
3587
  storeRef.current = services.store ?? services.storeFactory?.() ?? new ConversationStore();
2805
3588
  }
2806
3589
  const store = storeRef.current;
2807
- const artifactStoreRef = useRef11(null);
3590
+ const artifactStoreRef = useRef12(null);
2808
3591
  if (artifactStoreRef.current === null) {
2809
3592
  artifactStoreRef.current = services.artifactStore ?? new ArtifactStore();
2810
3593
  }
@@ -2828,7 +3611,7 @@ function ChatComposition({
2828
3611
  artifactStore.open(next);
2829
3612
  }
2830
3613
  };
2831
- const [attachments, setAttachments] = useState15([]);
3614
+ const [attachments, setAttachments] = useState14([]);
2832
3615
  const handleAttachFiles = services.onAttachFiles ? (files) => {
2833
3616
  void Promise.resolve(services.onAttachFiles(files)).then(
2834
3617
  (created) => {
@@ -2848,24 +3631,38 @@ function ChatComposition({
2848
3631
  model,
2849
3632
  onModelChange
2850
3633
  });
2851
- const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ jsx14(
2852
- ModelChip,
3634
+ const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ jsx17(
3635
+ ModelSelect,
2853
3636
  {
2854
3637
  models,
2855
3638
  model: currentModel,
2856
3639
  onModelChange: handleModelChange
2857
3640
  }
2858
3641
  ) : void 0;
2859
- const mergedToolbarEnd = services.toolbarEnd !== void 0 || toolbarEnd !== void 0 ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
3642
+ const mergedToolbarEnd = services.toolbarEnd !== void 0 || toolbarEnd !== void 0 ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
2860
3643
  services.toolbarEnd,
2861
3644
  toolbarEnd
2862
3645
  ] }) : void 0;
3646
+ const mergedToolbarStart = services.toolbarStart !== void 0 || toolbarStart !== void 0 ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
3647
+ services.toolbarStart,
3648
+ toolbarStart
3649
+ ] }) : void 0;
3650
+ const mergedComposerFooter = services.composerFooter !== void 0 || composerFooter !== void 0 ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
3651
+ services.composerFooter,
3652
+ composerFooter
3653
+ ] }) : void 0;
3654
+ const mergedComposerBottom = services.composerBottom !== void 0 || composerBottom !== void 0 ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
3655
+ services.composerBottom,
3656
+ composerBottom
3657
+ ] }) : void 0;
3658
+ 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;
3659
+ const renderHeader = showHeader !== false && hasHeaderContent;
2863
3660
  const workspace = /* @__PURE__ */ jsxs12(
2864
3661
  "div",
2865
3662
  {
2866
3663
  className: `pactor-chat-workspace${className ? ` ${className}` : ""}`,
2867
3664
  children: [
2868
- showHeader ? header ?? /* @__PURE__ */ jsx14(
3665
+ renderHeader ? header ?? /* @__PURE__ */ jsx17(
2869
3666
  WorkspaceHeader,
2870
3667
  {
2871
3668
  title,
@@ -2880,7 +3677,7 @@ function ChatComposition({
2880
3677
  onTitleChange
2881
3678
  }
2882
3679
  ) : null,
2883
- /* @__PURE__ */ jsx14(
3680
+ /* @__PURE__ */ jsx17(
2884
3681
  ChatView,
2885
3682
  {
2886
3683
  store,
@@ -2900,8 +3697,11 @@ function ChatComposition({
2900
3697
  onAttachmentsChange: setAttachments,
2901
3698
  onAttachFiles: handleAttachFiles,
2902
3699
  onAttachmentClick: services.onAttachmentClick,
2903
- toolbarStart: services.toolbarStart,
3700
+ toolbarStart: mergedToolbarStart,
2904
3701
  toolbarEnd: mergedToolbarEnd,
3702
+ composerFooter: mergedComposerFooter,
3703
+ composerBottom: mergedComposerBottom,
3704
+ hideToolbar,
2905
3705
  onMentionTrigger: services.onMentionTrigger,
2906
3706
  onPlusMenu: services.onPlusMenu,
2907
3707
  onArtifact,
@@ -2920,15 +3720,15 @@ function ChatComposition({
2920
3720
  ]
2921
3721
  }
2922
3722
  );
2923
- const main = sidebar ? /* @__PURE__ */ jsx14(AppShell, { sidebar, children: workspace }) : workspace;
3723
+ const main = sidebar ? /* @__PURE__ */ jsx17(AppShell, { sidebar, children: workspace }) : workspace;
2924
3724
  const panelFeatureUsed = artifact !== void 0 || panelOpen !== void 0 || inspector !== void 0 || onPanelClose !== void 0 || onArtifact !== void 0 || onOpenArtifact !== void 0 || services.artifactStore !== void 0;
2925
3725
  if (!panelFeatureUsed && !effectivePanelOpen) {
2926
3726
  return main;
2927
3727
  }
2928
3728
  return /* @__PURE__ */ jsxs12("div", { className: "pactor-chat-inspector-host", children: [
2929
3729
  main,
2930
- effectivePanelOpen ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
2931
- /* @__PURE__ */ jsx14(
3730
+ effectivePanelOpen ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
3731
+ /* @__PURE__ */ jsx17(
2932
3732
  "div",
2933
3733
  {
2934
3734
  className: "pactor-chat-inspector__scrim",
@@ -2936,7 +3736,7 @@ function ChatComposition({
2936
3736
  onClick: handlePanelClose
2937
3737
  }
2938
3738
  ),
2939
- /* @__PURE__ */ jsx14(
3739
+ /* @__PURE__ */ jsx17(
2940
3740
  ArtifactPanel,
2941
3741
  {
2942
3742
  artifact: effectiveArtifact,
@@ -2952,20 +3752,20 @@ function ChatComposition({
2952
3752
 
2953
3753
  // src/chat-services.tsx
2954
3754
  import { createContext as createContext2, useContext as useContext2 } from "react";
2955
- import { jsx as jsx15 } from "react/jsx-runtime";
3755
+ import { jsx as jsx18 } from "react/jsx-runtime";
2956
3756
  var ChatServicesContext = createContext2(null);
2957
3757
  function ChatServicesProvider({
2958
3758
  value,
2959
3759
  children
2960
3760
  }) {
2961
- return /* @__PURE__ */ jsx15(ChatServicesContext.Provider, { value, children });
3761
+ return /* @__PURE__ */ jsx18(ChatServicesContext.Provider, { value, children });
2962
3762
  }
2963
3763
  function useChatServices() {
2964
3764
  return useContext2(ChatServicesContext);
2965
3765
  }
2966
3766
 
2967
3767
  // src/chat-workspace.tsx
2968
- import { jsx as jsx16 } from "react/jsx-runtime";
3768
+ import { jsx as jsx19 } from "react/jsx-runtime";
2969
3769
  function createChatWorkspace(services) {
2970
3770
  function ChatWorkspace({
2971
3771
  title,
@@ -2975,7 +3775,11 @@ function createChatWorkspace(services) {
2975
3775
  conversationId,
2976
3776
  params,
2977
3777
  sidebar,
3778
+ toolbarStart,
2978
3779
  toolbarEnd,
3780
+ composerFooter,
3781
+ composerBottom,
3782
+ hideToolbar,
2979
3783
  artifact,
2980
3784
  panelOpen,
2981
3785
  onPanelClose,
@@ -2998,7 +3802,7 @@ function createChatWorkspace(services) {
2998
3802
  onMessageAction,
2999
3803
  className
3000
3804
  }) {
3001
- return /* @__PURE__ */ jsx16(ChatServicesProvider, { value: services, children: /* @__PURE__ */ jsx16(
3805
+ return /* @__PURE__ */ jsx19(ChatServicesProvider, { value: services, children: /* @__PURE__ */ jsx19(
3002
3806
  ChatComposition,
3003
3807
  {
3004
3808
  services,
@@ -3009,15 +3813,17 @@ function createChatWorkspace(services) {
3009
3813
  conversationId,
3010
3814
  params,
3011
3815
  className,
3012
- showHeader: title !== void 0 || services.headerExtra !== void 0 || // header 位模型 chip(决策 D2):仅配置 models 也渲染极简头部
3013
- modelPlacement === "header" && models !== void 0 && models.length > 0,
3014
3816
  headerExtra: services.headerExtra,
3015
- sidebar: sidebar ? /* @__PURE__ */ jsx16(DslNodeSlot, { node: sidebar }) : void 0,
3016
- toolbarEnd: toolbarEnd ? /* @__PURE__ */ jsx16(DslNodeSlot, { node: toolbarEnd }) : void 0,
3817
+ sidebar: sidebar ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: sidebar }) : void 0,
3818
+ toolbarStart: toolbarStart ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: toolbarStart }) : void 0,
3819
+ toolbarEnd: toolbarEnd ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: toolbarEnd }) : void 0,
3820
+ composerFooter: composerFooter ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: composerFooter }) : void 0,
3821
+ composerBottom: composerBottom ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: composerBottom }) : void 0,
3822
+ hideToolbar,
3017
3823
  artifact,
3018
3824
  panelOpen,
3019
3825
  onPanelClose,
3020
- inspector: inspector ? /* @__PURE__ */ jsx16(DslNodeSlot, { node: inspector }) : void 0,
3826
+ inspector: inspector ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: inspector }) : void 0,
3021
3827
  onArtifact,
3022
3828
  onOpenArtifact,
3023
3829
  headerActions,
@@ -3041,23 +3847,37 @@ function createChatWorkspace(services) {
3041
3847
  }
3042
3848
 
3043
3849
  // src/view/Chat.tsx
3044
- import {
3045
- Folder,
3046
- History,
3047
- MessageSquare,
3048
- Plus as Plus2,
3049
- Search,
3050
- Settings,
3051
- Star,
3052
- User
3053
- } from "lucide-react";
3054
- import {
3055
- Fragment as Fragment7,
3056
- useEffect as useEffect7
3057
- } from "react";
3850
+ import { createComponentRegistry, Icon as Icon11, RailTooltip } from "@pactor-app/ui/components";
3851
+ import { Fragment as Fragment10, useEffect as useEffect10, useRef as useRef13 } from "react";
3852
+
3853
+ // src/view/chrome/menu.tsx
3854
+ function buildSidebarMenuFragment(props) {
3855
+ return {
3856
+ requires: ["Menu"],
3857
+ node: {
3858
+ type: "Menu",
3859
+ props: {
3860
+ mode: "inline",
3861
+ selectedKey: props.activeMenuKey,
3862
+ items: props.menu.map((item) => ({
3863
+ key: item.key,
3864
+ label: item.label,
3865
+ ...item.icon !== void 0 ? { icon: item.icon } : {},
3866
+ ...item.badge !== void 0 ? { badge: item.badge } : {},
3867
+ ...item.dot === true ? { dot: true } : {},
3868
+ ...item.group !== void 0 ? { group: item.group } : {},
3869
+ ...item.disabled === true ? { disabled: true } : {}
3870
+ }))
3871
+ },
3872
+ events: {
3873
+ onSelect: [{ action: "chat.menuSelect", params: { key: "${event}" } }]
3874
+ }
3875
+ }
3876
+ };
3877
+ }
3058
3878
 
3059
3879
  // src/view/ChatShell.tsx
3060
- import { jsx as jsx17 } from "react/jsx-runtime";
3880
+ import { jsx as jsx20 } from "react/jsx-runtime";
3061
3881
  function ChatShell({
3062
3882
  menu,
3063
3883
  activeMenuKey,
@@ -3069,7 +3889,7 @@ function ChatShell({
3069
3889
  className,
3070
3890
  onMenuSelect
3071
3891
  }) {
3072
- const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx17(
3892
+ const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx20(
3073
3893
  ChatSidebarMenu,
3074
3894
  {
3075
3895
  menu,
@@ -3077,7 +3897,7 @@ function ChatShell({
3077
3897
  onMenuSelect
3078
3898
  }
3079
3899
  ) : null);
3080
- return /* @__PURE__ */ jsx17(
3900
+ return /* @__PURE__ */ jsx20(
3081
3901
  AppShell,
3082
3902
  {
3083
3903
  sidebar: sidebarContent,
@@ -3094,7 +3914,7 @@ function ChatShellDslAdapter({
3094
3914
  onMenuSelect,
3095
3915
  ...rest
3096
3916
  }) {
3097
- return /* @__PURE__ */ jsx17(
3917
+ return /* @__PURE__ */ jsx20(
3098
3918
  ChatShell,
3099
3919
  {
3100
3920
  ...rest,
@@ -3109,39 +3929,45 @@ function registerChatShellComponent(registry) {
3109
3929
  }
3110
3930
 
3111
3931
  // src/view/Chat.tsx
3112
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3113
- var MENU_ICONS = {
3114
- plus: Plus2,
3115
- chat: MessageSquare,
3116
- history: History,
3117
- settings: Settings,
3118
- star: Star,
3119
- search: Search,
3120
- user: User,
3121
- folder: Folder
3122
- };
3932
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
3123
3933
  function ChatSidebarMenu({
3124
3934
  menu,
3125
3935
  activeMenuKey,
3126
3936
  onMenuSelect
3127
3937
  }) {
3128
3938
  const drawer = useSidebarDrawer();
3129
- let lastGroup;
3130
- return /* @__PURE__ */ jsx18("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
3131
- const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
3132
- lastGroup = item.group;
3133
- const Icon = item.icon !== void 0 ? MENU_ICONS[item.icon] : void 0;
3134
- const active = item.key === activeMenuKey;
3135
- return /* @__PURE__ */ jsxs13(Fragment7, { children: [
3136
- groupLabel !== void 0 ? /* @__PURE__ */ jsx18("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
3137
- /* @__PURE__ */ jsxs13(
3939
+ const menuRef = useRef13(menu);
3940
+ menuRef.current = menu;
3941
+ const selectRef = useRef13(onMenuSelect);
3942
+ selectRef.current = onMenuSelect;
3943
+ const drawerRef = useRef13(drawer);
3944
+ drawerRef.current = drawer;
3945
+ useEffect10(() => {
3946
+ setChatChromeHandlers({
3947
+ menuSelect: (key) => {
3948
+ const item = menuRef.current.find((entry) => entry.key === key);
3949
+ if (item === void 0 || item.disabled === true) {
3950
+ return;
3951
+ }
3952
+ selectRef.current?.(key, item);
3953
+ drawerRef.current?.close();
3954
+ }
3955
+ });
3956
+ }, []);
3957
+ const fallback = (() => {
3958
+ let lastGroup;
3959
+ return /* @__PURE__ */ jsx21("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
3960
+ const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
3961
+ lastGroup = item.group;
3962
+ const active = item.key === activeMenuKey;
3963
+ const collapsed = drawer?.collapsed === true;
3964
+ const button = /* @__PURE__ */ jsxs13(
3138
3965
  "button",
3139
3966
  {
3140
3967
  type: "button",
3141
3968
  className: `pactor-chat-menu__item${active ? " pactor-chat-menu__item--active" : ""}`,
3142
3969
  disabled: item.disabled === true,
3143
3970
  "aria-current": active ? "true" : void 0,
3144
- title: drawer?.collapsed ? item.label : void 0,
3145
3971
  onClick: () => {
3146
3972
  if (item.disabled) {
3147
3973
  return;
@@ -3150,21 +3976,26 @@ function ChatSidebarMenu({
3150
3976
  drawer?.close();
3151
3977
  },
3152
3978
  children: [
3153
- item.icon !== void 0 ? Icon ? /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ jsx18(Icon, { size: 15, strokeWidth: 1.7 }) }) : /* @__PURE__ */ jsx18(
3154
- "span",
3155
- {
3156
- className: "pactor-chat-menu__icon-placeholder",
3157
- "aria-hidden": true
3158
- }
3159
- ) : null,
3160
- /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__label", children: item.label }),
3161
- item.dot === true ? /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
3162
- item.badge !== void 0 ? /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__badge", children: item.badge }) : null
3979
+ item.icon !== void 0 ? /* @__PURE__ */ jsx21("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ jsx21(Icon11, { name: item.icon, size: 15, strokeWidth: 1.7 }) }) : null,
3980
+ /* @__PURE__ */ jsx21("span", { className: "pactor-chat-menu__label", children: item.label }),
3981
+ item.dot === true ? /* @__PURE__ */ jsx21("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
3982
+ item.badge !== void 0 ? /* @__PURE__ */ jsx21("span", { className: "pactor-chat-menu__badge", children: item.badge }) : null
3163
3983
  ]
3164
3984
  }
3165
- )
3166
- ] }, item.key);
3167
- }) });
3985
+ );
3986
+ return /* @__PURE__ */ jsxs13(Fragment10, { children: [
3987
+ groupLabel !== void 0 ? /* @__PURE__ */ jsx21("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
3988
+ collapsed ? /* @__PURE__ */ jsx21(RailTooltip, { label: item.label, children: button }) : button
3989
+ ] }, item.key);
3990
+ }) });
3991
+ })();
3992
+ return /* @__PURE__ */ jsx21(
3993
+ ChromeSlot,
3994
+ {
3995
+ fragment: buildSidebarMenuFragment({ menu, activeMenuKey }),
3996
+ fallback
3997
+ }
3998
+ );
3168
3999
  }
3169
4000
  function Chat({
3170
4001
  title,
@@ -3177,6 +4008,11 @@ function Chat({
3177
4008
  menu,
3178
4009
  activeMenuKey,
3179
4010
  sidebar,
4011
+ toolbarStart,
4012
+ toolbarEnd,
4013
+ composerFooter,
4014
+ composerBottom,
4015
+ hideToolbar,
3180
4016
  headerExtra,
3181
4017
  header,
3182
4018
  headerActions,
@@ -3204,7 +4040,7 @@ function Chat({
3204
4040
  onMenuSelect
3205
4041
  }) {
3206
4042
  const services = useChatServices();
3207
- useEffect7(() => {
4043
+ useEffect10(() => {
3208
4044
  if (!services) {
3209
4045
  console.warn(
3210
4046
  "[@pactor-app/chat] Chat \u672A\u68C0\u6D4B\u5230 ChatServicesProvider\uFF1A\u8BF7\u5728\u5E94\u7528\u6839\u90E8\u4EE5 <ChatServicesProvider> \u6CE8\u5165 transport \u7B49\u670D\u52A1"
@@ -3212,10 +4048,10 @@ function Chat({
3212
4048
  }
3213
4049
  }, [services]);
3214
4050
  if (!services) {
3215
- return /* @__PURE__ */ jsx18("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" });
4051
+ return /* @__PURE__ */ jsx21("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" });
3216
4052
  }
3217
4053
  const sidebarSlot = resolveSlot(sidebar);
3218
- const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx18(
4054
+ const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx21(
3219
4055
  ChatSidebarMenu,
3220
4056
  {
3221
4057
  menu,
@@ -3223,7 +4059,7 @@ function Chat({
3223
4059
  onMenuSelect
3224
4060
  }
3225
4061
  ) : void 0);
3226
- return /* @__PURE__ */ jsx18(
4062
+ return /* @__PURE__ */ jsx21(
3227
4063
  ChatComposition,
3228
4064
  {
3229
4065
  services,
@@ -3234,9 +4070,14 @@ function Chat({
3234
4070
  conversationId,
3235
4071
  params,
3236
4072
  className,
3237
- showHeader: showHeader !== false,
4073
+ showHeader,
3238
4074
  header: resolveSlot(header),
3239
4075
  headerExtra: resolveSlot(headerExtra) ?? services.headerExtra,
4076
+ toolbarStart: resolveSlot(toolbarStart),
4077
+ toolbarEnd: resolveSlot(toolbarEnd),
4078
+ composerFooter: resolveSlot(composerFooter),
4079
+ composerBottom: resolveSlot(composerBottom),
4080
+ hideToolbar,
3240
4081
  headerActions,
3241
4082
  editableTitle,
3242
4083
  onExport,
@@ -3278,7 +4119,7 @@ function ChatDslAdapter({
3278
4119
  onTitleChange,
3279
4120
  ...rest
3280
4121
  }) {
3281
- return /* @__PURE__ */ jsx18(
4122
+ return /* @__PURE__ */ jsx21(
3282
4123
  Chat,
3283
4124
  {
3284
4125
  ...rest,
@@ -3303,13 +4144,18 @@ function registerChatComponent(registry) {
3303
4144
  }
3304
4145
  registerChatShellComponent(registry);
3305
4146
  }
4147
+ function createChatComponentRegistry() {
4148
+ const registry = createComponentRegistry();
4149
+ registerChatComponent(registry);
4150
+ return registry;
4151
+ }
3306
4152
 
3307
4153
  // src/view/ChatLayout.tsx
3308
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
4154
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
3309
4155
  function ChatLayout({ title, children }) {
3310
4156
  return /* @__PURE__ */ jsxs14("div", { className: "pactor-chat-layout", children: [
3311
- /* @__PURE__ */ jsx19("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
3312
- /* @__PURE__ */ jsx19("main", { className: "pactor-chat-layout__main", children })
4157
+ /* @__PURE__ */ jsx22("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
4158
+ /* @__PURE__ */ jsx22("main", { className: "pactor-chat-layout__main", children })
3313
4159
  ] });
3314
4160
  }
3315
4161
  var CHAT_LAYOUT_TYPE = "chat";
@@ -3338,14 +4184,13 @@ export {
3338
4184
  ConversationStore,
3339
4185
  DEFAULT_CHAT_GREETING,
3340
4186
  EmptyState,
3341
- MENU_ICONS,
3342
- MESSAGE_ACTION_ICONS,
3343
4187
  MessageActions,
3344
4188
  MessageBubble,
3345
4189
  PartRenderer,
3346
4190
  applyChatEvent,
3347
4191
  buildChatScope,
3348
4192
  collectMessageText,
4193
+ createChatComponentRegistry,
3349
4194
  createChatId,
3350
4195
  createChatRuntime,
3351
4196
  createChatWorkspace,