@yushaw/sanqian-chat 0.1.1 → 0.2.1
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/README.md +116 -0
- package/dist/core/index.d.mts +180 -6
- package/dist/core/index.d.ts +180 -6
- package/dist/core/index.js +548 -64
- package/dist/core/index.mjs +534 -63
- package/dist/main/index.d.mts +320 -5
- package/dist/main/index.d.ts +320 -5
- package/dist/main/index.js +466 -39
- package/dist/main/index.mjs +454 -38
- package/dist/preload/index.d.ts +88 -0
- package/dist/preload/index.js +5 -1
- package/dist/renderer/index.d.mts +609 -18
- package/dist/renderer/index.d.ts +609 -18
- package/dist/renderer/index.js +7870 -537
- package/dist/renderer/index.mjs +7854 -538
- package/package.json +16 -4
- package/src/renderer/styles/baseStyles.ts +74 -0
- package/src/renderer/styles/chat.css +3674 -0
- package/src/renderer/styles/chatCss.ts +11 -0
- package/src/renderer/styles/coreCss.ts +3237 -0
- package/src/renderer/styles/preflightCss.ts +454 -0
- package/src/renderer/styles/safe.css +3227 -0
- package/src/renderer/styles/safeCss.ts +10 -0
- package/src/renderer/styles/tailwind.css +683 -0
- package/src/renderer/styles/variables.css +704 -0
package/dist/main/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,51 +17,260 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/main/index.ts
|
|
21
31
|
var main_exports = {};
|
|
22
32
|
__export(main_exports, {
|
|
23
|
-
FloatingWindow: () => FloatingWindow
|
|
33
|
+
FloatingWindow: () => FloatingWindow,
|
|
34
|
+
SanqianAppClient: () => SanqianAppClient
|
|
24
35
|
});
|
|
25
36
|
module.exports = __toCommonJS(main_exports);
|
|
26
37
|
|
|
38
|
+
// src/main/client.ts
|
|
39
|
+
var import_sanqian_sdk = require("@yushaw/sanqian-sdk");
|
|
40
|
+
var SanqianAppClient = class {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
const tools = (config.tools || []).map((t) => ({
|
|
43
|
+
name: t.name,
|
|
44
|
+
description: t.description,
|
|
45
|
+
parameters: t.parameters,
|
|
46
|
+
handler: t.handler
|
|
47
|
+
}));
|
|
48
|
+
const sdkConfig = {
|
|
49
|
+
appName: config.appName,
|
|
50
|
+
appVersion: config.appVersion,
|
|
51
|
+
displayName: config.displayName,
|
|
52
|
+
launchCommand: config.launchCommand,
|
|
53
|
+
debug: config.debug,
|
|
54
|
+
tools
|
|
55
|
+
};
|
|
56
|
+
this.sdk = new import_sanqian_sdk.SanqianSDK(sdkConfig);
|
|
57
|
+
}
|
|
58
|
+
// ============================================================
|
|
59
|
+
// Connection Management
|
|
60
|
+
// ============================================================
|
|
61
|
+
/**
|
|
62
|
+
* Connect to Sanqian and register this application
|
|
63
|
+
*/
|
|
64
|
+
async connect() {
|
|
65
|
+
await this.sdk.connect();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Disconnect from Sanqian
|
|
69
|
+
*/
|
|
70
|
+
async disconnect() {
|
|
71
|
+
await this.sdk.disconnect();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if connected to Sanqian
|
|
75
|
+
*/
|
|
76
|
+
isConnected() {
|
|
77
|
+
return this.sdk.isConnected();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Ensure SDK is ready (connects if needed, waits for registration)
|
|
81
|
+
*/
|
|
82
|
+
async ensureReady() {
|
|
83
|
+
await this.sdk.ensureReady();
|
|
84
|
+
}
|
|
85
|
+
// ============================================================
|
|
86
|
+
// Reconnection Control (for Chat UI)
|
|
87
|
+
// ============================================================
|
|
88
|
+
/**
|
|
89
|
+
* Request persistent connection (enables auto-reconnect)
|
|
90
|
+
* Call when a component needs the connection to stay alive
|
|
91
|
+
*/
|
|
92
|
+
acquireReconnect() {
|
|
93
|
+
this.sdk.acquireReconnect();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Release persistent connection request
|
|
97
|
+
* Call when a component no longer needs the connection
|
|
98
|
+
*/
|
|
99
|
+
releaseReconnect() {
|
|
100
|
+
this.sdk.releaseReconnect();
|
|
101
|
+
}
|
|
102
|
+
// ============================================================
|
|
103
|
+
// Agent Management
|
|
104
|
+
// ============================================================
|
|
105
|
+
/**
|
|
106
|
+
* Create or update a private agent
|
|
107
|
+
* @returns The full agent ID (app_name:agent_id)
|
|
108
|
+
*/
|
|
109
|
+
async createAgent(config) {
|
|
110
|
+
const sdkConfig = {
|
|
111
|
+
agent_id: config.agentId,
|
|
112
|
+
name: config.name,
|
|
113
|
+
description: config.description,
|
|
114
|
+
system_prompt: config.systemPrompt,
|
|
115
|
+
tools: config.tools
|
|
116
|
+
};
|
|
117
|
+
const result = await this.sdk.createAgent(sdkConfig);
|
|
118
|
+
return { agentId: result.agent_id };
|
|
119
|
+
}
|
|
120
|
+
// ============================================================
|
|
121
|
+
// Embedding
|
|
122
|
+
// ============================================================
|
|
123
|
+
/**
|
|
124
|
+
* Get embedding configuration from Sanqian
|
|
125
|
+
* @returns Embedding config or null if not available
|
|
126
|
+
*/
|
|
127
|
+
async getEmbeddingConfig() {
|
|
128
|
+
try {
|
|
129
|
+
const config = await this.sdk.getEmbeddingConfig();
|
|
130
|
+
return {
|
|
131
|
+
available: config.available,
|
|
132
|
+
apiUrl: config.apiUrl,
|
|
133
|
+
apiKey: config.apiKey,
|
|
134
|
+
modelName: config.modelName,
|
|
135
|
+
dimensions: config.dimensions
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// ============================================================
|
|
142
|
+
// Events
|
|
143
|
+
// ============================================================
|
|
144
|
+
/**
|
|
145
|
+
* Subscribe to client events
|
|
146
|
+
*/
|
|
147
|
+
on(event, handler) {
|
|
148
|
+
this.sdk.on(event, handler);
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove all event listeners
|
|
153
|
+
*/
|
|
154
|
+
removeAllListeners() {
|
|
155
|
+
this.sdk.removeAllListeners();
|
|
156
|
+
}
|
|
157
|
+
// ============================================================
|
|
158
|
+
// Chat API
|
|
159
|
+
// ============================================================
|
|
160
|
+
/**
|
|
161
|
+
* Stream chat messages
|
|
162
|
+
*/
|
|
163
|
+
chatStream(agentId, messages, options) {
|
|
164
|
+
return this.sdk.chatStream(agentId, messages, options);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Send HITL (Human-in-the-Loop) response
|
|
168
|
+
*/
|
|
169
|
+
sendHitlResponse(runId, response) {
|
|
170
|
+
this.sdk.sendHitlResponse(runId, response);
|
|
171
|
+
}
|
|
172
|
+
// ============================================================
|
|
173
|
+
// Conversation Management
|
|
174
|
+
// ============================================================
|
|
175
|
+
/**
|
|
176
|
+
* List conversations for an agent
|
|
177
|
+
*/
|
|
178
|
+
async listConversations(options) {
|
|
179
|
+
return this.sdk.listConversations(options);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get conversation details
|
|
183
|
+
*/
|
|
184
|
+
async getConversation(conversationId, options) {
|
|
185
|
+
return this.sdk.getConversation(conversationId, options);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Delete a conversation
|
|
189
|
+
*/
|
|
190
|
+
async deleteConversation(conversationId) {
|
|
191
|
+
return this.sdk.deleteConversation(conversationId);
|
|
192
|
+
}
|
|
193
|
+
// ============================================================
|
|
194
|
+
// Internal Access (for FloatingWindow and other internal components)
|
|
195
|
+
// ============================================================
|
|
196
|
+
/**
|
|
197
|
+
* Get the underlying SDK instance
|
|
198
|
+
* @internal This is for internal use by other sanqian-chat components
|
|
199
|
+
*/
|
|
200
|
+
_getSdk() {
|
|
201
|
+
return this.sdk;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
27
205
|
// src/main/FloatingWindow.ts
|
|
28
206
|
var import_electron = require("electron");
|
|
207
|
+
var import_fs = __toESM(require("fs"));
|
|
208
|
+
var import_os = __toESM(require("os"));
|
|
209
|
+
var import_path = __toESM(require("path"));
|
|
29
210
|
var ipcHandlersRegistered = false;
|
|
30
211
|
var activeInstance = null;
|
|
31
|
-
var
|
|
212
|
+
var setActiveInstance = (instance) => {
|
|
213
|
+
activeInstance = instance;
|
|
214
|
+
};
|
|
215
|
+
var FloatingWindow = class _FloatingWindow {
|
|
32
216
|
constructor(options) {
|
|
33
217
|
this.window = null;
|
|
34
|
-
this.
|
|
218
|
+
this.savedState = null;
|
|
219
|
+
this.stateSaveTimer = null;
|
|
35
220
|
this.activeStreams = /* @__PURE__ */ new Map();
|
|
221
|
+
this.reconnectAcquired = false;
|
|
36
222
|
if (activeInstance) {
|
|
37
223
|
console.warn("[FloatingWindow] Only one instance supported. Destroying previous.");
|
|
38
224
|
activeInstance.destroy();
|
|
39
225
|
}
|
|
40
|
-
|
|
226
|
+
setActiveInstance(this);
|
|
41
227
|
this.options = {
|
|
42
228
|
width: 400,
|
|
43
229
|
height: 500,
|
|
44
230
|
alwaysOnTop: true,
|
|
45
231
|
showInTaskbar: false,
|
|
46
|
-
position
|
|
232
|
+
// position 默认不设置,让 rememberWindowState 优先读取已保存状态
|
|
233
|
+
// 若无保存状态则 fallback 到居中
|
|
234
|
+
rememberWindowState: true,
|
|
47
235
|
...options
|
|
48
236
|
};
|
|
237
|
+
this.loadWindowState();
|
|
49
238
|
this.setupIpcHandlers();
|
|
50
239
|
if (this.options.shortcut) {
|
|
51
240
|
import_electron.app.whenReady().then(() => this.registerShortcut());
|
|
52
241
|
}
|
|
53
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Get SDK instance from either getClient or getSdk
|
|
245
|
+
*/
|
|
246
|
+
static getSdkFromOptions(options) {
|
|
247
|
+
if (options.getClient) {
|
|
248
|
+
const client = options.getClient();
|
|
249
|
+
return client?._getSdk() ?? null;
|
|
250
|
+
}
|
|
251
|
+
if (options.getSdk) {
|
|
252
|
+
return options.getSdk();
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
54
256
|
createWindow() {
|
|
55
|
-
const {
|
|
257
|
+
const { alwaysOnTop, showInTaskbar, preloadPath } = this.options;
|
|
258
|
+
const initialBounds = this.getInitialBounds();
|
|
259
|
+
const { minWidth, minHeight } = this.getMinSize();
|
|
56
260
|
const win = new import_electron.BrowserWindow({
|
|
57
|
-
width,
|
|
58
|
-
height,
|
|
261
|
+
width: initialBounds.width,
|
|
262
|
+
height: initialBounds.height,
|
|
263
|
+
x: initialBounds.x,
|
|
264
|
+
y: initialBounds.y,
|
|
59
265
|
show: false,
|
|
60
266
|
frame: false,
|
|
61
267
|
transparent: true,
|
|
268
|
+
hasShadow: false,
|
|
269
|
+
// Disable system shadow to avoid white border on macOS
|
|
62
270
|
resizable: true,
|
|
271
|
+
backgroundColor: "#00000000",
|
|
272
|
+
minWidth,
|
|
273
|
+
minHeight,
|
|
63
274
|
alwaysOnTop,
|
|
64
275
|
skipTaskbar: !showInTaskbar,
|
|
65
276
|
webPreferences: {
|
|
@@ -75,19 +286,37 @@ var FloatingWindow = class {
|
|
|
75
286
|
}
|
|
76
287
|
win.on("blur", () => {
|
|
77
288
|
});
|
|
289
|
+
win.on("move", () => {
|
|
290
|
+
this.scheduleSaveWindowState();
|
|
291
|
+
});
|
|
292
|
+
win.on("resize", () => {
|
|
293
|
+
this.scheduleSaveWindowState();
|
|
294
|
+
});
|
|
295
|
+
win.on("close", () => {
|
|
296
|
+
this.saveWindowState();
|
|
297
|
+
});
|
|
78
298
|
win.on("closed", () => {
|
|
79
299
|
this.window = null;
|
|
80
300
|
});
|
|
81
301
|
return win;
|
|
82
302
|
}
|
|
83
|
-
|
|
84
|
-
const {
|
|
303
|
+
getInitialBounds() {
|
|
304
|
+
const { width = 400, height = 500 } = this.options;
|
|
305
|
+
const { minWidth, minHeight } = this.getMinSize();
|
|
306
|
+
const resolvedWidth = Math.max(width, minWidth);
|
|
307
|
+
const resolvedHeight = Math.max(height, minHeight);
|
|
308
|
+
const savedBounds = this.shouldUseSavedState() ? this.getSavedBounds() : null;
|
|
309
|
+
if (savedBounds) {
|
|
310
|
+
return savedBounds;
|
|
311
|
+
}
|
|
312
|
+
const pos = this.getInitialPosition(resolvedWidth, resolvedHeight);
|
|
313
|
+
return { ...pos, width: resolvedWidth, height: resolvedHeight };
|
|
314
|
+
}
|
|
315
|
+
getInitialPosition(width, height) {
|
|
316
|
+
const { position } = this.options;
|
|
85
317
|
if (typeof position === "object" && "x" in position) {
|
|
86
318
|
return position;
|
|
87
319
|
}
|
|
88
|
-
if (position === "remember" && this.savedPosition) {
|
|
89
|
-
return this.savedPosition;
|
|
90
|
-
}
|
|
91
320
|
if (position === "cursor") {
|
|
92
321
|
const cursorPos = import_electron.screen.getCursorScreenPoint();
|
|
93
322
|
const display = import_electron.screen.getDisplayNearestPoint(cursorPos);
|
|
@@ -96,10 +325,118 @@ var FloatingWindow = class {
|
|
|
96
325
|
return { x, y };
|
|
97
326
|
}
|
|
98
327
|
const primaryDisplay = import_electron.screen.getPrimaryDisplay();
|
|
99
|
-
const
|
|
328
|
+
const workArea = primaryDisplay.workArea;
|
|
329
|
+
return {
|
|
330
|
+
x: workArea.x + Math.round((workArea.width - width) / 2),
|
|
331
|
+
y: workArea.y + Math.round((workArea.height - height) / 2)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
shouldPersistWindowState() {
|
|
335
|
+
return Boolean(this.options.rememberWindowState || this.options.position === "remember");
|
|
336
|
+
}
|
|
337
|
+
shouldUseSavedState() {
|
|
338
|
+
const { position } = this.options;
|
|
339
|
+
if (position && position !== "remember") return false;
|
|
340
|
+
return this.shouldPersistWindowState();
|
|
341
|
+
}
|
|
342
|
+
getMinSize() {
|
|
343
|
+
return {
|
|
344
|
+
minWidth: this.options.minWidth ?? 320,
|
|
345
|
+
minHeight: this.options.minHeight ?? 420
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
getWindowStatePath() {
|
|
349
|
+
if (!this.shouldPersistWindowState()) return null;
|
|
350
|
+
if (this.options.windowStatePath) return this.options.windowStatePath;
|
|
351
|
+
const stateDir = import_path.default.join(import_os.default.homedir(), ".sanqian-chat");
|
|
352
|
+
const key = this.options.windowStateKey || import_electron.app.getName() || "sanqian-chat";
|
|
353
|
+
const safeKey = key.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
354
|
+
return import_path.default.join(stateDir, `${safeKey}-window.json`);
|
|
355
|
+
}
|
|
356
|
+
getResolvedUiConfig() {
|
|
357
|
+
const uiConfig = this.options.uiConfig ?? {};
|
|
358
|
+
const resolved = { ...uiConfig };
|
|
359
|
+
if (!resolved.theme && this.options.theme) {
|
|
360
|
+
resolved.theme = this.options.theme === "system" ? "auto" : this.options.theme;
|
|
361
|
+
}
|
|
362
|
+
if (typeof resolved.alwaysOnTop !== "boolean" && typeof this.options.alwaysOnTop === "boolean") {
|
|
363
|
+
resolved.alwaysOnTop = this.options.alwaysOnTop;
|
|
364
|
+
}
|
|
365
|
+
if (Object.keys(resolved).length === 0) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return resolved;
|
|
369
|
+
}
|
|
370
|
+
loadWindowState() {
|
|
371
|
+
const statePath = this.getWindowStatePath();
|
|
372
|
+
if (!statePath) return;
|
|
373
|
+
try {
|
|
374
|
+
const raw = import_fs.default.readFileSync(statePath, "utf8");
|
|
375
|
+
const parsed = JSON.parse(raw);
|
|
376
|
+
if (typeof parsed.displayId === "number" && parsed.bounds && parsed.percent && typeof parsed.percent.x === "number" && typeof parsed.percent.y === "number" && typeof parsed.percent.width === "number" && typeof parsed.percent.height === "number") {
|
|
377
|
+
this.savedState = parsed;
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
scheduleSaveWindowState() {
|
|
383
|
+
if (!this.shouldPersistWindowState()) return;
|
|
384
|
+
if (this.stateSaveTimer) clearTimeout(this.stateSaveTimer);
|
|
385
|
+
this.stateSaveTimer = setTimeout(() => {
|
|
386
|
+
this.stateSaveTimer = null;
|
|
387
|
+
this.saveWindowState();
|
|
388
|
+
}, 200);
|
|
389
|
+
}
|
|
390
|
+
saveWindowState() {
|
|
391
|
+
if (!this.window || !this.shouldPersistWindowState()) return;
|
|
392
|
+
const statePath = this.getWindowStatePath();
|
|
393
|
+
if (!statePath) return;
|
|
394
|
+
try {
|
|
395
|
+
const bounds = this.window.getBounds();
|
|
396
|
+
const display = import_electron.screen.getDisplayMatching(bounds);
|
|
397
|
+
const percent = this.getPercentFromBounds(bounds, display);
|
|
398
|
+
const state = {
|
|
399
|
+
displayId: display.id,
|
|
400
|
+
bounds: { ...bounds },
|
|
401
|
+
percent,
|
|
402
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
403
|
+
};
|
|
404
|
+
import_fs.default.mkdirSync(import_path.default.dirname(statePath), { recursive: true });
|
|
405
|
+
import_fs.default.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
406
|
+
this.savedState = state;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
console.warn("[FloatingWindow] Failed to save window state:", e);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
getPercentFromBounds(bounds, display) {
|
|
412
|
+
const workArea = display.workArea;
|
|
413
|
+
const clamp = (value) => Math.min(1, Math.max(0, value));
|
|
414
|
+
return {
|
|
415
|
+
x: clamp((bounds.x - workArea.x) / workArea.width),
|
|
416
|
+
y: clamp((bounds.y - workArea.y) / workArea.height),
|
|
417
|
+
width: clamp(bounds.width / workArea.width),
|
|
418
|
+
height: clamp(bounds.height / workArea.height)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
getSavedBounds() {
|
|
422
|
+
if (!this.shouldPersistWindowState() || !this.savedState) return null;
|
|
423
|
+
const displays = import_electron.screen.getAllDisplays();
|
|
424
|
+
const display = displays.find((d) => d.id === this.savedState?.displayId) ?? import_electron.screen.getPrimaryDisplay();
|
|
425
|
+
const workArea = display.workArea;
|
|
426
|
+
const { minWidth, minHeight } = this.getMinSize();
|
|
427
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
428
|
+
const percent = this.savedState.percent;
|
|
429
|
+
const width = clamp(Math.round(percent.width * workArea.width), minWidth, workArea.width);
|
|
430
|
+
const height = clamp(Math.round(percent.height * workArea.height), minHeight, workArea.height);
|
|
431
|
+
const x = Math.round(workArea.x + percent.x * workArea.width);
|
|
432
|
+
const y = Math.round(workArea.y + percent.y * workArea.height);
|
|
433
|
+
const maxX = workArea.x + workArea.width - width;
|
|
434
|
+
const maxY = workArea.y + workArea.height - height;
|
|
100
435
|
return {
|
|
101
|
-
x:
|
|
102
|
-
y:
|
|
436
|
+
x: clamp(x, workArea.x, maxX),
|
|
437
|
+
y: clamp(y, workArea.y, maxY),
|
|
438
|
+
width,
|
|
439
|
+
height
|
|
103
440
|
};
|
|
104
441
|
}
|
|
105
442
|
registerShortcut() {
|
|
@@ -119,7 +456,7 @@ var FloatingWindow = class {
|
|
|
119
456
|
ipcHandlersRegistered = true;
|
|
120
457
|
import_electron.ipcMain.handle("sanqian-chat:connect", async () => {
|
|
121
458
|
try {
|
|
122
|
-
const sdk = activeInstance
|
|
459
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
123
460
|
if (!sdk) throw new Error("SDK not available");
|
|
124
461
|
await sdk.ensureReady();
|
|
125
462
|
return { success: true };
|
|
@@ -128,27 +465,42 @@ var FloatingWindow = class {
|
|
|
128
465
|
}
|
|
129
466
|
});
|
|
130
467
|
import_electron.ipcMain.handle("sanqian-chat:isConnected", () => {
|
|
131
|
-
const sdk = activeInstance
|
|
468
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
132
469
|
return sdk?.isConnected() ?? false;
|
|
133
470
|
});
|
|
134
471
|
import_electron.ipcMain.handle("sanqian-chat:stream", async (event, params) => {
|
|
135
472
|
const webContents = event.sender;
|
|
136
|
-
const { streamId, messages, conversationId } = params;
|
|
137
|
-
const sdk = activeInstance
|
|
138
|
-
const agentId = activeInstance?.options.getAgentId();
|
|
473
|
+
const { streamId, messages, conversationId, agentId: requestedAgentId } = params;
|
|
474
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
475
|
+
const agentId = requestedAgentId ?? activeInstance?.options.getAgentId();
|
|
139
476
|
if (!sdk || !agentId) {
|
|
140
477
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
|
|
141
478
|
return;
|
|
142
479
|
}
|
|
143
|
-
const streamState = { cancelled: false };
|
|
480
|
+
const streamState = { cancelled: false, runId: null };
|
|
144
481
|
activeInstance?.activeStreams.set(streamId, streamState);
|
|
145
482
|
try {
|
|
146
483
|
await sdk.ensureReady();
|
|
147
484
|
const sdkMessages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
148
|
-
const stream = sdk.chatStream(
|
|
485
|
+
const stream = sdk.chatStream(
|
|
486
|
+
agentId,
|
|
487
|
+
sdkMessages,
|
|
488
|
+
{ conversationId, persistHistory: true }
|
|
489
|
+
);
|
|
149
490
|
for await (const evt of stream) {
|
|
150
491
|
if (streamState.cancelled) break;
|
|
492
|
+
if (activeInstance?.options.devMode) {
|
|
493
|
+
console.log("[FloatingWindow] SDK event:", evt.type, JSON.stringify(evt).slice(0, 200));
|
|
494
|
+
}
|
|
151
495
|
switch (evt.type) {
|
|
496
|
+
case "start": {
|
|
497
|
+
const startEvt = evt;
|
|
498
|
+
if (startEvt.run_id) {
|
|
499
|
+
streamState.runId = startEvt.run_id;
|
|
500
|
+
}
|
|
501
|
+
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "start", run_id: startEvt.run_id } });
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
152
504
|
case "text":
|
|
153
505
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "text", content: evt.content } });
|
|
154
506
|
break;
|
|
@@ -167,12 +519,13 @@ var FloatingWindow = class {
|
|
|
167
519
|
case "error":
|
|
168
520
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: evt.error } });
|
|
169
521
|
break;
|
|
170
|
-
default:
|
|
522
|
+
default: {
|
|
171
523
|
const anyEvt = evt;
|
|
172
524
|
if (anyEvt.type === "interrupt") {
|
|
173
525
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "interrupt", interrupt_type: anyEvt.interrupt_type, interrupt_payload: anyEvt.interrupt_payload, run_id: anyEvt.run_id } });
|
|
174
526
|
}
|
|
175
527
|
break;
|
|
528
|
+
}
|
|
176
529
|
}
|
|
177
530
|
}
|
|
178
531
|
} catch (e) {
|
|
@@ -187,40 +540,64 @@ var FloatingWindow = class {
|
|
|
187
540
|
const stream = activeInstance?.activeStreams.get(params.streamId);
|
|
188
541
|
if (stream) {
|
|
189
542
|
stream.cancelled = true;
|
|
543
|
+
if (stream.runId) {
|
|
544
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
545
|
+
if (sdk) {
|
|
546
|
+
try {
|
|
547
|
+
sdk.cancelRun(stream.runId);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.warn("[FloatingWindow] Failed to cancel run:", e);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
190
553
|
activeInstance?.activeStreams.delete(params.streamId);
|
|
191
554
|
}
|
|
192
555
|
return { success: true };
|
|
193
556
|
});
|
|
194
557
|
import_electron.ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
|
|
195
|
-
const sdk = activeInstance
|
|
558
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
196
559
|
if (sdk && params.runId) {
|
|
197
560
|
sdk.sendHitlResponse(params.runId, params.response);
|
|
198
561
|
}
|
|
199
562
|
return { success: true };
|
|
200
563
|
});
|
|
201
564
|
import_electron.ipcMain.handle("sanqian-chat:listConversations", async (_, params) => {
|
|
202
|
-
const sdk = activeInstance
|
|
203
|
-
|
|
204
|
-
if (!sdk || !agentId) return { success: false, error: "SDK not ready" };
|
|
565
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
566
|
+
if (!sdk) return { success: false, error: "SDK not ready" };
|
|
205
567
|
try {
|
|
206
|
-
const result = await sdk.listConversations({
|
|
568
|
+
const result = await sdk.listConversations({
|
|
569
|
+
limit: params?.limit,
|
|
570
|
+
offset: params?.offset
|
|
571
|
+
});
|
|
207
572
|
return { success: true, data: result };
|
|
208
573
|
} catch (e) {
|
|
209
574
|
return { success: false, error: e instanceof Error ? e.message : "Failed to list" };
|
|
210
575
|
}
|
|
211
576
|
});
|
|
212
577
|
import_electron.ipcMain.handle("sanqian-chat:getConversation", async (_, params) => {
|
|
213
|
-
const sdk = activeInstance
|
|
578
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
214
579
|
if (!sdk) return { success: false, error: "SDK not ready" };
|
|
215
580
|
try {
|
|
216
581
|
const result = await sdk.getConversation(params.conversationId, { messageLimit: params.messageLimit });
|
|
217
|
-
|
|
582
|
+
let messages = result?.messages;
|
|
583
|
+
const sdkWithHistory = sdk;
|
|
584
|
+
if (typeof sdkWithHistory.getMessages === "function") {
|
|
585
|
+
try {
|
|
586
|
+
const history = await sdkWithHistory.getMessages(params.conversationId, { limit: params.messageLimit });
|
|
587
|
+
if (history?.messages && history.messages.length > 0) {
|
|
588
|
+
messages = history.messages;
|
|
589
|
+
}
|
|
590
|
+
} catch (e) {
|
|
591
|
+
console.warn("[sanqian-chat][main] getMessages failed, fallback to getConversation:", e);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return { success: true, data: { ...result, messages } };
|
|
218
595
|
} catch (e) {
|
|
219
596
|
return { success: false, error: e instanceof Error ? e.message : "Failed to get" };
|
|
220
597
|
}
|
|
221
598
|
});
|
|
222
599
|
import_electron.ipcMain.handle("sanqian-chat:deleteConversation", async (_, params) => {
|
|
223
|
-
const sdk = activeInstance
|
|
600
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
224
601
|
if (!sdk) return { success: false, error: "SDK not ready" };
|
|
225
602
|
try {
|
|
226
603
|
await sdk.deleteConversation(params.conversationId);
|
|
@@ -233,26 +610,63 @@ var FloatingWindow = class {
|
|
|
233
610
|
activeInstance?.hide();
|
|
234
611
|
return { success: true };
|
|
235
612
|
});
|
|
613
|
+
import_electron.ipcMain.handle("sanqian-chat:setAlwaysOnTop", (_event, params) => {
|
|
614
|
+
if (!activeInstance) return { success: false, error: "Window not available" };
|
|
615
|
+
activeInstance.setAlwaysOnTop(params.alwaysOnTop);
|
|
616
|
+
return { success: true };
|
|
617
|
+
});
|
|
618
|
+
import_electron.ipcMain.handle("sanqian-chat:getAlwaysOnTop", () => {
|
|
619
|
+
if (!activeInstance) return { success: false, error: "Window not available" };
|
|
620
|
+
return { success: true, data: activeInstance.isAlwaysOnTop() };
|
|
621
|
+
});
|
|
622
|
+
import_electron.ipcMain.handle("sanqian-chat:getUiConfig", () => {
|
|
623
|
+
if (!activeInstance) return { success: false, error: "Window not available" };
|
|
624
|
+
return { success: true, data: activeInstance.getResolvedUiConfig() };
|
|
625
|
+
});
|
|
236
626
|
}
|
|
237
627
|
// Public API
|
|
238
628
|
show() {
|
|
239
629
|
if (!this.window) {
|
|
240
630
|
this.window = this.createWindow();
|
|
241
631
|
}
|
|
242
|
-
|
|
243
|
-
|
|
632
|
+
if (this.shouldPersistWindowState()) {
|
|
633
|
+
const bounds = this.getInitialBounds();
|
|
634
|
+
this.window.setBounds(bounds);
|
|
635
|
+
} else {
|
|
636
|
+
const pos = this.getInitialPosition(this.options.width ?? 400, this.options.height ?? 500);
|
|
637
|
+
this.window.setPosition(pos.x, pos.y);
|
|
638
|
+
}
|
|
244
639
|
this.window.show();
|
|
245
640
|
this.window.focus();
|
|
641
|
+
if (!this.reconnectAcquired) {
|
|
642
|
+
const sdk = _FloatingWindow.getSdkFromOptions(this.options);
|
|
643
|
+
sdk?.acquireReconnect();
|
|
644
|
+
this.reconnectAcquired = true;
|
|
645
|
+
}
|
|
246
646
|
}
|
|
247
647
|
hide() {
|
|
248
648
|
if (this.window) {
|
|
249
|
-
|
|
250
|
-
const [x, y] = this.window.getPosition();
|
|
251
|
-
this.savedPosition = { x, y };
|
|
252
|
-
}
|
|
649
|
+
this.saveWindowState();
|
|
253
650
|
this.window.hide();
|
|
651
|
+
if (this.reconnectAcquired) {
|
|
652
|
+
const sdk = _FloatingWindow.getSdkFromOptions(this.options);
|
|
653
|
+
sdk?.releaseReconnect();
|
|
654
|
+
this.reconnectAcquired = false;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
setAlwaysOnTop(alwaysOnTop) {
|
|
659
|
+
this.options.alwaysOnTop = alwaysOnTop;
|
|
660
|
+
if (this.window) {
|
|
661
|
+
this.window.setAlwaysOnTop(alwaysOnTop);
|
|
254
662
|
}
|
|
255
663
|
}
|
|
664
|
+
isAlwaysOnTop() {
|
|
665
|
+
if (this.window) {
|
|
666
|
+
return this.window.isAlwaysOnTop();
|
|
667
|
+
}
|
|
668
|
+
return Boolean(this.options.alwaysOnTop);
|
|
669
|
+
}
|
|
256
670
|
toggle() {
|
|
257
671
|
if (this.window?.isVisible()) {
|
|
258
672
|
this.hide();
|
|
@@ -267,6 +681,15 @@ var FloatingWindow = class {
|
|
|
267
681
|
if (this.options.shortcut) {
|
|
268
682
|
import_electron.globalShortcut.unregister(this.options.shortcut);
|
|
269
683
|
}
|
|
684
|
+
if (this.stateSaveTimer) {
|
|
685
|
+
clearTimeout(this.stateSaveTimer);
|
|
686
|
+
this.stateSaveTimer = null;
|
|
687
|
+
}
|
|
688
|
+
if (this.reconnectAcquired) {
|
|
689
|
+
const sdk = _FloatingWindow.getSdkFromOptions(this.options);
|
|
690
|
+
sdk?.releaseReconnect();
|
|
691
|
+
this.reconnectAcquired = false;
|
|
692
|
+
}
|
|
270
693
|
this.window?.destroy();
|
|
271
694
|
this.window = null;
|
|
272
695
|
this.activeStreams.forEach((stream) => {
|
|
@@ -285,6 +708,9 @@ var FloatingWindow = class {
|
|
|
285
708
|
import_electron.ipcMain.removeHandler("sanqian-chat:getConversation");
|
|
286
709
|
import_electron.ipcMain.removeHandler("sanqian-chat:deleteConversation");
|
|
287
710
|
import_electron.ipcMain.removeHandler("sanqian-chat:hide");
|
|
711
|
+
import_electron.ipcMain.removeHandler("sanqian-chat:setAlwaysOnTop");
|
|
712
|
+
import_electron.ipcMain.removeHandler("sanqian-chat:getAlwaysOnTop");
|
|
713
|
+
import_electron.ipcMain.removeHandler("sanqian-chat:getUiConfig");
|
|
288
714
|
ipcHandlersRegistered = false;
|
|
289
715
|
}
|
|
290
716
|
}
|
|
@@ -295,5 +721,6 @@ var FloatingWindow = class {
|
|
|
295
721
|
};
|
|
296
722
|
// Annotate the CommonJS export names for ESM import in node:
|
|
297
723
|
0 && (module.exports = {
|
|
298
|
-
FloatingWindow
|
|
724
|
+
FloatingWindow,
|
|
725
|
+
SanqianAppClient
|
|
299
726
|
});
|