@yushaw/sanqian-chat 0.2.1 → 0.2.3

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.
@@ -66,8 +66,19 @@ interface AppAgentConfig {
66
66
  description?: string;
67
67
  /** System prompt */
68
68
  systemPrompt?: string;
69
- /** Enabled tool names (without app prefix) */
69
+ /**
70
+ * Tools to enable. Supports:
71
+ * - Builtin: "file_ops", "run_bash", "web_search"
72
+ * - SDK: "myapp:tool_name" or just "tool_name" (auto-prefixed)
73
+ * - MCP: "mcp_server_tool"
74
+ * - Wildcard: ["*"]
75
+ */
70
76
  tools?: string[];
77
+ /**
78
+ * Skills to enable.
79
+ * @example ["pdf", "web-research", "xlsx"]
80
+ */
81
+ skills?: string[];
71
82
  }
72
83
  /**
73
84
  * Embedding configuration from Sanqian
@@ -207,6 +218,40 @@ declare class SanqianAppClient {
207
218
  * Delete a conversation
208
219
  */
209
220
  deleteConversation(conversationId: string): Promise<void>;
221
+ /**
222
+ * List available capabilities (tools, skills, agents)
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * // List all tools
227
+ * const tools = await client.listCapabilities({ type: 'tool' });
228
+ *
229
+ * // List builtin tools only
230
+ * const builtinTools = await client.listCapabilities({ type: 'tool', source: 'builtin' });
231
+ * ```
232
+ */
233
+ listCapabilities(options?: _yushaw_sanqian_sdk.ListCapabilitiesOptions): Promise<_yushaw_sanqian_sdk.Capability[]>;
234
+ /**
235
+ * Search capabilities by query
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const results = await client.searchCapabilities('file operations', { type: 'tool', limit: 5 });
240
+ * ```
241
+ */
242
+ searchCapabilities(query: string, options?: _yushaw_sanqian_sdk.SearchCapabilitiesOptions): Promise<_yushaw_sanqian_sdk.CapabilitySearchResult[]>;
243
+ /**
244
+ * List available tools
245
+ */
246
+ listTools(source?: 'builtin' | 'sdk' | 'mcp'): Promise<_yushaw_sanqian_sdk.ToolCapability[]>;
247
+ /**
248
+ * List available skills
249
+ */
250
+ listSkills(): Promise<_yushaw_sanqian_sdk.SkillCapability[]>;
251
+ /**
252
+ * List all available agents (not just own agents)
253
+ */
254
+ listAvailableAgents(): Promise<_yushaw_sanqian_sdk.AgentCapability[]>;
210
255
  /**
211
256
  * Get the underlying SDK instance
212
257
  * @internal This is for internal use by other sanqian-chat components
@@ -66,8 +66,19 @@ interface AppAgentConfig {
66
66
  description?: string;
67
67
  /** System prompt */
68
68
  systemPrompt?: string;
69
- /** Enabled tool names (without app prefix) */
69
+ /**
70
+ * Tools to enable. Supports:
71
+ * - Builtin: "file_ops", "run_bash", "web_search"
72
+ * - SDK: "myapp:tool_name" or just "tool_name" (auto-prefixed)
73
+ * - MCP: "mcp_server_tool"
74
+ * - Wildcard: ["*"]
75
+ */
70
76
  tools?: string[];
77
+ /**
78
+ * Skills to enable.
79
+ * @example ["pdf", "web-research", "xlsx"]
80
+ */
81
+ skills?: string[];
71
82
  }
72
83
  /**
73
84
  * Embedding configuration from Sanqian
@@ -207,6 +218,40 @@ declare class SanqianAppClient {
207
218
  * Delete a conversation
208
219
  */
209
220
  deleteConversation(conversationId: string): Promise<void>;
221
+ /**
222
+ * List available capabilities (tools, skills, agents)
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * // List all tools
227
+ * const tools = await client.listCapabilities({ type: 'tool' });
228
+ *
229
+ * // List builtin tools only
230
+ * const builtinTools = await client.listCapabilities({ type: 'tool', source: 'builtin' });
231
+ * ```
232
+ */
233
+ listCapabilities(options?: _yushaw_sanqian_sdk.ListCapabilitiesOptions): Promise<_yushaw_sanqian_sdk.Capability[]>;
234
+ /**
235
+ * Search capabilities by query
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const results = await client.searchCapabilities('file operations', { type: 'tool', limit: 5 });
240
+ * ```
241
+ */
242
+ searchCapabilities(query: string, options?: _yushaw_sanqian_sdk.SearchCapabilitiesOptions): Promise<_yushaw_sanqian_sdk.CapabilitySearchResult[]>;
243
+ /**
244
+ * List available tools
245
+ */
246
+ listTools(source?: 'builtin' | 'sdk' | 'mcp'): Promise<_yushaw_sanqian_sdk.ToolCapability[]>;
247
+ /**
248
+ * List available skills
249
+ */
250
+ listSkills(): Promise<_yushaw_sanqian_sdk.SkillCapability[]>;
251
+ /**
252
+ * List all available agents (not just own agents)
253
+ */
254
+ listAvailableAgents(): Promise<_yushaw_sanqian_sdk.AgentCapability[]>;
210
255
  /**
211
256
  * Get the underlying SDK instance
212
257
  * @internal This is for internal use by other sanqian-chat components
@@ -112,7 +112,8 @@ var SanqianAppClient = class {
112
112
  name: config.name,
113
113
  description: config.description,
114
114
  system_prompt: config.systemPrompt,
115
- tools: config.tools
115
+ tools: config.tools,
116
+ skills: config.skills
116
117
  };
117
118
  const result = await this.sdk.createAgent(sdkConfig);
118
119
  return { agentId: result.agent_id };
@@ -191,6 +192,53 @@ var SanqianAppClient = class {
191
192
  return this.sdk.deleteConversation(conversationId);
192
193
  }
193
194
  // ============================================================
195
+ // Capability Discovery
196
+ // ============================================================
197
+ /**
198
+ * List available capabilities (tools, skills, agents)
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * // List all tools
203
+ * const tools = await client.listCapabilities({ type: 'tool' });
204
+ *
205
+ * // List builtin tools only
206
+ * const builtinTools = await client.listCapabilities({ type: 'tool', source: 'builtin' });
207
+ * ```
208
+ */
209
+ async listCapabilities(options) {
210
+ return this.sdk.listCapabilities(options);
211
+ }
212
+ /**
213
+ * Search capabilities by query
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const results = await client.searchCapabilities('file operations', { type: 'tool', limit: 5 });
218
+ * ```
219
+ */
220
+ async searchCapabilities(query, options) {
221
+ return this.sdk.searchCapabilities(query, options);
222
+ }
223
+ /**
224
+ * List available tools
225
+ */
226
+ async listTools(source) {
227
+ return this.sdk.listTools(source);
228
+ }
229
+ /**
230
+ * List available skills
231
+ */
232
+ async listSkills() {
233
+ return this.sdk.listSkills();
234
+ }
235
+ /**
236
+ * List all available agents (not just own agents)
237
+ */
238
+ async listAvailableAgents() {
239
+ return this.sdk.listAvailableAgents();
240
+ }
241
+ // ============================================================
194
242
  // Internal Access (for FloatingWindow and other internal components)
195
243
  // ============================================================
196
244
  /**
@@ -257,6 +305,7 @@ var FloatingWindow = class _FloatingWindow {
257
305
  const { alwaysOnTop, showInTaskbar, preloadPath } = this.options;
258
306
  const initialBounds = this.getInitialBounds();
259
307
  const { minWidth, minHeight } = this.getMinSize();
308
+ const isWindows = process.platform === "win32";
260
309
  const win = new import_electron.BrowserWindow({
261
310
  width: initialBounds.width,
262
311
  height: initialBounds.height,
@@ -264,11 +313,13 @@ var FloatingWindow = class _FloatingWindow {
264
313
  y: initialBounds.y,
265
314
  show: false,
266
315
  frame: false,
267
- transparent: true,
316
+ // Windows: transparent 会移除 resize 边框,需要禁用
317
+ transparent: !isWindows,
268
318
  hasShadow: false,
269
319
  // Disable system shadow to avoid white border on macOS
270
320
  resizable: true,
271
- backgroundColor: "#00000000",
321
+ // Windows: 非透明时需要设置背景色(默认深色,renderer 会根据主题同步)
322
+ backgroundColor: isWindows ? "#1F1F1F" : "#00000000",
272
323
  minWidth,
273
324
  minHeight,
274
325
  alwaysOnTop,
@@ -623,6 +674,17 @@ var FloatingWindow = class _FloatingWindow {
623
674
  if (!activeInstance) return { success: false, error: "Window not available" };
624
675
  return { success: true, data: activeInstance.getResolvedUiConfig() };
625
676
  });
677
+ import_electron.ipcMain.handle("sanqian-chat:setBackgroundColor", (_event, params) => {
678
+ if (!activeInstance) return { success: false, error: "Window not available" };
679
+ const win = activeInstance.getWindow();
680
+ if (!win) return { success: false, error: "Window not available" };
681
+ try {
682
+ win.setBackgroundColor(params.color);
683
+ return { success: true };
684
+ } catch (e) {
685
+ return { success: false, error: e instanceof Error ? e.message : "Failed to set background color" };
686
+ }
687
+ });
626
688
  }
627
689
  // Public API
628
690
  show() {
@@ -711,6 +773,7 @@ var FloatingWindow = class _FloatingWindow {
711
773
  import_electron.ipcMain.removeHandler("sanqian-chat:setAlwaysOnTop");
712
774
  import_electron.ipcMain.removeHandler("sanqian-chat:getAlwaysOnTop");
713
775
  import_electron.ipcMain.removeHandler("sanqian-chat:getUiConfig");
776
+ import_electron.ipcMain.removeHandler("sanqian-chat:setBackgroundColor");
714
777
  ipcHandlersRegistered = false;
715
778
  }
716
779
  }
@@ -75,7 +75,8 @@ var SanqianAppClient = class {
75
75
  name: config.name,
76
76
  description: config.description,
77
77
  system_prompt: config.systemPrompt,
78
- tools: config.tools
78
+ tools: config.tools,
79
+ skills: config.skills
79
80
  };
80
81
  const result = await this.sdk.createAgent(sdkConfig);
81
82
  return { agentId: result.agent_id };
@@ -154,6 +155,53 @@ var SanqianAppClient = class {
154
155
  return this.sdk.deleteConversation(conversationId);
155
156
  }
156
157
  // ============================================================
158
+ // Capability Discovery
159
+ // ============================================================
160
+ /**
161
+ * List available capabilities (tools, skills, agents)
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // List all tools
166
+ * const tools = await client.listCapabilities({ type: 'tool' });
167
+ *
168
+ * // List builtin tools only
169
+ * const builtinTools = await client.listCapabilities({ type: 'tool', source: 'builtin' });
170
+ * ```
171
+ */
172
+ async listCapabilities(options) {
173
+ return this.sdk.listCapabilities(options);
174
+ }
175
+ /**
176
+ * Search capabilities by query
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const results = await client.searchCapabilities('file operations', { type: 'tool', limit: 5 });
181
+ * ```
182
+ */
183
+ async searchCapabilities(query, options) {
184
+ return this.sdk.searchCapabilities(query, options);
185
+ }
186
+ /**
187
+ * List available tools
188
+ */
189
+ async listTools(source) {
190
+ return this.sdk.listTools(source);
191
+ }
192
+ /**
193
+ * List available skills
194
+ */
195
+ async listSkills() {
196
+ return this.sdk.listSkills();
197
+ }
198
+ /**
199
+ * List all available agents (not just own agents)
200
+ */
201
+ async listAvailableAgents() {
202
+ return this.sdk.listAvailableAgents();
203
+ }
204
+ // ============================================================
157
205
  // Internal Access (for FloatingWindow and other internal components)
158
206
  // ============================================================
159
207
  /**
@@ -220,6 +268,7 @@ var FloatingWindow = class _FloatingWindow {
220
268
  const { alwaysOnTop, showInTaskbar, preloadPath } = this.options;
221
269
  const initialBounds = this.getInitialBounds();
222
270
  const { minWidth, minHeight } = this.getMinSize();
271
+ const isWindows = process.platform === "win32";
223
272
  const win = new BrowserWindow({
224
273
  width: initialBounds.width,
225
274
  height: initialBounds.height,
@@ -227,11 +276,13 @@ var FloatingWindow = class _FloatingWindow {
227
276
  y: initialBounds.y,
228
277
  show: false,
229
278
  frame: false,
230
- transparent: true,
279
+ // Windows: transparent 会移除 resize 边框,需要禁用
280
+ transparent: !isWindows,
231
281
  hasShadow: false,
232
282
  // Disable system shadow to avoid white border on macOS
233
283
  resizable: true,
234
- backgroundColor: "#00000000",
284
+ // Windows: 非透明时需要设置背景色(默认深色,renderer 会根据主题同步)
285
+ backgroundColor: isWindows ? "#1F1F1F" : "#00000000",
235
286
  minWidth,
236
287
  minHeight,
237
288
  alwaysOnTop,
@@ -586,6 +637,17 @@ var FloatingWindow = class _FloatingWindow {
586
637
  if (!activeInstance) return { success: false, error: "Window not available" };
587
638
  return { success: true, data: activeInstance.getResolvedUiConfig() };
588
639
  });
640
+ ipcMain.handle("sanqian-chat:setBackgroundColor", (_event, params) => {
641
+ if (!activeInstance) return { success: false, error: "Window not available" };
642
+ const win = activeInstance.getWindow();
643
+ if (!win) return { success: false, error: "Window not available" };
644
+ try {
645
+ win.setBackgroundColor(params.color);
646
+ return { success: true };
647
+ } catch (e) {
648
+ return { success: false, error: e instanceof Error ? e.message : "Failed to set background color" };
649
+ }
650
+ });
589
651
  }
590
652
  // Public API
591
653
  show() {
@@ -674,6 +736,7 @@ var FloatingWindow = class _FloatingWindow {
674
736
  ipcMain.removeHandler("sanqian-chat:setAlwaysOnTop");
675
737
  ipcMain.removeHandler("sanqian-chat:getAlwaysOnTop");
676
738
  ipcMain.removeHandler("sanqian-chat:getUiConfig");
739
+ ipcMain.removeHandler("sanqian-chat:setBackgroundColor");
677
740
  ipcHandlersRegistered = false;
678
741
  }
679
742
  }
@@ -144,6 +144,13 @@ interface SanqianChatAPI {
144
144
  data?: ChatUiConfigSerializable | null;
145
145
  error?: string;
146
146
  }>;
147
+ setBackgroundColor(params: {
148
+ color: string;
149
+ }): Promise<{
150
+ success: boolean;
151
+ error?: string;
152
+ }>;
153
+ getPlatform(): string;
147
154
  }
148
155
  declare global {
149
156
  interface Window {
@@ -36,7 +36,9 @@ var api = {
36
36
  hide: () => import_electron.ipcRenderer.invoke("sanqian-chat:hide"),
37
37
  setAlwaysOnTop: (params) => import_electron.ipcRenderer.invoke("sanqian-chat:setAlwaysOnTop", params),
38
38
  getAlwaysOnTop: () => import_electron.ipcRenderer.invoke("sanqian-chat:getAlwaysOnTop"),
39
- getUiConfig: () => import_electron.ipcRenderer.invoke("sanqian-chat:getUiConfig")
39
+ getUiConfig: () => import_electron.ipcRenderer.invoke("sanqian-chat:getUiConfig"),
40
+ setBackgroundColor: (params) => import_electron.ipcRenderer.invoke("sanqian-chat:setBackgroundColor", params),
41
+ getPlatform: () => process.platform
40
42
  };
41
43
  import_electron.contextBridge.exposeInMainWorld("sanqianChat", api);
42
44
  import_electron.contextBridge.exposeInMainWorld("__SANQIAN_CHAT_STYLE_MODE__", "full");
@@ -7624,9 +7624,37 @@ var SanqianChat = (0, import_react22.memo)(function SanqianChat2({
7624
7624
  });
7625
7625
 
7626
7626
  // src/renderer/components/FloatingChat.tsx
7627
+ var import_react24 = require("react");
7628
+
7629
+ // src/renderer/hooks/useWindowBackgroundSync.ts
7627
7630
  var import_react23 = require("react");
7631
+ var cachedPlatform = null;
7632
+ var getPlatform = () => {
7633
+ if (cachedPlatform !== null) return cachedPlatform;
7634
+ const api = typeof window !== "undefined" ? window.sanqianChat : null;
7635
+ if (api?.getPlatform) {
7636
+ cachedPlatform = api.getPlatform();
7637
+ return cachedPlatform;
7638
+ }
7639
+ return null;
7640
+ };
7641
+ var setBackgroundColor = (color) => {
7642
+ const api = typeof window !== "undefined" ? window.sanqianChat : null;
7643
+ if (api?.setBackgroundColor) {
7644
+ void api.setBackgroundColor({ color });
7645
+ }
7646
+ };
7647
+ function useWindowBackgroundSync(isDarkMode) {
7648
+ (0, import_react23.useEffect)(() => {
7649
+ if (getPlatform() !== "win32") return;
7650
+ const colors = getBaseColors(isDarkMode);
7651
+ setBackgroundColor(colors.bg);
7652
+ }, [isDarkMode]);
7653
+ }
7654
+
7655
+ // src/renderer/components/FloatingChat.tsx
7628
7656
  var import_jsx_runtime15 = require("react/jsx-runtime");
7629
- var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7657
+ var FloatingChat = (0, import_react24.memo)(function FloatingChat2({
7630
7658
  messages,
7631
7659
  isLoading,
7632
7660
  isStreaming,
@@ -7647,8 +7675,8 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7647
7675
  header,
7648
7676
  footer
7649
7677
  }) {
7650
- const chatContainerRef = (0, import_react23.useRef)(null);
7651
- const chatInputRef = (0, import_react23.useRef)(null);
7678
+ const chatContainerRef = (0, import_react24.useRef)(null);
7679
+ const chatInputRef = (0, import_react24.useRef)(null);
7652
7680
  useChatStyles();
7653
7681
  useFocusPersistence({
7654
7682
  containerRef: chatContainerRef,
@@ -7659,18 +7687,19 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7659
7687
  const themeMode = resolvedConfig?.theme ?? "auto";
7660
7688
  const { themeClass, isDarkMode } = useResolvedTheme(themeMode);
7661
7689
  const accentStyle = useAccentStyle(resolvedConfig?.accentColor);
7662
- const rootStyle = (0, import_react23.useMemo)(() => ({
7690
+ useWindowBackgroundSync(isDarkMode);
7691
+ const rootStyle = (0, import_react24.useMemo)(() => ({
7663
7692
  ...accentStyle || {},
7664
7693
  minHeight: "100vh",
7665
7694
  minWidth: "100vw"
7666
7695
  }), [accentStyle]);
7667
7696
  const resolvedLogo = logo ?? resolvedConfig?.logo;
7668
7697
  const resolvedLocale = resolvedConfig?.locale ?? locale;
7669
- const resolvedStrings = (0, import_react23.useMemo)(
7698
+ const resolvedStrings = (0, import_react24.useMemo)(
7670
7699
  () => resolveChatStrings(resolvedLocale, resolvedConfig?.strings),
7671
7700
  [resolvedLocale, resolvedConfig?.strings]
7672
7701
  );
7673
- const headerConfig = (0, import_react23.useMemo)(
7702
+ const headerConfig = (0, import_react24.useMemo)(
7674
7703
  () => {
7675
7704
  if (!resolvedConfig) {
7676
7705
  return resolvedLogo ? { logo: resolvedLogo } : void 0;
@@ -7683,7 +7712,7 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7683
7712
  [resolvedConfig, resolvedLogo]
7684
7713
  );
7685
7714
  const { logoNode, showPin, showClose, isPinned, togglePin, resolvedOnClose } = useChatHeader(headerConfig);
7686
- const emptyLogoNode = (0, import_react23.useMemo)(() => resolveLogoNode(resolvedLogo, "empty"), [resolvedLogo]);
7715
+ const emptyLogoNode = (0, import_react24.useMemo)(() => resolveLogoNode(resolvedLogo, "empty"), [resolvedLogo]);
7687
7716
  const inputPlaceholder = placeholder ?? resolvedStrings.inputPlaceholder;
7688
7717
  const showHeader = !!(header || logoNode || showPin || showClose);
7689
7718
  const defaultHeader = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("header", { className: "flex h-9 flex-shrink-0 items-center px-2 border-b chat-divider-border", children: [
@@ -7722,7 +7751,7 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7722
7751
  ] })
7723
7752
  ] });
7724
7753
  const resolvedHeader = header ?? (showHeader ? defaultHeader : null);
7725
- const defaultRenderMessage = (0, import_react23.useCallback)(
7754
+ const defaultRenderMessage = (0, import_react24.useCallback)(
7726
7755
  (message) => {
7727
7756
  if (message.role === "tool") return null;
7728
7757
  const isUser = message.role === "user";
@@ -7736,7 +7765,7 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7736
7765
  },
7737
7766
  [renderContent]
7738
7767
  );
7739
- const defaultRenderHitl = (0, import_react23.useCallback)(
7768
+ const defaultRenderHitl = (0, import_react24.useCallback)(
7740
7769
  (interrupt, onApprove, onReject) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg m-2", children: [
7741
7770
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "font-medium mb-2", children: interrupt.type === "approval_request" ? resolvedStrings.hitlApprovalRequired : resolvedStrings.hitlInputRequired }),
7742
7771
  interrupt.tool && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "text-sm mb-2", children: [
@@ -7810,10 +7839,10 @@ var FloatingChat = (0, import_react23.memo)(function FloatingChat2({
7810
7839
  });
7811
7840
 
7812
7841
  // src/renderer/components/HistoryList.tsx
7813
- var import_react24 = require("react");
7842
+ var import_react25 = require("react");
7814
7843
  var import_jsx_runtime16 = require("react/jsx-runtime");
7815
7844
  function DeleteButton({ onClick, title, colors }) {
7816
- const [isHovered, setIsHovered] = (0, import_react24.useState)(false);
7845
+ const [isHovered, setIsHovered] = (0, import_react25.useState)(false);
7817
7846
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7818
7847
  "button",
7819
7848
  {
@@ -7857,7 +7886,7 @@ function DeleteButton({ onClick, title, colors }) {
7857
7886
  );
7858
7887
  }
7859
7888
  function LoadMoreButton({ onClick, colors, children }) {
7860
- const [isHovered, setIsHovered] = (0, import_react24.useState)(false);
7889
+ const [isHovered, setIsHovered] = (0, import_react25.useState)(false);
7861
7890
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7862
7891
  "button",
7863
7892
  {
@@ -7907,7 +7936,7 @@ function formatRelativeTime(dateStr, strings) {
7907
7936
  return date.toLocaleDateString(void 0, { month: "short", day: "numeric" });
7908
7937
  }
7909
7938
  }
7910
- var HistoryList = (0, import_react24.memo)(function HistoryList2({
7939
+ var HistoryList = (0, import_react25.memo)(function HistoryList2({
7911
7940
  conversations,
7912
7941
  selectedId,
7913
7942
  isLoading,
@@ -7919,11 +7948,11 @@ var HistoryList = (0, import_react24.memo)(function HistoryList2({
7919
7948
  isDarkMode = false,
7920
7949
  strings = {}
7921
7950
  }) {
7922
- const [hoveredId, setHoveredId] = (0, import_react24.useState)(null);
7923
- const loadMoreRef = (0, import_react24.useRef)(null);
7924
- const isLoadingRef = (0, import_react24.useRef)(isLoading);
7951
+ const [hoveredId, setHoveredId] = (0, import_react25.useState)(null);
7952
+ const loadMoreRef = (0, import_react25.useRef)(null);
7953
+ const isLoadingRef = (0, import_react25.useRef)(isLoading);
7925
7954
  isLoadingRef.current = isLoading;
7926
- (0, import_react24.useEffect)(() => {
7955
+ (0, import_react25.useEffect)(() => {
7927
7956
  if (!hasMore || loadError || !onLoadMore) return;
7928
7957
  const sentinel = loadMoreRef.current;
7929
7958
  if (!sentinel) return;
@@ -8020,10 +8049,10 @@ var HistoryList = (0, import_react24.memo)(function HistoryList2({
8020
8049
  });
8021
8050
 
8022
8051
  // src/renderer/components/CompactChat.tsx
8023
- var import_react25 = require("react");
8052
+ var import_react26 = require("react");
8024
8053
  var import_react_dom = require("react-dom");
8025
8054
  var import_jsx_runtime17 = require("react/jsx-runtime");
8026
- var CompactChat = (0, import_react25.memo)(function CompactChat2({
8055
+ var CompactChat = (0, import_react26.memo)(function CompactChat2({
8027
8056
  adapter,
8028
8057
  config,
8029
8058
  logo,
@@ -8058,12 +8087,12 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8058
8087
  const { themeClass, isDarkMode: resolvedIsDarkMode } = useResolvedTheme(themeMode);
8059
8088
  const accentStyle = useAccentStyle(resolvedConfig?.accentColor);
8060
8089
  const resolvedLogo = logo ?? resolvedConfig?.logo;
8061
- const baseStrings = (0, import_react25.useMemo)(
8090
+ const baseStrings = (0, import_react26.useMemo)(
8062
8091
  () => resolveChatStrings(resolvedConfig?.locale, resolvedConfig?.strings),
8063
8092
  [resolvedConfig?.locale, resolvedConfig?.strings]
8064
8093
  );
8065
- const mergedStrings = (0, import_react25.useMemo)(() => ({ ...baseStrings, ...strings }), [baseStrings, strings]);
8066
- const headerConfig = (0, import_react25.useMemo)(
8094
+ const mergedStrings = (0, import_react26.useMemo)(() => ({ ...baseStrings, ...strings }), [baseStrings, strings]);
8095
+ const headerConfig = (0, import_react26.useMemo)(
8067
8096
  () => {
8068
8097
  if (!resolvedConfig) {
8069
8098
  return resolvedLogo ? { logo: resolvedLogo } : void 0;
@@ -8076,12 +8105,12 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8076
8105
  [resolvedConfig, resolvedLogo]
8077
8106
  );
8078
8107
  const { logoNode, showPin, showClose, isPinned, togglePin, resolvedOnClose } = useChatHeader(headerConfig);
8079
- const emptyLogoNode = (0, import_react25.useMemo)(() => resolveLogoNode(resolvedLogo, "empty"), [resolvedLogo]);
8080
- const chatContainerRef = (0, import_react25.useRef)(null);
8081
- const chatInputRef = (0, import_react25.useRef)(null);
8082
- const [showHistory, setShowHistory] = (0, import_react25.useState)(false);
8083
- const [connectionAlert, setConnectionAlert] = (0, import_react25.useState)(null);
8084
- const portalContainerRef = (0, import_react25.useRef)(inputPortalContainer ?? null);
8108
+ const emptyLogoNode = (0, import_react26.useMemo)(() => resolveLogoNode(resolvedLogo, "empty"), [resolvedLogo]);
8109
+ const chatContainerRef = (0, import_react26.useRef)(null);
8110
+ const chatInputRef = (0, import_react26.useRef)(null);
8111
+ const [showHistory, setShowHistory] = (0, import_react26.useState)(false);
8112
+ const [connectionAlert, setConnectionAlert] = (0, import_react26.useState)(null);
8113
+ const portalContainerRef = (0, import_react26.useRef)(inputPortalContainer ?? null);
8085
8114
  portalContainerRef.current = inputPortalContainer ?? null;
8086
8115
  const shouldRenderInputExternally = !!inputPortalContainer;
8087
8116
  const inputPlaceholder = placeholder ?? mergedStrings.inputPlaceholder;
@@ -8119,27 +8148,27 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8119
8148
  adapter,
8120
8149
  onError
8121
8150
  });
8122
- (0, import_react25.useEffect)(() => {
8151
+ (0, import_react26.useEffect)(() => {
8123
8152
  if (sendMessageRef) {
8124
8153
  sendMessageRef.current = chat.sendMessage;
8125
8154
  }
8126
8155
  }, [sendMessageRef, chat.sendMessage]);
8127
- (0, import_react25.useEffect)(() => {
8156
+ (0, import_react26.useEffect)(() => {
8128
8157
  if (newConversationRef) {
8129
8158
  newConversationRef.current = chat.newConversation;
8130
8159
  }
8131
8160
  }, [newConversationRef, chat.newConversation]);
8132
- (0, import_react25.useEffect)(() => {
8161
+ (0, import_react26.useEffect)(() => {
8133
8162
  if (parentFocusInputRef) {
8134
8163
  parentFocusInputRef.current = () => chatInputRef.current?.focus();
8135
8164
  }
8136
8165
  }, [parentFocusInputRef]);
8137
- (0, import_react25.useEffect)(() => {
8166
+ (0, import_react26.useEffect)(() => {
8138
8167
  if (parentSetTextRef) {
8139
8168
  parentSetTextRef.current = (text) => chatInputRef.current?.setValue(text);
8140
8169
  }
8141
8170
  }, [parentSetTextRef]);
8142
- (0, import_react25.useEffect)(() => {
8171
+ (0, import_react26.useEffect)(() => {
8143
8172
  if (onMessageReceived && chat.messages.length > 0) {
8144
8173
  const lastMessage = chat.messages[chat.messages.length - 1];
8145
8174
  if (lastMessage.role === "assistant" && !lastMessage.isStreaming) {
@@ -8147,12 +8176,12 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8147
8176
  }
8148
8177
  }
8149
8178
  }, [chat.messages, onMessageReceived]);
8150
- (0, import_react25.useEffect)(() => {
8179
+ (0, import_react26.useEffect)(() => {
8151
8180
  if (onLoadingChange) {
8152
8181
  onLoadingChange(chat.isLoading);
8153
8182
  }
8154
8183
  }, [chat.isLoading, onLoadingChange]);
8155
- (0, import_react25.useEffect)(() => {
8184
+ (0, import_react26.useEffect)(() => {
8156
8185
  if (onStateChange) {
8157
8186
  onStateChange({
8158
8187
  messages: chat.messages,
@@ -8160,19 +8189,19 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8160
8189
  });
8161
8190
  }
8162
8191
  }, [chat.messages, chat.conversationId, onStateChange]);
8163
- (0, import_react25.useEffect)(() => {
8192
+ (0, import_react26.useEffect)(() => {
8164
8193
  if (showHistory && connection.isConnected) {
8165
8194
  conversations.loadConversations();
8166
8195
  }
8167
8196
  }, [showHistory, connection.isConnected]);
8168
- const handleSelectConversation = (0, import_react25.useCallback)(
8197
+ const handleSelectConversation = (0, import_react26.useCallback)(
8169
8198
  async (id) => {
8170
8199
  await chat.loadConversation(id);
8171
8200
  setShowHistory(false);
8172
8201
  },
8173
8202
  [chat]
8174
8203
  );
8175
- const handleDeleteConversation = (0, import_react25.useCallback)(
8204
+ const handleDeleteConversation = (0, import_react26.useCallback)(
8176
8205
  async (id) => {
8177
8206
  await conversations.deleteConversation(id);
8178
8207
  if (id === chat.conversationId) {
@@ -8181,14 +8210,14 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8181
8210
  },
8182
8211
  [conversations, chat]
8183
8212
  );
8184
- const handleNewChat = (0, import_react25.useCallback)(() => {
8213
+ const handleNewChat = (0, import_react26.useCallback)(() => {
8185
8214
  chat.newConversation();
8186
8215
  setShowHistory(false);
8187
8216
  setTimeout(() => {
8188
8217
  chatInputRef.current?.focus();
8189
8218
  }, 0);
8190
8219
  }, [chat]);
8191
- (0, import_react25.useEffect)(() => {
8220
+ (0, import_react26.useEffect)(() => {
8192
8221
  const handleKeyDown = (e) => {
8193
8222
  const isMac2 = navigator.platform.includes("Mac");
8194
8223
  const modifierKey = isMac2 ? e.metaKey : e.ctrlKey;
@@ -8202,7 +8231,7 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8202
8231
  }, [handleNewChat]);
8203
8232
  const isMac = typeof navigator !== "undefined" && navigator.platform.includes("Mac");
8204
8233
  const shortcutKey = isMac ? "\u2318N" : "Ctrl+N";
8205
- const defaultRenderMessage = (0, import_react25.useCallback)((message) => {
8234
+ const defaultRenderMessage = (0, import_react26.useCallback)((message) => {
8206
8235
  if (message.role === "tool") return null;
8207
8236
  const isUser = message.role === "user";
8208
8237
  const hasToolCalls = message.toolCalls && message.toolCalls.length > 0;
@@ -8248,11 +8277,11 @@ var CompactChat = (0, import_react25.memo)(function CompactChat2({
8248
8277
  ] }) });
8249
8278
  }, [mergedStrings]);
8250
8279
  const containerClass = floating ? `chat-window-container ${themeClass} ${className}` : `flex h-full flex-col bg-[var(--chat-bg)] text-[var(--chat-text)] ${themeClass} ${className}`;
8251
- const baseColors = (0, import_react25.useMemo)(() => {
8280
+ const baseColors = (0, import_react26.useMemo)(() => {
8252
8281
  if (!floating) return null;
8253
8282
  return getBaseColors(resolvedIsDarkMode);
8254
8283
  }, [floating, resolvedIsDarkMode]);
8255
- const containerStyle = (0, import_react25.useMemo)(() => {
8284
+ const containerStyle = (0, import_react26.useMemo)(() => {
8256
8285
  if (!floating || !baseColors) return accentStyle;
8257
8286
  return {
8258
8287
  ...accentStyle,
@@ -7569,6 +7569,34 @@ var SanqianChat = memo10(function SanqianChat2({
7569
7569
 
7570
7570
  // src/renderer/components/FloatingChat.tsx
7571
7571
  import { memo as memo11, useCallback as useCallback12, useMemo as useMemo9, useRef as useRef11 } from "react";
7572
+
7573
+ // src/renderer/hooks/useWindowBackgroundSync.ts
7574
+ import { useEffect as useEffect16 } from "react";
7575
+ var cachedPlatform = null;
7576
+ var getPlatform = () => {
7577
+ if (cachedPlatform !== null) return cachedPlatform;
7578
+ const api = typeof window !== "undefined" ? window.sanqianChat : null;
7579
+ if (api?.getPlatform) {
7580
+ cachedPlatform = api.getPlatform();
7581
+ return cachedPlatform;
7582
+ }
7583
+ return null;
7584
+ };
7585
+ var setBackgroundColor = (color) => {
7586
+ const api = typeof window !== "undefined" ? window.sanqianChat : null;
7587
+ if (api?.setBackgroundColor) {
7588
+ void api.setBackgroundColor({ color });
7589
+ }
7590
+ };
7591
+ function useWindowBackgroundSync(isDarkMode) {
7592
+ useEffect16(() => {
7593
+ if (getPlatform() !== "win32") return;
7594
+ const colors = getBaseColors(isDarkMode);
7595
+ setBackgroundColor(colors.bg);
7596
+ }, [isDarkMode]);
7597
+ }
7598
+
7599
+ // src/renderer/components/FloatingChat.tsx
7572
7600
  import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
7573
7601
  var FloatingChat = memo11(function FloatingChat2({
7574
7602
  messages,
@@ -7603,6 +7631,7 @@ var FloatingChat = memo11(function FloatingChat2({
7603
7631
  const themeMode = resolvedConfig?.theme ?? "auto";
7604
7632
  const { themeClass, isDarkMode } = useResolvedTheme(themeMode);
7605
7633
  const accentStyle = useAccentStyle(resolvedConfig?.accentColor);
7634
+ useWindowBackgroundSync(isDarkMode);
7606
7635
  const rootStyle = useMemo9(() => ({
7607
7636
  ...accentStyle || {},
7608
7637
  minHeight: "100vh",
@@ -7754,7 +7783,7 @@ var FloatingChat = memo11(function FloatingChat2({
7754
7783
  });
7755
7784
 
7756
7785
  // src/renderer/components/HistoryList.tsx
7757
- import { memo as memo12, useState as useState14, useEffect as useEffect16, useRef as useRef12 } from "react";
7786
+ import { memo as memo12, useState as useState14, useEffect as useEffect17, useRef as useRef12 } from "react";
7758
7787
  import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
7759
7788
  function DeleteButton({ onClick, title, colors }) {
7760
7789
  const [isHovered, setIsHovered] = useState14(false);
@@ -7867,7 +7896,7 @@ var HistoryList = memo12(function HistoryList2({
7867
7896
  const loadMoreRef = useRef12(null);
7868
7897
  const isLoadingRef = useRef12(isLoading);
7869
7898
  isLoadingRef.current = isLoading;
7870
- useEffect16(() => {
7899
+ useEffect17(() => {
7871
7900
  if (!hasMore || loadError || !onLoadMore) return;
7872
7901
  const sentinel = loadMoreRef.current;
7873
7902
  if (!sentinel) return;
@@ -7964,7 +7993,7 @@ var HistoryList = memo12(function HistoryList2({
7964
7993
  });
7965
7994
 
7966
7995
  // src/renderer/components/CompactChat.tsx
7967
- import { memo as memo13, useRef as useRef13, useState as useState15, useEffect as useEffect17, useCallback as useCallback14, useMemo as useMemo10 } from "react";
7996
+ import { memo as memo13, useRef as useRef13, useState as useState15, useEffect as useEffect18, useCallback as useCallback14, useMemo as useMemo10 } from "react";
7968
7997
  import { createPortal } from "react-dom";
7969
7998
  import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
7970
7999
  var CompactChat = memo13(function CompactChat2({
@@ -8063,27 +8092,27 @@ var CompactChat = memo13(function CompactChat2({
8063
8092
  adapter,
8064
8093
  onError
8065
8094
  });
8066
- useEffect17(() => {
8095
+ useEffect18(() => {
8067
8096
  if (sendMessageRef) {
8068
8097
  sendMessageRef.current = chat.sendMessage;
8069
8098
  }
8070
8099
  }, [sendMessageRef, chat.sendMessage]);
8071
- useEffect17(() => {
8100
+ useEffect18(() => {
8072
8101
  if (newConversationRef) {
8073
8102
  newConversationRef.current = chat.newConversation;
8074
8103
  }
8075
8104
  }, [newConversationRef, chat.newConversation]);
8076
- useEffect17(() => {
8105
+ useEffect18(() => {
8077
8106
  if (parentFocusInputRef) {
8078
8107
  parentFocusInputRef.current = () => chatInputRef.current?.focus();
8079
8108
  }
8080
8109
  }, [parentFocusInputRef]);
8081
- useEffect17(() => {
8110
+ useEffect18(() => {
8082
8111
  if (parentSetTextRef) {
8083
8112
  parentSetTextRef.current = (text) => chatInputRef.current?.setValue(text);
8084
8113
  }
8085
8114
  }, [parentSetTextRef]);
8086
- useEffect17(() => {
8115
+ useEffect18(() => {
8087
8116
  if (onMessageReceived && chat.messages.length > 0) {
8088
8117
  const lastMessage = chat.messages[chat.messages.length - 1];
8089
8118
  if (lastMessage.role === "assistant" && !lastMessage.isStreaming) {
@@ -8091,12 +8120,12 @@ var CompactChat = memo13(function CompactChat2({
8091
8120
  }
8092
8121
  }
8093
8122
  }, [chat.messages, onMessageReceived]);
8094
- useEffect17(() => {
8123
+ useEffect18(() => {
8095
8124
  if (onLoadingChange) {
8096
8125
  onLoadingChange(chat.isLoading);
8097
8126
  }
8098
8127
  }, [chat.isLoading, onLoadingChange]);
8099
- useEffect17(() => {
8128
+ useEffect18(() => {
8100
8129
  if (onStateChange) {
8101
8130
  onStateChange({
8102
8131
  messages: chat.messages,
@@ -8104,7 +8133,7 @@ var CompactChat = memo13(function CompactChat2({
8104
8133
  });
8105
8134
  }
8106
8135
  }, [chat.messages, chat.conversationId, onStateChange]);
8107
- useEffect17(() => {
8136
+ useEffect18(() => {
8108
8137
  if (showHistory && connection.isConnected) {
8109
8138
  conversations.loadConversations();
8110
8139
  }
@@ -8132,7 +8161,7 @@ var CompactChat = memo13(function CompactChat2({
8132
8161
  chatInputRef.current?.focus();
8133
8162
  }, 0);
8134
8163
  }, [chat]);
8135
- useEffect17(() => {
8164
+ useEffect18(() => {
8136
8165
  const handleKeyDown = (e) => {
8137
8166
  const isMac2 = navigator.platform.includes("Mac");
8138
8167
  const modifierKey = isMac2 ? e.metaKey : e.ctrlKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yushaw/sanqian-chat",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Floating chat window SDK for Sanqian AI Assistant",
5
5
  "main": "./dist/main/index.js",
6
6
  "types": "./dist/main/index.d.ts",
@@ -43,7 +43,7 @@
43
43
  "test:watch": "vitest"
44
44
  },
45
45
  "dependencies": {
46
- "@yushaw/sanqian-sdk": "^0.3.0",
46
+ "@yushaw/sanqian-sdk": "^0.3.1",
47
47
  "react-virtuoso": "^4.15.0",
48
48
  "rehype-harden": "^1.1.6",
49
49
  "remark-gfm": "^4.0.1",