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