@pactor-app/chat 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -653,20 +653,34 @@ function createChatRuntime(options = {}) {
653
653
  }
654
654
 
655
655
  // src/dsl-guard.ts
656
+ import { isDslSourceRef } from "@pactor-app/core";
656
657
  var CHAT_DENIED_TYPE = "ChatDenied";
657
658
  function guardChatDsl(node, registry) {
658
- if (node.type !== CHAT_DENIED_TYPE && !registry.has(node.type)) {
659
+ const type = node.type ?? "(reference)";
660
+ if (type !== CHAT_DENIED_TYPE && !registry.has(type)) {
659
661
  return {
660
662
  type: CHAT_DENIED_TYPE,
661
- props: { deniedType: node.type }
663
+ props: { deniedType: type }
662
664
  };
663
665
  }
664
- if (!node.children || node.children.length === 0) {
666
+ if (node.children === void 0) {
667
+ return node;
668
+ }
669
+ if (!Array.isArray(node.children)) {
670
+ return {
671
+ type: CHAT_DENIED_TYPE,
672
+ props: { deniedType: `${type}(remote)` }
673
+ };
674
+ }
675
+ if (node.children.length === 0) {
665
676
  return node;
666
677
  }
667
678
  return {
668
679
  ...node,
669
- children: node.children.map((child) => guardChatDsl(child, registry))
680
+ // 数组中混排的引用元素同样无法静态过白名单:替换为受控占位
681
+ children: node.children.map(
682
+ (child) => isDslSourceRef(child) ? { type: CHAT_DENIED_TYPE, props: { deniedType: "(remote)" } } : guardChatDsl(child, registry)
683
+ )
670
684
  };
671
685
  }
672
686
 
@@ -778,12 +792,12 @@ function useChatAutoScroll({
778
792
  }
779
793
 
780
794
  // src/chat-composition.tsx
781
- import { useRenderer as useRenderer3 } from "@pactor-app/runtime";
795
+ import { useRenderer as useRenderer4 } from "@pactor-app/runtime";
782
796
  import {
783
- Component as Component2,
797
+ Component as Component3,
784
798
  isValidElement,
785
- useRef as useRef11,
786
- useState as useState15,
799
+ useRef as useRef12,
800
+ useState as useState14,
787
801
  useSyncExternalStore as useSyncExternalStore2
788
802
  } from "react";
789
803
 
@@ -810,132 +824,283 @@ function useChatModel({
810
824
  }
811
825
 
812
826
  // src/view/AppShell.tsx
813
- import { Menu, PanelLeft } from "lucide-react";
814
827
  import {
815
- createContext,
816
- useCallback as useCallback2,
817
- useContext,
818
- useEffect as useEffect2,
819
- useState as useState4
820
- } from "react";
821
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
822
- 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";
823
832
  function useSidebarDrawer() {
824
- return useContext(SidebarDrawerContext);
833
+ return useUiSidebarDrawer();
825
834
  }
826
- var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
827
- function useIsMobileViewport() {
828
- const [isMobile, setIsMobile] = useState4(
829
- () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
830
- );
831
- useEffect2(() => {
832
- if (typeof window.matchMedia !== "function") {
833
- return;
834
- }
835
- const media = window.matchMedia(MOBILE_MEDIA_QUERY);
836
- const onChange = (event) => {
837
- setIsMobile(event.matches);
838
- };
839
- setIsMobile(media.matches);
840
- media.addEventListener("change", onChange);
841
- return () => media.removeEventListener("change", onChange);
842
- }, []);
843
- return isMobile;
844
- }
845
- function AppShell({ sidebar, brand, locationKey, sidebarFooter, collapsible = false, children, className }) {
846
- const [drawerOpen, setDrawerOpen] = useState4(false);
847
- const isMobile = useIsMobileViewport();
848
- const close = useCallback2(() => setDrawerOpen(false), []);
849
- const [collapsed, setCollapsed] = useState4(false);
850
- const collapsedEffective = collapsible && collapsed && !isMobile;
851
- const framed = brand !== void 0 || sidebarFooter !== void 0;
852
- const [trackedLocationKey, setTrackedLocationKey] = useState4(locationKey);
853
- if (trackedLocationKey !== locationKey) {
854
- setTrackedLocationKey(locationKey);
855
- setDrawerOpen(false);
856
- }
857
- useEffect2(() => {
858
- if (!drawerOpen) {
859
- 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
860
854
  }
861
- const onKeyDown = (event) => {
862
- if (event.key === "Escape") {
863
- setDrawerOpen(false);
864
- }
865
- };
866
- window.addEventListener("keydown", onKeyDown);
867
- return () => window.removeEventListener("keydown", onKeyDown);
868
- }, [drawerOpen]);
869
- return /* @__PURE__ */ jsx(SidebarDrawerContext.Provider, { value: { close, collapsed: collapsedEffective }, children: /* @__PURE__ */ jsxs("div", { className: `pactor-chat-shell${className ? ` ${className}` : ""}`, children: [
870
- drawerOpen ? /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__scrim", "aria-hidden": true, onClick: close }) : null,
871
- /* @__PURE__ */ jsx(
872
- "div",
873
- {
874
- className: `pactor-chat-shell__sidebar${framed ? " pactor-chat-shell__sidebar--framed" : ""}${collapsedEffective ? " pactor-chat-shell__sidebar--collapsed" : ""}`,
875
- "data-open": drawerOpen ? "true" : "false",
876
- inert: isMobile && !drawerOpen ? true : void 0,
877
- children: framed ? /* @__PURE__ */ jsxs(Fragment, { children: [
878
- brand !== void 0 && // 桌面品牌行(决策 D3):brand 左、折叠钮右;移动端由
879
- // CSS 隐藏(品牌维持顶栏现状)
880
- /* @__PURE__ */ jsxs("div", { className: "pactor-chat-shell__sidebar-header", children: [
881
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__brand", children: brand }),
882
- collapsible && !isMobile && /* @__PURE__ */ jsx(
883
- "button",
884
- {
885
- type: "button",
886
- className: "pactor-chat-icon-button pactor-chat-shell__collapse",
887
- "aria-label": collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
888
- "aria-expanded": !collapsedEffective,
889
- title: collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
890
- onClick: () => setCollapsed((value) => !value),
891
- children: /* @__PURE__ */ jsx(PanelLeft, { size: 16, strokeWidth: 1.7 })
892
- }
893
- )
894
- ] }),
895
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__sidebar-body", children: sidebar }),
896
- sidebarFooter !== void 0 && /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__sidebar-footer", children: sidebarFooter })
897
- ] }) : sidebar
898
- }
899
- ),
900
- /* @__PURE__ */ jsxs("main", { className: "pactor-chat-shell__main", children: [
901
- /* @__PURE__ */ jsxs("div", { className: "pactor-chat-shell__topbar", children: [
902
- /* @__PURE__ */ jsx(
903
- "button",
904
- {
905
- type: "button",
906
- className: "pactor-chat-icon-button pactor-chat-shell__menu",
907
- "aria-label": "\u6253\u5F00\u5BFC\u822A",
908
- "aria-expanded": drawerOpen,
909
- onClick: () => setDrawerOpen(true),
910
- children: /* @__PURE__ */ jsx(Menu, { size: 18, strokeWidth: 1.7 })
911
- }
912
- ),
913
- brand
914
- ] }),
915
- /* @__PURE__ */ jsx("div", { className: "pactor-chat-shell__content", children })
916
- ] })
917
- ] }) });
855
+ );
918
856
  }
919
857
 
920
858
  // src/view/ArtifactPanel.tsx
921
- import { X } from "lucide-react";
859
+ import { Icon as Icon3 } from "@pactor-app/ui/components";
922
860
  import { Registry as Registry2 } from "@pactor-app/core";
923
861
  import { MarkdownRenderer as MarkdownRenderer2 } from "@pactor-app/markdown";
924
- import { DslRenderer as DslRenderer2, useRenderer as useRenderer2 } from "@pactor-app/runtime";
862
+ import { DslRenderer as DslRenderer2, useRenderer as useRenderer3 } from "@pactor-app/runtime";
925
863
  import {
926
- Component,
864
+ Component as Component2,
865
+ useEffect as useEffect2,
927
866
  useMemo,
928
867
  useRef as useRef3,
929
- useState as useState5
868
+ useState as useState4
930
869
  } from "react";
931
870
 
932
871
  // src/view/PartRenderer.tsx
933
- import { CircleAlert, PanelRightOpen } from "lucide-react";
872
+ import { Icon } from "@pactor-app/ui/components";
934
873
  import { MarkdownRenderer } from "@pactor-app/markdown";
935
- import { DslRenderer, useRenderer } from "@pactor-app/runtime";
936
- 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";
937
1102
  function ChatDenied({ deniedType }) {
938
- 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: [
939
1104
  "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728 Chat \u4E2D\u4F7F\u7528",
940
1105
  deniedType ? `\uFF1A${deniedType}` : ""
941
1106
  ] });
@@ -944,64 +1109,90 @@ function ChatDslNode({
944
1109
  node,
945
1110
  scope
946
1111
  }) {
947
- const renderer = useRenderer();
1112
+ const renderer = useRenderer2();
948
1113
  const extraScope = scope ? buildChatScope(scope) : void 0;
949
- return /* @__PURE__ */ jsx2(Fragment2, { children: renderer.renderChildren([node], extraScope) });
1114
+ return /* @__PURE__ */ jsx3(Fragment2, { children: renderer.renderChildren([node], extraScope) });
950
1115
  }
951
1116
  function SmoothText({ content, streaming }) {
952
1117
  const shown = useSmoothStreamText(content, streaming);
953
- return /* @__PURE__ */ jsx2("div", { className: "pactor-chat-text", children: shown });
1118
+ return /* @__PURE__ */ jsx3("div", { className: "pactor-chat-text", children: shown });
954
1119
  }
955
1120
  function SmoothMarkdown({ content, streaming }) {
956
1121
  const shown = useSmoothStreamText(content, streaming);
957
- return /* @__PURE__ */ jsx2(MarkdownRenderer, { content: shown });
1122
+ return /* @__PURE__ */ jsx3(MarkdownRenderer, { content: shown });
958
1123
  }
959
1124
  function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact }) {
960
1125
  switch (part.type) {
961
1126
  case "text":
962
- return /* @__PURE__ */ jsx2(SmoothText, { content: part.content, streaming });
1127
+ return /* @__PURE__ */ jsx3(SmoothText, { content: part.content, streaming });
963
1128
  case "markdown":
964
- return /* @__PURE__ */ jsx2(SmoothMarkdown, { content: part.content, streaming });
1129
+ return /* @__PURE__ */ jsx3(SmoothMarkdown, { content: part.content, streaming });
965
1130
  case "thinking":
966
1131
  case "tool-call":
967
1132
  case "tool-result":
968
1133
  return null;
969
1134
  case "dsl": {
970
1135
  if (!part.schema || !runtime) {
971
- 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
+ );
972
1145
  }
973
1146
  const guarded = guardChatDsl(part.schema, runtime.context.components);
974
- 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 }) }) });
975
1148
  }
976
1149
  case "error":
977
- return /* @__PURE__ */ jsxs2("div", { className: "pactor-chat-error", role: "alert", children: [
978
- /* @__PURE__ */ jsx2(CircleAlert, { size: 14 }),
979
- /* @__PURE__ */ jsx2("span", { children: part.message })
980
- ] });
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
+ );
981
1160
  case "status":
982
- 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
+ );
983
1168
  case "artifact":
984
- return /* @__PURE__ */ jsxs2(
985
- "div",
1169
+ return /* @__PURE__ */ jsx3(
1170
+ ChromeSlot,
986
1171
  {
987
- className: "pactor-chat-artifact-card",
988
- "data-artifact-kind": part.artifact.kind,
989
- children: [
990
- /* @__PURE__ */ jsx2("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
991
- /* @__PURE__ */ jsx2("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
992
- /* @__PURE__ */ jsxs2(
993
- "button",
994
- {
995
- type: "button",
996
- className: "pactor-chat-artifact-card__open",
997
- onClick: () => onOpenArtifact?.(part.artifact),
998
- children: [
999
- /* @__PURE__ */ jsx2(PanelRightOpen, { size: 13, strokeWidth: 1.7 }),
1000
- "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1001
- ]
1002
- }
1003
- )
1004
- ]
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
+ )
1005
1196
  }
1006
1197
  );
1007
1198
  default:
@@ -1009,8 +1200,65 @@ function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact
1009
1200
  }
1010
1201
  }
1011
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
+
1012
1260
  // src/view/ArtifactPanel.tsx
1013
- 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";
1014
1262
  function useArtifactRuntime(safeRegistry, user) {
1015
1263
  return useMemo(() => {
1016
1264
  if (!safeRegistry) {
@@ -1024,7 +1272,7 @@ function useArtifactRuntime(safeRegistry, user) {
1024
1272
  return createChatRuntime({ safeRegistry: components, user });
1025
1273
  }, [safeRegistry, user]);
1026
1274
  }
1027
- var ArtifactDslGuard = class extends Component {
1275
+ var ArtifactDslGuard = class extends Component2 {
1028
1276
  constructor() {
1029
1277
  super(...arguments);
1030
1278
  this.state = { failed: false };
@@ -1036,12 +1284,12 @@ var ArtifactDslGuard = class extends Component {
1036
1284
  console.warn("[@pactor-app/chat] DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
1037
1285
  }
1038
1286
  render() {
1039
- 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;
1040
1288
  }
1041
1289
  };
1042
1290
  function ArtifactDslNode({ node }) {
1043
- const { renderChildren } = useRenderer2();
1044
- return /* @__PURE__ */ jsx3(Fragment3, { children: renderChildren([node]) });
1291
+ const { renderChildren } = useRenderer3();
1292
+ return /* @__PURE__ */ jsx5(Fragment3, { children: renderChildren([node]) });
1045
1293
  }
1046
1294
  function ArtifactDslContent({
1047
1295
  content,
@@ -1049,10 +1297,10 @@ function ArtifactDslContent({
1049
1297
  }) {
1050
1298
  const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
1051
1299
  if (!node || !runtime) {
1052
- 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" });
1053
1301
  }
1054
1302
  const guarded = guardChatDsl(node, runtime.context.components);
1055
- 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 }) }) });
1056
1304
  }
1057
1305
  function ArtifactContent({
1058
1306
  artifact,
@@ -1060,16 +1308,16 @@ function ArtifactContent({
1060
1308
  }) {
1061
1309
  switch (artifact.kind) {
1062
1310
  case "markdown":
1063
- return /* @__PURE__ */ jsx3(MarkdownRenderer2, { content: String(artifact.content) });
1311
+ return /* @__PURE__ */ jsx5(MarkdownRenderer2, { content: String(artifact.content) });
1064
1312
  case "code":
1065
- return /* @__PURE__ */ jsxs3("div", { className: "pactor-chat-inspector__code", children: [
1066
- artifact.language ? /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1067
- /* @__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) }) })
1068
1316
  ] });
1069
1317
  case "text":
1070
- 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) });
1071
1319
  case "dsl":
1072
- return /* @__PURE__ */ jsx3(ArtifactDslContent, { content: artifact.content, runtime });
1320
+ return /* @__PURE__ */ jsx5(ArtifactDslContent, { content: artifact.content, runtime });
1073
1321
  default:
1074
1322
  return null;
1075
1323
  }
@@ -1083,8 +1331,13 @@ function ArtifactPanel({
1083
1331
  className
1084
1332
  }) {
1085
1333
  const runtime = useArtifactRuntime(safeRegistry, user);
1334
+ const closeRef = useRef3(onClose);
1335
+ closeRef.current = onClose;
1336
+ useEffect2(() => {
1337
+ setChatChromeHandlers({ panelClose: () => closeRef.current?.() });
1338
+ }, []);
1086
1339
  const panelRef = useRef3(null);
1087
- const [width, setWidth] = useState5(null);
1340
+ const [width, setWidth] = useState4(null);
1088
1341
  const handleResizeStart = (event) => {
1089
1342
  event.preventDefault();
1090
1343
  const panel = panelRef.current;
@@ -1110,7 +1363,7 @@ function ArtifactPanel({
1110
1363
  document.addEventListener("pointermove", onMove);
1111
1364
  document.addEventListener("pointerup", onUp);
1112
1365
  };
1113
- return /* @__PURE__ */ jsxs3(
1366
+ return /* @__PURE__ */ jsxs2(
1114
1367
  "aside",
1115
1368
  {
1116
1369
  ref: panelRef,
@@ -1118,7 +1371,7 @@ function ArtifactPanel({
1118
1371
  "data-kind": artifact?.kind,
1119
1372
  style: width !== null ? { width: `${width}px` } : void 0,
1120
1373
  children: [
1121
- /* @__PURE__ */ jsx3(
1374
+ /* @__PURE__ */ jsx5(
1122
1375
  "div",
1123
1376
  {
1124
1377
  className: "pactor-chat-inspector__resize",
@@ -1128,43 +1381,53 @@ function ArtifactPanel({
1128
1381
  onPointerDown: handleResizeStart
1129
1382
  }
1130
1383
  ),
1131
- /* @__PURE__ */ jsxs3("div", { className: "pactor-chat-inspector__header", children: [
1132
- /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1133
- artifact ? /* @__PURE__ */ jsx3("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1134
- /* @__PURE__ */ jsx3(
1135
- "button",
1136
- {
1137
- type: "button",
1138
- className: "pactor-chat-icon-button pactor-chat-inspector__close",
1139
- "aria-label": "\u5173\u95ED\u9762\u677F",
1140
- onClick: onClose,
1141
- children: /* @__PURE__ */ jsx3(X, { size: 16, strokeWidth: 1.7 })
1142
- }
1143
- )
1144
- ] }),
1145
- /* @__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 })
1146
1409
  ]
1147
1410
  }
1148
1411
  );
1149
1412
  }
1150
1413
 
1151
1414
  // src/view/ChatView.tsx
1152
- import { ArrowDown } from "lucide-react";
1415
+ import { Icon as Icon9 } from "@pactor-app/ui/components";
1153
1416
  import { Registry as Registry3 } from "@pactor-app/core";
1154
- 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";
1155
1418
 
1156
1419
  // src/view/Composer.tsx
1157
- import { ArrowUp, FileText, Paperclip, Plus, Square, X as X2 } from "lucide-react";
1158
- 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";
1159
1422
 
1160
1423
  // src/view/AnchoredPopover.tsx
1161
1424
  import {
1162
1425
  useEffect as useEffect3,
1163
1426
  useLayoutEffect as useLayoutEffect2,
1164
1427
  useRef as useRef4,
1165
- useState as useState6
1428
+ useState as useState5
1166
1429
  } from "react";
1167
- import { jsx as jsx4 } from "react/jsx-runtime";
1430
+ import { jsx as jsx6 } from "react/jsx-runtime";
1168
1431
  function AnchoredPopover({
1169
1432
  anchorRect,
1170
1433
  onClose,
@@ -1174,7 +1437,7 @@ function AnchoredPopover({
1174
1437
  sideOffset = 8
1175
1438
  }) {
1176
1439
  const ref = useRef4(null);
1177
- const [position, setPosition] = useState6(null);
1440
+ const [position, setPosition] = useState5(null);
1178
1441
  useLayoutEffect2(() => {
1179
1442
  const element = ref.current;
1180
1443
  if (!element) return;
@@ -1221,7 +1484,7 @@ function AnchoredPopover({
1221
1484
  position?.below ? "pactor-chat-popover--below" : "",
1222
1485
  className ?? ""
1223
1486
  ].filter(Boolean).join(" ");
1224
- return /* @__PURE__ */ jsx4(
1487
+ return /* @__PURE__ */ jsx6(
1225
1488
  "div",
1226
1489
  {
1227
1490
  ref,
@@ -1240,19 +1503,44 @@ function AnchoredPopover({
1240
1503
  }
1241
1504
 
1242
1505
  // src/view/ModelChip.tsx
1243
- import { Check, ChevronsUpDown } from "lucide-react";
1244
- import { useRef as useRef5, useState as useState7 } from "react";
1245
- 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";
1246
1534
  function ModelChip({
1247
1535
  models,
1248
1536
  model,
1249
1537
  onModelChange
1250
1538
  }) {
1251
1539
  const chipRef = useRef5(null);
1252
- const [anchor, setAnchor] = useState7(null);
1540
+ const [anchor, setAnchor] = useState6(null);
1253
1541
  const currentOption = models.find((option) => option.value === model) ?? models[0];
1254
- return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1255
- /* @__PURE__ */ jsxs4(
1542
+ return /* @__PURE__ */ jsxs3(Fragment4, { children: [
1543
+ /* @__PURE__ */ jsxs3(
1256
1544
  "button",
1257
1545
  {
1258
1546
  ref: chipRef,
@@ -1262,18 +1550,18 @@ function ModelChip({
1262
1550
  "aria-label": "\u9009\u62E9\u6A21\u578B",
1263
1551
  onClick: () => setAnchor(chipRef.current.getBoundingClientRect()),
1264
1552
  children: [
1265
- /* @__PURE__ */ jsx5("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1266
- /* @__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 })
1267
1555
  ]
1268
1556
  }
1269
1557
  ),
1270
- anchor && /* @__PURE__ */ jsx5(
1558
+ anchor && /* @__PURE__ */ jsx7(
1271
1559
  AnchoredPopover,
1272
1560
  {
1273
1561
  anchorRect: anchor,
1274
1562
  onClose: () => setAnchor(null),
1275
1563
  role: "listbox",
1276
- children: models.map((option) => /* @__PURE__ */ jsxs4(
1564
+ children: models.map((option) => /* @__PURE__ */ jsxs3(
1277
1565
  "button",
1278
1566
  {
1279
1567
  type: "button",
@@ -1285,8 +1573,8 @@ function ModelChip({
1285
1573
  setAnchor(null);
1286
1574
  },
1287
1575
  children: [
1288
- /* @__PURE__ */ jsx5("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1289
- 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" })
1290
1578
  ]
1291
1579
  },
1292
1580
  option.value
@@ -1295,10 +1583,243 @@ function ModelChip({
1295
1583
  )
1296
1584
  ] });
1297
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
+ }
1298
1819
 
1299
1820
  // src/view/context-sources.tsx
1300
- import { useEffect as useEffect4, useState as useState8 } from "react";
1301
- 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";
1302
1823
  function sourceCacheKey(source) {
1303
1824
  return `${source.service ?? ""}:${JSON.stringify(source.params ?? {})}`;
1304
1825
  }
@@ -1333,8 +1854,8 @@ function ContextSourceList({
1333
1854
  cache,
1334
1855
  onSelect
1335
1856
  }) {
1336
- const [resolutions, setResolutions] = useState8([]);
1337
- useEffect4(() => {
1857
+ const [resolutions, setResolutions] = useState7([]);
1858
+ useEffect5(() => {
1338
1859
  let cancelled = false;
1339
1860
  const initial = sources.map(
1340
1861
  (source) => source.items ? { status: "ready", items: source.items } : { status: "loading" }
@@ -1381,10 +1902,10 @@ function ContextSourceList({
1381
1902
  });
1382
1903
  return /* @__PURE__ */ jsxs5("div", { className: "pactor-chat-source-list", children: [
1383
1904
  groups.map(({ source, resolution, items }) => /* @__PURE__ */ jsxs5("div", { className: "pactor-chat-source-list__group", children: [
1384
- sources.length > 1 && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1385
- resolution.status === "loading" && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1386
- resolution.status === "error" && /* @__PURE__ */ jsx6("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1387
- 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(
1388
1909
  "button",
1389
1910
  {
1390
1911
  type: "button",
@@ -1395,22 +1916,13 @@ function ContextSourceList({
1395
1916
  item.id
1396
1917
  ))
1397
1918
  ] }, `${source.trigger}:${source.label}`)),
1398
- 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" })
1399
1920
  ] });
1400
1921
  }
1401
1922
 
1402
1923
  // src/view/Composer.tsx
1403
- 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";
1404
1925
  var MAX_INPUT_HEIGHT = 200;
1405
- function formatSize(bytes) {
1406
- if (bytes < 1024) {
1407
- return `${bytes} B`;
1408
- }
1409
- if (bytes < 1024 * 1024) {
1410
- return `${(bytes / 1024).toFixed(1)} KB`;
1411
- }
1412
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1413
- }
1414
1926
  function detectMention(text, caret) {
1415
1927
  const beforeCaret = text.slice(0, caret);
1416
1928
  const match = /(?:^|\s)@([^\s@]*)$/.exec(beforeCaret);
@@ -1439,10 +1951,12 @@ function Composer({
1439
1951
  contextSources,
1440
1952
  contextChips,
1441
1953
  onContextChipsChange,
1442
- callService
1954
+ callService,
1955
+ composerFooter,
1956
+ hideToolbar = false
1443
1957
  }) {
1444
- const [text, setText] = useState9("");
1445
- const [dragOver, setDragOver] = useState9(false);
1958
+ const [text, setText] = useState8("");
1959
+ const [dragOver, setDragOver] = useState8(false);
1446
1960
  const inputRef = useRef6(null);
1447
1961
  const fileInputRef = useRef6(null);
1448
1962
  const plusButtonRef = useRef6(null);
@@ -1458,8 +1972,8 @@ function Composer({
1458
1972
  );
1459
1973
  const builtinMentionEnabled = onMentionTrigger === void 0 && mentionSources.length > 0;
1460
1974
  const builtinPlusEnabled = onPlusMenu === void 0 && plusSources.length > 0;
1461
- const [mentionState, setMentionState] = useState9(null);
1462
- const [plusAnchor, setPlusAnchor] = useState9(null);
1975
+ const [mentionState, setMentionState] = useState8(null);
1976
+ const [plusAnchor, setPlusAnchor] = useState8(null);
1463
1977
  const sourceCacheRef = useRef6(null);
1464
1978
  if (sourceCacheRef.current === null) {
1465
1979
  sourceCacheRef.current = /* @__PURE__ */ new Map();
@@ -1494,105 +2008,147 @@ function Composer({
1494
2008
  onSend?.(value);
1495
2009
  setText("");
1496
2010
  };
1497
- const handleChange = (event) => {
1498
- 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) => {
1499
2017
  setText(value);
1500
2018
  if (onMentionTrigger || builtinMentionEnabled) {
1501
- const caret = event.target.selectionStart ?? value.length;
1502
2019
  const query = detectMention(value, caret);
1503
2020
  if (onMentionTrigger) {
1504
2021
  if (query !== null) {
1505
- onMentionTrigger(query, event.target.getBoundingClientRect());
2022
+ onMentionTrigger(query, rect);
1506
2023
  }
1507
2024
  } else {
1508
- setMentionState(
1509
- query !== null ? { query, anchorRect: event.target.getBoundingClientRect() } : null
1510
- );
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);
1511
2067
  }
1512
2068
  }
1513
2069
  };
1514
- const showToolbar = toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled;
1515
- return /* @__PURE__ */ jsxs6(
1516
- "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",
1517
2073
  {
1518
- className: `pactor-chat-composer${dragOver ? " pactor-chat-composer--dragover" : ""}`,
1519
- onDragOver: (event) => {
1520
- event.preventDefault();
1521
- setDragOver(true);
1522
- },
1523
- onDragLeave: () => setDragOver(false),
1524
- onDrop: (event) => {
1525
- event.preventDefault();
1526
- setDragOver(false);
1527
- const files = collectDropFiles(event);
1528
- 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) {
1529
2081
  onAttachFiles(files);
1530
2082
  }
1531
- },
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,
1532
2130
  children: [
1533
- attachments !== void 0 && attachments.length > 0 && /* @__PURE__ */ jsx7("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1534
- const isImage = attachment.mimeType.startsWith("image/");
1535
- return /* @__PURE__ */ jsxs6(
1536
- "div",
1537
- {
1538
- className: "pactor-chat-composer__attachment",
1539
- "data-attachment-id": attachment.id,
1540
- onClick: () => onAttachmentClick?.(attachment),
1541
- children: [
1542
- 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: [
1543
- /* @__PURE__ */ jsx7(FileText, { size: 14 }),
1544
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1545
- attachment.size !== void 0 && /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1546
- ] }),
1547
- /* @__PURE__ */ jsx7(
1548
- "button",
1549
- {
1550
- type: "button",
1551
- className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1552
- title: "\u79FB\u9664\u9644\u4EF6",
1553
- "aria-label": "\u79FB\u9664\u9644\u4EF6",
1554
- onClick: (event) => {
1555
- event.stopPropagation();
1556
- onAttachmentsChange?.(
1557
- attachments.filter((item) => item.id !== attachment.id)
1558
- );
1559
- },
1560
- children: /* @__PURE__ */ jsx7(X2, { size: 12 })
1561
- }
1562
- )
1563
- ]
1564
- },
1565
- attachment.id
1566
- );
1567
- }) }),
1568
- contextChips !== void 0 && contextChips.length > 0 && /* @__PURE__ */ jsx7("div", { className: "pactor-chat-composer__contexts", children: contextChips.map((chip) => /* @__PURE__ */ jsxs6(
1569
- "span",
2131
+ attachments !== void 0 && /* @__PURE__ */ jsx10(
2132
+ ComposerAttachments,
1570
2133
  {
1571
- className: "pactor-chat-context-chip",
1572
- "data-context-id": chip.id,
1573
- children: [
1574
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1575
- /* @__PURE__ */ jsx7(
1576
- "button",
1577
- {
1578
- type: "button",
1579
- className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1580
- title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1581
- "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1582
- onClick: () => onContextChipsChange?.(
1583
- contextChips.filter(
1584
- (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1585
- )
1586
- ),
1587
- children: /* @__PURE__ */ jsx7(X2, { size: 12 })
1588
- }
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)
1589
2146
  )
1590
- ]
1591
- },
1592
- `${chip.trigger ?? ""}:${chip.id}`
1593
- )) }),
2147
+ )
2148
+ }
2149
+ ),
1594
2150
  /* @__PURE__ */ jsxs6("div", { className: "pactor-chat-composer__row", children: [
1595
- /* @__PURE__ */ jsx7(
2151
+ /* @__PURE__ */ jsx10(
1596
2152
  "textarea",
1597
2153
  {
1598
2154
  ref: inputRef,
@@ -1609,10 +2165,8 @@ function Composer({
1609
2165
  composingRef.current = false;
1610
2166
  },
1611
2167
  onPaste: (event) => {
1612
- const files = Array.from(event.clipboardData.files);
1613
- if (files.length > 0 && onAttachFiles) {
2168
+ if (handlePasteFiles(event.clipboardData)) {
1614
2169
  event.preventDefault();
1615
- onAttachFiles(files);
1616
2170
  }
1617
2171
  },
1618
2172
  onKeyDown: (event) => {
@@ -1626,7 +2180,7 @@ function Composer({
1626
2180
  }
1627
2181
  }
1628
2182
  ),
1629
- /* @__PURE__ */ jsx7(
2183
+ hideToolbar ? null : /* @__PURE__ */ jsx10(
1630
2184
  "button",
1631
2185
  {
1632
2186
  type: "button",
@@ -1635,14 +2189,14 @@ function Composer({
1635
2189
  "aria-label": streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1636
2190
  disabled: streaming ? false : disabled || !text.trim(),
1637
2191
  onClick: streaming ? onStop : send,
1638
- 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 })
1639
2193
  }
1640
2194
  )
1641
2195
  ] }),
1642
2196
  showToolbar && /* @__PURE__ */ jsxs6("div", { className: "pactor-chat-composer__toolbar", children: [
1643
2197
  toolbarStart,
1644
- /* @__PURE__ */ jsx7("span", { className: "pactor-chat-composer__toolbar-spacer" }),
1645
- (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ jsx7(
2198
+ /* @__PURE__ */ jsx10("span", { className: "pactor-chat-composer__toolbar-spacer" }),
2199
+ (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ jsx10(
1646
2200
  "button",
1647
2201
  {
1648
2202
  ref: plusButtonRef,
@@ -1658,10 +2212,10 @@ function Composer({
1658
2212
  setPlusAnchor(rect);
1659
2213
  }
1660
2214
  },
1661
- children: /* @__PURE__ */ jsx7(Plus, { size: 14 })
2215
+ children: /* @__PURE__ */ jsx10(Icon6, { name: "plus", size: 14 })
1662
2216
  }
1663
2217
  ),
1664
- onAttachFiles && /* @__PURE__ */ jsx7(
2218
+ onAttachFiles && /* @__PURE__ */ jsx10(
1665
2219
  "button",
1666
2220
  {
1667
2221
  type: "button",
@@ -1669,11 +2223,11 @@ function Composer({
1669
2223
  title: "\u6DFB\u52A0\u9644\u4EF6",
1670
2224
  "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1671
2225
  onClick: () => fileInputRef.current?.click(),
1672
- children: /* @__PURE__ */ jsx7(Paperclip, { size: 14 })
2226
+ children: /* @__PURE__ */ jsx10(Icon6, { name: "paperclip", size: 14 })
1673
2227
  }
1674
2228
  ),
1675
- modelChipEnabled && /* @__PURE__ */ jsx7(
1676
- ModelChip,
2229
+ modelChipEnabled && /* @__PURE__ */ jsx10(
2230
+ ModelSelect,
1677
2231
  {
1678
2232
  models,
1679
2233
  model,
@@ -1682,111 +2236,206 @@ function Composer({
1682
2236
  ),
1683
2237
  toolbarEnd
1684
2238
  ] }),
1685
- onAttachFiles && /* @__PURE__ */ jsx7(
1686
- "input",
1687
- {
1688
- ref: fileInputRef,
1689
- type: "file",
1690
- multiple: true,
1691
- hidden: true,
1692
- onChange: (event) => {
1693
- const files = event.target.files ? Array.from(event.target.files) : [];
1694
- if (files.length > 0) {
1695
- onAttachFiles(files);
1696
- }
1697
- event.target.value = "";
1698
- }
1699
- }
1700
- ),
1701
- builtinMentionEnabled && mentionState && /* @__PURE__ */ jsx7(
1702
- AnchoredPopover,
1703
- {
1704
- anchorRect: mentionState.anchorRect,
1705
- onClose: () => setMentionState(null),
1706
- role: "listbox",
1707
- children: /* @__PURE__ */ jsx7(
1708
- ContextSourceList,
1709
- {
1710
- sources: mentionSources,
1711
- query: mentionState.query,
1712
- callService,
1713
- cache: sourceCacheRef.current,
1714
- onSelect: handleSelectContext
1715
- }
1716
- )
1717
- }
1718
- ),
1719
- builtinPlusEnabled && plusAnchor && /* @__PURE__ */ jsx7(
1720
- AnchoredPopover,
1721
- {
1722
- anchorRect: plusAnchor,
1723
- onClose: () => setPlusAnchor(null),
1724
- role: "listbox",
1725
- children: /* @__PURE__ */ jsx7(
1726
- ContextSourceList,
1727
- {
1728
- sources: plusSources,
1729
- query: "",
1730
- callService,
1731
- cache: sourceCacheRef.current,
1732
- onSelect: handleSelectContext
1733
- }
1734
- )
1735
- }
1736
- )
2239
+ composerFooter !== void 0 && composerFooter !== null ? /* @__PURE__ */ jsx10("div", { className: "pactor-chat-composer__footer", children: composerFooter }) : null,
2240
+ fileInputNode,
2241
+ popoverNodes
1737
2242
  ]
1738
2243
  }
1739
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
+ );
1740
2309
  }
1741
2310
 
1742
- // src/view/EmptyState.tsx
1743
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1744
- var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
2311
+ // src/view/chrome/empty-state.tsx
1745
2312
  function suggestionLabel(suggestion) {
1746
2313
  return typeof suggestion === "string" ? suggestion : suggestion.label;
1747
2314
  }
1748
2315
  function suggestionPrompt(suggestion) {
1749
2316
  return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
1750
2317
  }
1751
- function EmptyState({
1752
- 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({
1753
2386
  subtitle,
1754
2387
  suggestions,
1755
- onSuggestionClick
2388
+ onSuggestionClick,
2389
+ greeting
1756
2390
  }) {
1757
- return /* @__PURE__ */ jsxs7("div", { className: "pactor-chat-empty", children: [
1758
- /* @__PURE__ */ jsx8("div", { className: "pactor-chat-empty__greeting", children: greeting }),
1759
- subtitle && /* @__PURE__ */ jsx8("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
1760
- 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(
1761
2395
  "button",
1762
2396
  {
1763
2397
  type: "button",
1764
2398
  className: "pactor-chat-empty__suggestion",
1765
- onClick: () => onSuggestionClick?.(suggestionPrompt(suggestion)),
1766
- children: suggestionLabel(suggestion)
2399
+ onClick: () => onSuggestionClick?.(suggestionPrompt2(suggestion)),
2400
+ children: suggestionLabel2(suggestion)
1767
2401
  },
1768
- suggestionLabel(suggestion)
2402
+ suggestionLabel2(suggestion)
1769
2403
  )) })
1770
2404
  ] });
1771
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
+ }
1772
2428
 
1773
2429
  // src/view/MessageBubble.tsx
1774
- import { useRef as useRef8, useState as useState12 } from "react";
2430
+ import { useRef as useRef9, useState as useState11 } from "react";
1775
2431
 
1776
2432
  // src/view/ActivityTrace.tsx
1777
- import {
1778
- Brain,
1779
- CheckCircle2,
1780
- ChevronRight,
1781
- Clock,
1782
- Loader2,
1783
- XCircle
1784
- } from "lucide-react";
2433
+ import { Icon as Icon7 } from "@pactor-app/ui/components";
1785
2434
  import { MarkdownRenderer as MarkdownRenderer3 } from "@pactor-app/markdown";
1786
- import { useEffect as useEffect5, useRef as useRef7, useState as useState10 } from "react";
1787
- 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";
1788
2437
  function usePinnedCollapse(autoOpen) {
1789
- const [state, setState] = useState10("auto");
2438
+ const [state, setState] = useState9("auto");
1790
2439
  const open = state === "auto" ? autoOpen : state === "pinned-open";
1791
2440
  const toggle = () => {
1792
2441
  setState(open ? "pinned-closed" : "pinned-open");
@@ -1812,8 +2461,8 @@ function statusText(message, mode, toolCount) {
1812
2461
  }
1813
2462
  function useElapsedSeconds(startedAt, active) {
1814
2463
  const compute = () => Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1815
- const [elapsed, setElapsed] = useState10(compute);
1816
- useEffect5(() => {
2464
+ const [elapsed, setElapsed] = useState9(compute);
2465
+ useEffect6(() => {
1817
2466
  if (!active) {
1818
2467
  setElapsed(compute());
1819
2468
  return;
@@ -1827,15 +2476,15 @@ function useElapsedSeconds(startedAt, active) {
1827
2476
  }
1828
2477
  function TraceStatusIcon({ status }) {
1829
2478
  if (status === "running") {
1830
- return /* @__PURE__ */ jsx9(Loader2, { size: 13, className: "pactor-chat-spin" });
2479
+ return /* @__PURE__ */ jsx12(Icon7, { name: "loading", size: 13, className: "pactor-chat-spin" });
1831
2480
  }
1832
2481
  if (status === "success") {
1833
- return /* @__PURE__ */ jsx9(CheckCircle2, { size: 13 });
2482
+ return /* @__PURE__ */ jsx12(Icon7, { name: "check-circle", size: 13 });
1834
2483
  }
1835
2484
  if (status === "failed") {
1836
- return /* @__PURE__ */ jsx9(XCircle, { size: 13 });
2485
+ return /* @__PURE__ */ jsx12(Icon7, { name: "x-circle", size: 13 });
1837
2486
  }
1838
- return /* @__PURE__ */ jsx9(Clock, { size: 13 });
2487
+ return /* @__PURE__ */ jsx12(Icon7, { name: "clock", size: 13 });
1839
2488
  }
1840
2489
  function summarizeArgs(args) {
1841
2490
  if (args === void 0) {
@@ -1864,7 +2513,7 @@ function formatDetail(value) {
1864
2513
  }
1865
2514
  }
1866
2515
  function TraceRow({ entry }) {
1867
- const [open, setOpen] = useState10(false);
2516
+ const [open, setOpen] = useState9(false);
1868
2517
  const summary = summarizeArgs(entry.args);
1869
2518
  return /* @__PURE__ */ jsxs8(
1870
2519
  "div",
@@ -1881,16 +2530,16 @@ function TraceRow({ entry }) {
1881
2530
  "aria-expanded": open,
1882
2531
  onClick: () => setOpen((value) => !value),
1883
2532
  children: [
1884
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx9(ChevronRight, { size: 12 }) }),
1885
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ jsx9(TraceStatusIcon, { status: entry.status }) }),
1886
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
1887
- 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 })
1888
2537
  ]
1889
2538
  }
1890
2539
  ),
1891
2540
  open && /* @__PURE__ */ jsxs8("div", { className: "pactor-chat-trace__entry-body", children: [
1892
- entry.args !== void 0 && /* @__PURE__ */ jsx9("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
1893
- /* @__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) })
1894
2543
  ] })
1895
2544
  ]
1896
2545
  }
@@ -1917,13 +2566,13 @@ function ThinkingCard({
1917
2566
  "aria-expanded": open,
1918
2567
  onClick: toggle,
1919
2568
  children: [
1920
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx9(ChevronRight, { size: 12 }) }),
1921
- /* @__PURE__ */ jsx9("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ jsx9(Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ jsx9(Brain, { size: 13 }) }),
1922
- /* @__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" })
1923
2572
  ]
1924
2573
  }
1925
2574
  ),
1926
- 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 }) })
1927
2576
  ]
1928
2577
  }
1929
2578
  );
@@ -1945,9 +2594,9 @@ function ActivityTrace({ message }) {
1945
2594
  message.createdAt ?? mountTimeRef.current,
1946
2595
  streaming
1947
2596
  );
1948
- const header = /* @__PURE__ */ jsxs8(Fragment5, { children: [
1949
- /* @__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 }) }),
1950
- /* @__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(
1951
2600
  "span",
1952
2601
  {
1953
2602
  className: `pactor-chat-trace__status-text${streaming ? " pactor-chat-trace__status-text--breathing" : ""}`,
@@ -1958,10 +2607,10 @@ function ActivityTrace({ message }) {
1958
2607
  elapsed,
1959
2608
  "s"
1960
2609
  ] }),
1961
- 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 }) })
1962
2611
  ] });
1963
2612
  return /* @__PURE__ */ jsxs8("div", { className: "pactor-chat-trace", "data-open": open, children: [
1964
- entries.length > 0 ? /* @__PURE__ */ jsx9(
2613
+ entries.length > 0 ? /* @__PURE__ */ jsx12(
1965
2614
  "button",
1966
2615
  {
1967
2616
  type: "button",
@@ -1970,8 +2619,8 @@ function ActivityTrace({ message }) {
1970
2619
  onClick: toggle,
1971
2620
  children: header
1972
2621
  }
1973
- ) : /* @__PURE__ */ jsx9("div", { className: "pactor-chat-trace__header", children: header }),
1974
- thinkingParts.map((part, index) => /* @__PURE__ */ jsx9(
2622
+ ) : /* @__PURE__ */ jsx12("div", { className: "pactor-chat-trace__header", children: header }),
2623
+ thinkingParts.map((part, index) => /* @__PURE__ */ jsx12(
1975
2624
  ThinkingCard,
1976
2625
  {
1977
2626
  part,
@@ -1979,36 +2628,66 @@ function ActivityTrace({ message }) {
1979
2628
  },
1980
2629
  part.id ?? index
1981
2630
  )),
1982
- 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)) })
1983
2632
  ] });
1984
2633
  }
1985
2634
 
1986
2635
  // src/view/MessageActions.tsx
1987
- import {
1988
- Bookmark,
1989
- Check as Check2,
1990
- Copy,
1991
- HelpCircle,
1992
- Pencil,
1993
- Quote,
1994
- RotateCcw,
1995
- Share2,
1996
- ThumbsDown,
1997
- ThumbsUp
1998
- } from "lucide-react";
1999
- import { useState as useState11 } from "react";
2000
- import { jsx as jsx10 } from "react/jsx-runtime";
2001
- var MESSAGE_ACTION_ICONS = {
2002
- copy: Copy,
2003
- check: Check2,
2004
- "rotate-ccw": RotateCcw,
2005
- pencil: Pencil,
2006
- quote: Quote,
2007
- "thumbs-up": ThumbsUp,
2008
- "thumbs-down": ThumbsDown,
2009
- share: Share2,
2010
- bookmark: Bookmark
2011
- };
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";
2012
2691
  var BUILTIN_ACTION_DEFAULTS = {
2013
2692
  copy: {
2014
2693
  label: "\u590D\u5236",
@@ -2081,7 +2760,21 @@ function MessageActions({
2081
2760
  callService,
2082
2761
  onMessageAction
2083
2762
  }) {
2084
- 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]);
2085
2778
  const copy = async () => {
2086
2779
  try {
2087
2780
  await navigator.clipboard.writeText(collectMessageText(message));
@@ -2141,41 +2834,53 @@ function MessageActions({
2141
2834
  }
2142
2835
  onMessageAction?.(action.id, message);
2143
2836
  };
2144
- 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(
2145
2858
  "div",
2146
2859
  {
2147
2860
  className: `pactor-chat-message__actions${forceVisible ? " pactor-chat-message__actions--visible" : ""}`,
2148
- children: visibleActions.map((action) => {
2149
- const copiedFeedback = action.builtin === "copy" && copied;
2150
- const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2151
- const iconName = copiedFeedback ? "check" : action.icon;
2152
- const Icon = iconName !== void 0 ? MESSAGE_ACTION_ICONS[iconName] : void 0;
2153
- return /* @__PURE__ */ jsx10(
2154
- "button",
2155
- {
2156
- type: "button",
2157
- className: "pactor-chat-icon-button",
2158
- title: label,
2159
- "aria-label": label,
2160
- "data-message-action": action.id,
2161
- onClick: () => dispatch(action),
2162
- children: Icon ? /* @__PURE__ */ jsx10(Icon, { size: 14 }) : /* @__PURE__ */ jsx10(HelpCircle, { size: 14, "data-icon-placeholder": "true" })
2163
- },
2164
- action.id
2165
- );
2166
- })
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
+ )
2167
2872
  }
2168
2873
  );
2169
2874
  }
2170
2875
 
2171
2876
  // src/view/MessageBubble.tsx
2172
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2877
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2173
2878
  function MessageEditForm({
2174
2879
  initialText,
2175
2880
  onConfirm,
2176
2881
  onCancel
2177
2882
  }) {
2178
- const [text, setText] = useState12(initialText);
2883
+ const [text, setText] = useState11(initialText);
2179
2884
  const confirm = () => {
2180
2885
  const value = text.trim();
2181
2886
  if (!value) {
@@ -2184,7 +2889,7 @@ function MessageEditForm({
2184
2889
  onConfirm(value);
2185
2890
  };
2186
2891
  return /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__edit", children: [
2187
- /* @__PURE__ */ jsx11(
2892
+ /* @__PURE__ */ jsx14(
2188
2893
  "textarea",
2189
2894
  {
2190
2895
  className: "pactor-chat-message__edit-input",
@@ -2203,7 +2908,7 @@ function MessageEditForm({
2203
2908
  }
2204
2909
  ),
2205
2910
  /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__edit-actions", children: [
2206
- /* @__PURE__ */ jsx11(
2911
+ /* @__PURE__ */ jsx14(
2207
2912
  "button",
2208
2913
  {
2209
2914
  type: "button",
@@ -2213,7 +2918,7 @@ function MessageEditForm({
2213
2918
  children: "\u53D1\u9001"
2214
2919
  }
2215
2920
  ),
2216
- /* @__PURE__ */ jsx11(
2921
+ /* @__PURE__ */ jsx14(
2217
2922
  "button",
2218
2923
  {
2219
2924
  type: "button",
@@ -2239,7 +2944,7 @@ function MessageBubble({
2239
2944
  onMessageAction,
2240
2945
  onOpenArtifact
2241
2946
  }) {
2242
- const [editing, setEditing] = useState12(false);
2947
+ const [editing, setEditing] = useState11(false);
2243
2948
  const bubbleScope = {
2244
2949
  conversation: scope?.conversation ?? {},
2245
2950
  message: { id: message.id },
@@ -2247,7 +2952,7 @@ function MessageBubble({
2247
2952
  };
2248
2953
  const streaming = message.status === "streaming";
2249
2954
  const live = message.status === "pending" || message.status === "streaming";
2250
- const wasLiveRef = useRef8(live);
2955
+ const wasLiveRef = useRef9(live);
2251
2956
  if (live) {
2252
2957
  wasLiveRef.current = true;
2253
2958
  }
@@ -2261,7 +2966,7 @@ function MessageBubble({
2261
2966
  "data-message-id": message.id,
2262
2967
  "data-message-status": message.status,
2263
2968
  children: [
2264
- editing ? /* @__PURE__ */ jsx11(
2969
+ editing ? /* @__PURE__ */ jsx14(
2265
2970
  MessageEditForm,
2266
2971
  {
2267
2972
  initialText: collectMessageText(message),
@@ -2272,8 +2977,8 @@ function MessageBubble({
2272
2977
  onCancel: () => setEditing(false)
2273
2978
  }
2274
2979
  ) : /* @__PURE__ */ jsxs9("div", { className: "pactor-chat-message__body", children: [
2275
- showTrace && /* @__PURE__ */ jsx11(ActivityTrace, { message }),
2276
- 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(
2277
2982
  PartRenderer,
2278
2983
  {
2279
2984
  part,
@@ -2285,7 +2990,7 @@ function MessageBubble({
2285
2990
  part.id ?? index
2286
2991
  ))
2287
2992
  ] }),
2288
- !editing && /* @__PURE__ */ jsx11(
2993
+ !editing && /* @__PURE__ */ jsx14(
2289
2994
  MessageActions,
2290
2995
  {
2291
2996
  message,
@@ -2305,7 +3010,7 @@ function MessageBubble({
2305
3010
  }
2306
3011
 
2307
3012
  // src/view/ChatView.tsx
2308
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3013
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2309
3014
  function lastContentLength(messages) {
2310
3015
  const last = messages[messages.length - 1];
2311
3016
  if (!last) {
@@ -2339,6 +3044,8 @@ function ChatView({
2339
3044
  onAttachmentClick,
2340
3045
  toolbarStart,
2341
3046
  toolbarEnd,
3047
+ composerFooter,
3048
+ hideToolbar,
2342
3049
  onMentionTrigger,
2343
3050
  onPlusMenu,
2344
3051
  models,
@@ -2349,7 +3056,8 @@ function ChatView({
2349
3056
  onContextsChange,
2350
3057
  callService,
2351
3058
  messageActions,
2352
- onMessageAction
3059
+ onMessageAction,
3060
+ composerBottom
2353
3061
  }) {
2354
3062
  const messages = useSyncExternalStore(
2355
3063
  (listener) => store.subscribe(listener),
@@ -2379,8 +3087,8 @@ function ChatView({
2379
3087
  () => ({ conversation: { id: conversationId }, params }),
2380
3088
  [conversationId, params]
2381
3089
  );
2382
- const notifiedArtifactsRef = useRef9(/* @__PURE__ */ new Set());
2383
- useEffect6(() => {
3090
+ const notifiedArtifactsRef = useRef10(/* @__PURE__ */ new Set());
3091
+ useEffect8(() => {
2384
3092
  if (!onArtifact) {
2385
3093
  return;
2386
3094
  }
@@ -2399,7 +3107,7 @@ function ChatView({
2399
3107
  });
2400
3108
  }
2401
3109
  }, [messages, onArtifact]);
2402
- const abortRef = useRef9(null);
3110
+ const abortRef = useRef10(null);
2403
3111
  const { containerRef, isFollowing, scrollToBottom } = useChatAutoScroll({
2404
3112
  messageCount: messages.length,
2405
3113
  lastContentLength: lastContentLength(messages),
@@ -2410,7 +3118,7 @@ function ChatView({
2410
3118
  model,
2411
3119
  onModelChange
2412
3120
  });
2413
- const [contextChips, setContextChips] = useState13([]);
3121
+ const [contextChips, setContextChips] = useState12([]);
2414
3122
  const handleContextChipsChange = (chips) => {
2415
3123
  setContextChips(chips);
2416
3124
  onContextsChange?.(chips);
@@ -2442,6 +3150,16 @@ function ChatView({
2442
3150
  abortRef.current?.abort();
2443
3151
  abortRef.current = null;
2444
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
+ }, []);
2445
3163
  const handleRegenerate = () => {
2446
3164
  if (!transport) {
2447
3165
  return;
@@ -2459,7 +3177,7 @@ function ChatView({
2459
3177
  const lastMessage = messages[messages.length - 1];
2460
3178
  const regenerableMessageId = lastMessage?.role === "assistant" && lastMessage.status === "completed" ? lastMessage.id : void 0;
2461
3179
  const isEmpty = messages.length === 0;
2462
- const composerNode = /* @__PURE__ */ jsx12(
3180
+ const composerNode = /* @__PURE__ */ jsx15(
2463
3181
  Composer,
2464
3182
  {
2465
3183
  onSend: handleSend,
@@ -2471,6 +3189,8 @@ function ChatView({
2471
3189
  onAttachmentClick,
2472
3190
  toolbarStart,
2473
3191
  toolbarEnd,
3192
+ composerFooter,
3193
+ hideToolbar,
2474
3194
  onMentionTrigger,
2475
3195
  onPlusMenu,
2476
3196
  models: modelPlacement === "header" ? void 0 : models,
@@ -2482,6 +3202,10 @@ function ChatView({
2482
3202
  callService
2483
3203
  }
2484
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
+ ] });
2485
3209
  return /* @__PURE__ */ jsxs10(
2486
3210
  "div",
2487
3211
  {
@@ -2489,7 +3213,7 @@ function ChatView({
2489
3213
  children: [
2490
3214
  /* @__PURE__ */ jsxs10("div", { className: "pactor-chat-messages", ref: containerRef, children: [
2491
3215
  isEmpty ? /* @__PURE__ */ jsxs10("div", { className: "pactor-chat-view__empty-stack", children: [
2492
- /* @__PURE__ */ jsx12(
3216
+ /* @__PURE__ */ jsx15(
2493
3217
  EmptyState,
2494
3218
  {
2495
3219
  greeting,
@@ -2498,8 +3222,8 @@ function ChatView({
2498
3222
  onSuggestionClick: handleSend
2499
3223
  }
2500
3224
  ),
2501
- composerNode
2502
- ] }) : messages.map((message) => /* @__PURE__ */ jsx12(
3225
+ composerDock
3226
+ ] }) : messages.map((message) => /* @__PURE__ */ jsx15(
2503
3227
  MessageBubble,
2504
3228
  {
2505
3229
  message,
@@ -2523,21 +3247,21 @@ function ChatView({
2523
3247
  className: "pactor-chat-scroll-bottom",
2524
3248
  onClick: scrollToBottom,
2525
3249
  children: [
2526
- /* @__PURE__ */ jsx12(ArrowDown, { size: 13 }),
3250
+ /* @__PURE__ */ jsx15(Icon9, { name: "arrow-down", size: 13 }),
2527
3251
  "\u56DE\u5230\u5E95\u90E8"
2528
3252
  ]
2529
3253
  }
2530
3254
  )
2531
3255
  ] }),
2532
- !isEmpty && composerNode
3256
+ !isEmpty && composerDock
2533
3257
  ]
2534
3258
  }
2535
3259
  );
2536
3260
  }
2537
3261
 
2538
3262
  // src/view/WorkspaceHeader.tsx
2539
- import { Download, Moon, Sun, Trash2 } from "lucide-react";
2540
- 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";
2541
3265
 
2542
3266
  // src/export-markdown.ts
2543
3267
  var ROLE_LABELS = {
@@ -2569,8 +3293,52 @@ function downloadMessagesAsMarkdown(title, messages) {
2569
3293
  URL.revokeObjectURL(url);
2570
3294
  }
2571
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
+
2572
3340
  // src/view/WorkspaceHeader.tsx
2573
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
3341
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
2574
3342
  function readCurrentTheme() {
2575
3343
  if (typeof document === "undefined") {
2576
3344
  return "light";
@@ -2590,10 +3358,10 @@ function WorkspaceHeader({
2590
3358
  onTitleChange
2591
3359
  }) {
2592
3360
  const hasActions = headerActions !== void 0 && headerActions.length > 0;
2593
- const [overrideTitle, setOverrideTitle] = useState14(null);
2594
- const [editing, setEditing] = useState14(false);
2595
- const [draft, setDraft] = useState14("");
2596
- 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);
2597
3365
  if (prevTitleRef.current !== title) {
2598
3366
  prevTitleRef.current = title;
2599
3367
  if (overrideTitle !== null) {
@@ -2601,7 +3369,7 @@ function WorkspaceHeader({
2601
3369
  }
2602
3370
  }
2603
3371
  const displayTitle = overrideTitle ?? title;
2604
- const [theme, setTheme] = useState14(readCurrentTheme);
3372
+ const [theme, setTheme] = useState13(readCurrentTheme);
2605
3373
  const commitTitle = () => {
2606
3374
  setEditing(false);
2607
3375
  const next = draft.trim();
@@ -2628,8 +3396,24 @@ function WorkspaceHeader({
2628
3396
  store.clear();
2629
3397
  onClear?.();
2630
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
+ }, []);
2631
3415
  const actionButtons = {
2632
- export: /* @__PURE__ */ jsx13(
3416
+ export: /* @__PURE__ */ jsx16(
2633
3417
  "button",
2634
3418
  {
2635
3419
  type: "button",
@@ -2637,11 +3421,11 @@ function WorkspaceHeader({
2637
3421
  title: "\u5BFC\u51FA Markdown",
2638
3422
  "aria-label": "\u5BFC\u51FA Markdown",
2639
3423
  onClick: handleExport,
2640
- children: /* @__PURE__ */ jsx13(Download, { size: 15 })
3424
+ children: /* @__PURE__ */ jsx16(Icon10, { name: "download", size: 15 })
2641
3425
  },
2642
3426
  "export"
2643
3427
  ),
2644
- theme: /* @__PURE__ */ jsx13(
3428
+ theme: /* @__PURE__ */ jsx16(
2645
3429
  "button",
2646
3430
  {
2647
3431
  type: "button",
@@ -2649,11 +3433,11 @@ function WorkspaceHeader({
2649
3433
  title: theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
2650
3434
  "aria-label": "\u5207\u6362\u660E\u6697\u4E3B\u9898",
2651
3435
  onClick: handleTheme,
2652
- 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 })
2653
3437
  },
2654
3438
  "theme"
2655
3439
  ),
2656
- clear: /* @__PURE__ */ jsx13(
3440
+ clear: /* @__PURE__ */ jsx16(
2657
3441
  "button",
2658
3442
  {
2659
3443
  type: "button",
@@ -2661,12 +3445,12 @@ function WorkspaceHeader({
2661
3445
  title: "\u6E05\u7A7A\u4F1A\u8BDD",
2662
3446
  "aria-label": "\u6E05\u7A7A\u4F1A\u8BDD",
2663
3447
  onClick: handleClear,
2664
- children: /* @__PURE__ */ jsx13(Trash2, { size: 15 })
3448
+ children: /* @__PURE__ */ jsx16(Icon10, { name: "trash", size: 15 })
2665
3449
  },
2666
3450
  "clear"
2667
3451
  )
2668
3452
  };
2669
- const titleNode = editableTitle ? editing ? /* @__PURE__ */ jsx13(
3453
+ const titleNode = editableTitle ? editing ? /* @__PURE__ */ jsx16(
2670
3454
  "input",
2671
3455
  {
2672
3456
  className: "pactor-chat-workspace__title-input",
@@ -2683,7 +3467,7 @@ function WorkspaceHeader({
2683
3467
  }
2684
3468
  }
2685
3469
  }
2686
- ) : /* @__PURE__ */ jsx13(
3470
+ ) : /* @__PURE__ */ jsx16(
2687
3471
  "button",
2688
3472
  {
2689
3473
  type: "button",
@@ -2695,7 +3479,7 @@ function WorkspaceHeader({
2695
3479
  },
2696
3480
  children: displayTitle
2697
3481
  }
2698
- ) : /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__title", children: title });
3482
+ ) : /* @__PURE__ */ jsx16("div", { className: "pactor-chat-workspace__title", children: title });
2699
3483
  return /* @__PURE__ */ jsxs11("header", { className: "pactor-chat-workspace__header", children: [
2700
3484
  headerStart !== void 0 ? (
2701
3485
  // header 位模型 chip(决策 D2):与 title 组成左端组,chip 在前
@@ -2705,19 +3489,28 @@ function WorkspaceHeader({
2705
3489
  ] })
2706
3490
  ) : titleNode,
2707
3491
  hasActions ? /* @__PURE__ */ jsxs11("div", { className: "pactor-chat-workspace__header-right", children: [
2708
- /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__header-actions", children: headerActions.map((action) => actionButtons[action]) }),
2709
- headerExtra !== void 0 ? /* @__PURE__ */ jsx13("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2710
- ] }) : 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
2711
3504
  ] });
2712
3505
  }
2713
3506
 
2714
3507
  // src/chat-composition.tsx
2715
- 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";
2716
3509
  function DslNodeSlotInner({ node }) {
2717
- const { renderChildren } = useRenderer3();
3510
+ const { renderChildren } = useRenderer4();
2718
3511
  return renderChildren([node]);
2719
3512
  }
2720
- var DslSlotGuard = class extends Component2 {
3513
+ var DslSlotGuard = class extends Component3 {
2721
3514
  constructor() {
2722
3515
  super(...arguments);
2723
3516
  this.state = { failed: false };
@@ -2735,7 +3528,7 @@ var DslSlotGuard = class extends Component2 {
2735
3528
  }
2736
3529
  };
2737
3530
  function DslNodeSlot({ node }) {
2738
- return /* @__PURE__ */ jsx14(DslSlotGuard, { children: /* @__PURE__ */ jsx14(DslNodeSlotInner, { node }) });
3531
+ return /* @__PURE__ */ jsx17(DslSlotGuard, { children: /* @__PURE__ */ jsx17(DslNodeSlotInner, { node }) });
2739
3532
  }
2740
3533
  function isDslNodeValue(value) {
2741
3534
  return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value) && typeof value.type === "string";
@@ -2745,7 +3538,7 @@ function resolveSlot(slot) {
2745
3538
  return void 0;
2746
3539
  }
2747
3540
  if (isDslNodeValue(slot)) {
2748
- return /* @__PURE__ */ jsx14(DslNodeSlot, { node: slot });
3541
+ return /* @__PURE__ */ jsx17(DslNodeSlot, { node: slot });
2749
3542
  }
2750
3543
  return slot;
2751
3544
  }
@@ -2775,7 +3568,11 @@ function ChatComposition({
2775
3568
  messageActions,
2776
3569
  onMessageAction,
2777
3570
  sidebar,
3571
+ toolbarStart,
2778
3572
  toolbarEnd,
3573
+ composerFooter,
3574
+ composerBottom,
3575
+ hideToolbar,
2779
3576
  className,
2780
3577
  onSend,
2781
3578
  artifact,
@@ -2785,12 +3582,12 @@ function ChatComposition({
2785
3582
  onArtifact,
2786
3583
  onOpenArtifact
2787
3584
  }) {
2788
- const storeRef = useRef11(null);
3585
+ const storeRef = useRef12(null);
2789
3586
  if (storeRef.current === null) {
2790
3587
  storeRef.current = services.store ?? services.storeFactory?.() ?? new ConversationStore();
2791
3588
  }
2792
3589
  const store = storeRef.current;
2793
- const artifactStoreRef = useRef11(null);
3590
+ const artifactStoreRef = useRef12(null);
2794
3591
  if (artifactStoreRef.current === null) {
2795
3592
  artifactStoreRef.current = services.artifactStore ?? new ArtifactStore();
2796
3593
  }
@@ -2814,7 +3611,7 @@ function ChatComposition({
2814
3611
  artifactStore.open(next);
2815
3612
  }
2816
3613
  };
2817
- const [attachments, setAttachments] = useState15([]);
3614
+ const [attachments, setAttachments] = useState14([]);
2818
3615
  const handleAttachFiles = services.onAttachFiles ? (files) => {
2819
3616
  void Promise.resolve(services.onAttachFiles(files)).then(
2820
3617
  (created) => {
@@ -2834,24 +3631,38 @@ function ChatComposition({
2834
3631
  model,
2835
3632
  onModelChange
2836
3633
  });
2837
- const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ jsx14(
2838
- ModelChip,
3634
+ const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ jsx17(
3635
+ ModelSelect,
2839
3636
  {
2840
3637
  models,
2841
3638
  model: currentModel,
2842
3639
  onModelChange: handleModelChange
2843
3640
  }
2844
3641
  ) : void 0;
2845
- 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: [
2846
3643
  services.toolbarEnd,
2847
3644
  toolbarEnd
2848
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;
2849
3660
  const workspace = /* @__PURE__ */ jsxs12(
2850
3661
  "div",
2851
3662
  {
2852
3663
  className: `pactor-chat-workspace${className ? ` ${className}` : ""}`,
2853
3664
  children: [
2854
- showHeader ? header ?? /* @__PURE__ */ jsx14(
3665
+ renderHeader ? header ?? /* @__PURE__ */ jsx17(
2855
3666
  WorkspaceHeader,
2856
3667
  {
2857
3668
  title,
@@ -2866,7 +3677,7 @@ function ChatComposition({
2866
3677
  onTitleChange
2867
3678
  }
2868
3679
  ) : null,
2869
- /* @__PURE__ */ jsx14(
3680
+ /* @__PURE__ */ jsx17(
2870
3681
  ChatView,
2871
3682
  {
2872
3683
  store,
@@ -2886,8 +3697,11 @@ function ChatComposition({
2886
3697
  onAttachmentsChange: setAttachments,
2887
3698
  onAttachFiles: handleAttachFiles,
2888
3699
  onAttachmentClick: services.onAttachmentClick,
2889
- toolbarStart: services.toolbarStart,
3700
+ toolbarStart: mergedToolbarStart,
2890
3701
  toolbarEnd: mergedToolbarEnd,
3702
+ composerFooter: mergedComposerFooter,
3703
+ composerBottom: mergedComposerBottom,
3704
+ hideToolbar,
2891
3705
  onMentionTrigger: services.onMentionTrigger,
2892
3706
  onPlusMenu: services.onPlusMenu,
2893
3707
  onArtifact,
@@ -2906,15 +3720,15 @@ function ChatComposition({
2906
3720
  ]
2907
3721
  }
2908
3722
  );
2909
- const main = sidebar ? /* @__PURE__ */ jsx14(AppShell, { sidebar, children: workspace }) : workspace;
3723
+ const main = sidebar ? /* @__PURE__ */ jsx17(AppShell, { sidebar, children: workspace }) : workspace;
2910
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;
2911
3725
  if (!panelFeatureUsed && !effectivePanelOpen) {
2912
3726
  return main;
2913
3727
  }
2914
3728
  return /* @__PURE__ */ jsxs12("div", { className: "pactor-chat-inspector-host", children: [
2915
3729
  main,
2916
- effectivePanelOpen ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
2917
- /* @__PURE__ */ jsx14(
3730
+ effectivePanelOpen ? /* @__PURE__ */ jsxs12(Fragment9, { children: [
3731
+ /* @__PURE__ */ jsx17(
2918
3732
  "div",
2919
3733
  {
2920
3734
  className: "pactor-chat-inspector__scrim",
@@ -2922,7 +3736,7 @@ function ChatComposition({
2922
3736
  onClick: handlePanelClose
2923
3737
  }
2924
3738
  ),
2925
- /* @__PURE__ */ jsx14(
3739
+ /* @__PURE__ */ jsx17(
2926
3740
  ArtifactPanel,
2927
3741
  {
2928
3742
  artifact: effectiveArtifact,
@@ -2938,20 +3752,20 @@ function ChatComposition({
2938
3752
 
2939
3753
  // src/chat-services.tsx
2940
3754
  import { createContext as createContext2, useContext as useContext2 } from "react";
2941
- import { jsx as jsx15 } from "react/jsx-runtime";
3755
+ import { jsx as jsx18 } from "react/jsx-runtime";
2942
3756
  var ChatServicesContext = createContext2(null);
2943
3757
  function ChatServicesProvider({
2944
3758
  value,
2945
3759
  children
2946
3760
  }) {
2947
- return /* @__PURE__ */ jsx15(ChatServicesContext.Provider, { value, children });
3761
+ return /* @__PURE__ */ jsx18(ChatServicesContext.Provider, { value, children });
2948
3762
  }
2949
3763
  function useChatServices() {
2950
3764
  return useContext2(ChatServicesContext);
2951
3765
  }
2952
3766
 
2953
3767
  // src/chat-workspace.tsx
2954
- import { jsx as jsx16 } from "react/jsx-runtime";
3768
+ import { jsx as jsx19 } from "react/jsx-runtime";
2955
3769
  function createChatWorkspace(services) {
2956
3770
  function ChatWorkspace({
2957
3771
  title,
@@ -2961,7 +3775,11 @@ function createChatWorkspace(services) {
2961
3775
  conversationId,
2962
3776
  params,
2963
3777
  sidebar,
3778
+ toolbarStart,
2964
3779
  toolbarEnd,
3780
+ composerFooter,
3781
+ composerBottom,
3782
+ hideToolbar,
2965
3783
  artifact,
2966
3784
  panelOpen,
2967
3785
  onPanelClose,
@@ -2984,7 +3802,7 @@ function createChatWorkspace(services) {
2984
3802
  onMessageAction,
2985
3803
  className
2986
3804
  }) {
2987
- return /* @__PURE__ */ jsx16(ChatServicesProvider, { value: services, children: /* @__PURE__ */ jsx16(
3805
+ return /* @__PURE__ */ jsx19(ChatServicesProvider, { value: services, children: /* @__PURE__ */ jsx19(
2988
3806
  ChatComposition,
2989
3807
  {
2990
3808
  services,
@@ -2995,15 +3813,17 @@ function createChatWorkspace(services) {
2995
3813
  conversationId,
2996
3814
  params,
2997
3815
  className,
2998
- showHeader: title !== void 0 || services.headerExtra !== void 0 || // header 位模型 chip(决策 D2):仅配置 models 也渲染极简头部
2999
- modelPlacement === "header" && models !== void 0 && models.length > 0,
3000
3816
  headerExtra: services.headerExtra,
3001
- sidebar: sidebar ? /* @__PURE__ */ jsx16(DslNodeSlot, { node: sidebar }) : void 0,
3002
- 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,
3003
3823
  artifact,
3004
3824
  panelOpen,
3005
3825
  onPanelClose,
3006
- inspector: inspector ? /* @__PURE__ */ jsx16(DslNodeSlot, { node: inspector }) : void 0,
3826
+ inspector: inspector ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: inspector }) : void 0,
3007
3827
  onArtifact,
3008
3828
  onOpenArtifact,
3009
3829
  headerActions,
@@ -3027,23 +3847,37 @@ function createChatWorkspace(services) {
3027
3847
  }
3028
3848
 
3029
3849
  // src/view/Chat.tsx
3030
- import {
3031
- Folder,
3032
- History,
3033
- MessageSquare,
3034
- Plus as Plus2,
3035
- Search,
3036
- Settings,
3037
- Star,
3038
- User
3039
- } from "lucide-react";
3040
- import {
3041
- Fragment as Fragment7,
3042
- useEffect as useEffect7
3043
- } from "react";
3850
+ import { 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
+ }
3044
3878
 
3045
3879
  // src/view/ChatShell.tsx
3046
- import { jsx as jsx17 } from "react/jsx-runtime";
3880
+ import { jsx as jsx20 } from "react/jsx-runtime";
3047
3881
  function ChatShell({
3048
3882
  menu,
3049
3883
  activeMenuKey,
@@ -3055,7 +3889,7 @@ function ChatShell({
3055
3889
  className,
3056
3890
  onMenuSelect
3057
3891
  }) {
3058
- const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx17(
3892
+ const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx20(
3059
3893
  ChatSidebarMenu,
3060
3894
  {
3061
3895
  menu,
@@ -3063,7 +3897,7 @@ function ChatShell({
3063
3897
  onMenuSelect
3064
3898
  }
3065
3899
  ) : null);
3066
- return /* @__PURE__ */ jsx17(
3900
+ return /* @__PURE__ */ jsx20(
3067
3901
  AppShell,
3068
3902
  {
3069
3903
  sidebar: sidebarContent,
@@ -3080,7 +3914,7 @@ function ChatShellDslAdapter({
3080
3914
  onMenuSelect,
3081
3915
  ...rest
3082
3916
  }) {
3083
- return /* @__PURE__ */ jsx17(
3917
+ return /* @__PURE__ */ jsx20(
3084
3918
  ChatShell,
3085
3919
  {
3086
3920
  ...rest,
@@ -3095,39 +3929,45 @@ function registerChatShellComponent(registry) {
3095
3929
  }
3096
3930
 
3097
3931
  // src/view/Chat.tsx
3098
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3099
- var MENU_ICONS = {
3100
- plus: Plus2,
3101
- chat: MessageSquare,
3102
- history: History,
3103
- settings: Settings,
3104
- star: Star,
3105
- search: Search,
3106
- user: User,
3107
- folder: Folder
3108
- };
3932
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
3109
3933
  function ChatSidebarMenu({
3110
3934
  menu,
3111
3935
  activeMenuKey,
3112
3936
  onMenuSelect
3113
3937
  }) {
3114
3938
  const drawer = useSidebarDrawer();
3115
- let lastGroup;
3116
- return /* @__PURE__ */ jsx18("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
3117
- const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
3118
- lastGroup = item.group;
3119
- const Icon = item.icon !== void 0 ? MENU_ICONS[item.icon] : void 0;
3120
- const active = item.key === activeMenuKey;
3121
- return /* @__PURE__ */ jsxs13(Fragment7, { children: [
3122
- groupLabel !== void 0 ? /* @__PURE__ */ jsx18("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
3123
- /* @__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(
3124
3965
  "button",
3125
3966
  {
3126
3967
  type: "button",
3127
3968
  className: `pactor-chat-menu__item${active ? " pactor-chat-menu__item--active" : ""}`,
3128
3969
  disabled: item.disabled === true,
3129
3970
  "aria-current": active ? "true" : void 0,
3130
- title: drawer?.collapsed ? item.label : void 0,
3131
3971
  onClick: () => {
3132
3972
  if (item.disabled) {
3133
3973
  return;
@@ -3136,21 +3976,26 @@ function ChatSidebarMenu({
3136
3976
  drawer?.close();
3137
3977
  },
3138
3978
  children: [
3139
- item.icon !== void 0 ? Icon ? /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ jsx18(Icon, { size: 15, strokeWidth: 1.7 }) }) : /* @__PURE__ */ jsx18(
3140
- "span",
3141
- {
3142
- className: "pactor-chat-menu__icon-placeholder",
3143
- "aria-hidden": true
3144
- }
3145
- ) : null,
3146
- /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__label", children: item.label }),
3147
- item.dot === true ? /* @__PURE__ */ jsx18("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
3148
- 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
3149
3983
  ]
3150
3984
  }
3151
- )
3152
- ] }, item.key);
3153
- }) });
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
+ );
3154
3999
  }
3155
4000
  function Chat({
3156
4001
  title,
@@ -3163,6 +4008,11 @@ function Chat({
3163
4008
  menu,
3164
4009
  activeMenuKey,
3165
4010
  sidebar,
4011
+ toolbarStart,
4012
+ toolbarEnd,
4013
+ composerFooter,
4014
+ composerBottom,
4015
+ hideToolbar,
3166
4016
  headerExtra,
3167
4017
  header,
3168
4018
  headerActions,
@@ -3190,7 +4040,7 @@ function Chat({
3190
4040
  onMenuSelect
3191
4041
  }) {
3192
4042
  const services = useChatServices();
3193
- useEffect7(() => {
4043
+ useEffect10(() => {
3194
4044
  if (!services) {
3195
4045
  console.warn(
3196
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"
@@ -3198,10 +4048,10 @@ function Chat({
3198
4048
  }
3199
4049
  }, [services]);
3200
4050
  if (!services) {
3201
- 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" });
3202
4052
  }
3203
4053
  const sidebarSlot = resolveSlot(sidebar);
3204
- const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx18(
4054
+ const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ jsx21(
3205
4055
  ChatSidebarMenu,
3206
4056
  {
3207
4057
  menu,
@@ -3209,7 +4059,7 @@ function Chat({
3209
4059
  onMenuSelect
3210
4060
  }
3211
4061
  ) : void 0);
3212
- return /* @__PURE__ */ jsx18(
4062
+ return /* @__PURE__ */ jsx21(
3213
4063
  ChatComposition,
3214
4064
  {
3215
4065
  services,
@@ -3220,9 +4070,14 @@ function Chat({
3220
4070
  conversationId,
3221
4071
  params,
3222
4072
  className,
3223
- showHeader: showHeader !== false,
4073
+ showHeader,
3224
4074
  header: resolveSlot(header),
3225
4075
  headerExtra: resolveSlot(headerExtra) ?? services.headerExtra,
4076
+ toolbarStart: resolveSlot(toolbarStart),
4077
+ toolbarEnd: resolveSlot(toolbarEnd),
4078
+ composerFooter: resolveSlot(composerFooter),
4079
+ composerBottom: resolveSlot(composerBottom),
4080
+ hideToolbar,
3226
4081
  headerActions,
3227
4082
  editableTitle,
3228
4083
  onExport,
@@ -3264,7 +4119,7 @@ function ChatDslAdapter({
3264
4119
  onTitleChange,
3265
4120
  ...rest
3266
4121
  }) {
3267
- return /* @__PURE__ */ jsx18(
4122
+ return /* @__PURE__ */ jsx21(
3268
4123
  Chat,
3269
4124
  {
3270
4125
  ...rest,
@@ -3291,11 +4146,11 @@ function registerChatComponent(registry) {
3291
4146
  }
3292
4147
 
3293
4148
  // src/view/ChatLayout.tsx
3294
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
4149
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
3295
4150
  function ChatLayout({ title, children }) {
3296
4151
  return /* @__PURE__ */ jsxs14("div", { className: "pactor-chat-layout", children: [
3297
- /* @__PURE__ */ jsx19("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
3298
- /* @__PURE__ */ jsx19("main", { className: "pactor-chat-layout__main", children })
4152
+ /* @__PURE__ */ jsx22("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
4153
+ /* @__PURE__ */ jsx22("main", { className: "pactor-chat-layout__main", children })
3299
4154
  ] });
3300
4155
  }
3301
4156
  var CHAT_LAYOUT_TYPE = "chat";
@@ -3324,8 +4179,6 @@ export {
3324
4179
  ConversationStore,
3325
4180
  DEFAULT_CHAT_GREETING,
3326
4181
  EmptyState,
3327
- MENU_ICONS,
3328
- MESSAGE_ACTION_ICONS,
3329
4182
  MessageActions,
3330
4183
  MessageBubble,
3331
4184
  PartRenderer,