@desktalk/core 0.1.0-alpha.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/LICENSE +21 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +82 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/frontend/assets/frontend-B3QNYf3p.js +413 -0
- package/dist/frontend/assets/frontend-B3QNYf3p.js.map +1 -0
- package/dist/frontend/assets/frontend-B4aeXn9d.js +416 -0
- package/dist/frontend/assets/frontend-B4aeXn9d.js.map +1 -0
- package/dist/frontend/assets/frontend-BwyRSlHp.js +6513 -0
- package/dist/frontend/assets/frontend-BwyRSlHp.js.map +1 -0
- package/dist/frontend/assets/frontend-Dix2OWTT.js +229 -0
- package/dist/frontend/assets/frontend-Dix2OWTT.js.map +1 -0
- package/dist/frontend/assets/frontend-Dx41dEM9.js +407 -0
- package/dist/frontend/assets/frontend-Dx41dEM9.js.map +1 -0
- package/dist/frontend/assets/frontend-WpQng8Mt.js +1991 -0
- package/dist/frontend/assets/frontend-WpQng8Mt.js.map +1 -0
- package/dist/frontend/assets/frontend-rTwBdJbn.js +2123 -0
- package/dist/frontend/assets/frontend-rTwBdJbn.js.map +1 -0
- package/dist/frontend/assets/highlighted-body-TPN3WLV5-DD4wpkf4.js +2 -0
- package/dist/frontend/assets/highlighted-body-TPN3WLV5-DD4wpkf4.js.map +1 -0
- package/dist/frontend/assets/index-C5-XUOS7.js +1863 -0
- package/dist/frontend/assets/index-C5-XUOS7.js.map +1 -0
- package/dist/frontend/assets/index-C_e3_6yE.css +1 -0
- package/dist/frontend/index.html +22 -0
- package/dist/frontend/pcm-capture-processor.js +65 -0
- package/dist/i18n/manifest.json +34 -0
- package/dist/i18n/zh-CN.json +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server/admin-routes.d.ts +14 -0
- package/dist/server/admin-routes.d.ts.map +1 -0
- package/dist/server/admin-routes.js +118 -0
- package/dist/server/admin-routes.js.map +1 -0
- package/dist/server/api-routes.d.ts +8 -0
- package/dist/server/api-routes.d.ts.map +1 -0
- package/dist/server/api-routes.js +203 -0
- package/dist/server/api-routes.js.map +1 -0
- package/dist/server/auth-routes.d.ts +19 -0
- package/dist/server/auth-routes.d.ts.map +1 -0
- package/dist/server/auth-routes.js +155 -0
- package/dist/server/auth-routes.js.map +1 -0
- package/dist/server/dtfs-routes.d.ts +3 -0
- package/dist/server/dtfs-routes.d.ts.map +1 -0
- package/dist/server/dtfs-routes.js +183 -0
- package/dist/server/dtfs-routes.js.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +158 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/monaco-routes.d.ts +3 -0
- package/dist/server/monaco-routes.d.ts.map +1 -0
- package/dist/server/monaco-routes.js +45 -0
- package/dist/server/monaco-routes.js.map +1 -0
- package/dist/server/voice-routes.d.ts +7 -0
- package/dist/server/voice-routes.d.ts.map +1 -0
- package/dist/server/voice-routes.js +142 -0
- package/dist/server/voice-routes.js.map +1 -0
- package/dist/server/ws-routes.d.ts +29 -0
- package/dist/server/ws-routes.d.ts.map +1 -0
- package/dist/server/ws-routes.js +334 -0
- package/dist/server/ws-routes.js.map +1 -0
- package/dist/services/ai/action-tool.d.ts +9 -0
- package/dist/services/ai/action-tool.d.ts.map +1 -0
- package/dist/services/ai/action-tool.js +51 -0
- package/dist/services/ai/action-tool.js.map +1 -0
- package/dist/services/ai/app-tools.d.ts +15 -0
- package/dist/services/ai/app-tools.d.ts.map +1 -0
- package/dist/services/ai/app-tools.js +119 -0
- package/dist/services/ai/app-tools.js.map +1 -0
- package/dist/services/ai/chat-service.d.ts +27 -0
- package/dist/services/ai/chat-service.d.ts.map +1 -0
- package/dist/services/ai/chat-service.js +213 -0
- package/dist/services/ai/chat-service.js.map +1 -0
- package/dist/services/ai/create-liveapp-tool.d.ts +15 -0
- package/dist/services/ai/create-liveapp-tool.d.ts.map +1 -0
- package/dist/services/ai/create-liveapp-tool.js +142 -0
- package/dist/services/ai/create-liveapp-tool.js.map +1 -0
- package/dist/services/ai/desktop-tool.d.ts +27 -0
- package/dist/services/ai/desktop-tool.d.ts.map +1 -0
- package/dist/services/ai/desktop-tool.js +106 -0
- package/dist/services/ai/desktop-tool.js.map +1 -0
- package/dist/services/ai/edit-history.d.ts +16 -0
- package/dist/services/ai/edit-history.d.ts.map +1 -0
- package/dist/services/ai/edit-history.js +137 -0
- package/dist/services/ai/edit-history.js.map +1 -0
- package/dist/services/ai/edit-tool.d.ts +9 -0
- package/dist/services/ai/edit-tool.d.ts.map +1 -0
- package/dist/services/ai/edit-tool.js +113 -0
- package/dist/services/ai/edit-tool.js.map +1 -0
- package/dist/services/ai/generate-html-tool.d.ts +15 -0
- package/dist/services/ai/generate-html-tool.d.ts.map +1 -0
- package/dist/services/ai/generate-html-tool.js +140 -0
- package/dist/services/ai/generate-html-tool.js.map +1 -0
- package/dist/services/ai/generate-icon-tool.d.ts +10 -0
- package/dist/services/ai/generate-icon-tool.d.ts.map +1 -0
- package/dist/services/ai/generate-icon-tool.js +58 -0
- package/dist/services/ai/generate-icon-tool.js.map +1 -0
- package/dist/services/ai/html-bridge-script.d.ts +2 -0
- package/dist/services/ai/html-bridge-script.d.ts.map +1 -0
- package/dist/services/ai/html-bridge-script.js +2 -0
- package/dist/services/ai/html-bridge-script.js.map +1 -0
- package/dist/services/ai/html-guidelines-tool.d.ts +3 -0
- package/dist/services/ai/html-guidelines-tool.d.ts.map +1 -0
- package/dist/services/ai/html-guidelines-tool.js +157 -0
- package/dist/services/ai/html-guidelines-tool.js.map +1 -0
- package/dist/services/ai/html-stream-coordinator.d.ts +92 -0
- package/dist/services/ai/html-stream-coordinator.d.ts.map +1 -0
- package/dist/services/ai/html-stream-coordinator.js +314 -0
- package/dist/services/ai/html-stream-coordinator.js.map +1 -0
- package/dist/services/ai/html-theme-link.d.ts +9 -0
- package/dist/services/ai/html-theme-link.d.ts.map +1 -0
- package/dist/services/ai/html-theme-link.js +12 -0
- package/dist/services/ai/html-theme-link.js.map +1 -0
- package/dist/services/ai/html-ui-script.d.ts +9 -0
- package/dist/services/ai/html-ui-script.d.ts.map +1 -0
- package/dist/services/ai/html-ui-script.js +9 -0
- package/dist/services/ai/html-ui-script.js.map +1 -0
- package/dist/services/ai/image-generation-service.d.ts +26 -0
- package/dist/services/ai/image-generation-service.d.ts.map +1 -0
- package/dist/services/ai/image-generation-service.js +205 -0
- package/dist/services/ai/image-generation-service.js.map +1 -0
- package/dist/services/ai/manual-pages/desktop-actions.md +36 -0
- package/dist/services/ai/manual-pages/desktop-windows.md +24 -0
- package/dist/services/ai/manual-pages/dt-badge.md +36 -0
- package/dist/services/ai/manual-pages/dt-button.md +38 -0
- package/dist/services/ai/manual-pages/dt-card.md +22 -0
- package/dist/services/ai/manual-pages/dt-chart.md +261 -0
- package/dist/services/ai/manual-pages/dt-divider.md +25 -0
- package/dist/services/ai/manual-pages/dt-grid.md +29 -0
- package/dist/services/ai/manual-pages/dt-list-view.md +96 -0
- package/dist/services/ai/manual-pages/dt-markdown-editor.md +90 -0
- package/dist/services/ai/manual-pages/dt-markdown.md +86 -0
- package/dist/services/ai/manual-pages/dt-select.md +21 -0
- package/dist/services/ai/manual-pages/dt-stack.md +31 -0
- package/dist/services/ai/manual-pages/dt-stat.md +43 -0
- package/dist/services/ai/manual-pages/dt-table-view.md +120 -0
- package/dist/services/ai/manual-pages/dt-tooltip.md +18 -0
- package/dist/services/ai/manual-pages/editing-preview.md +25 -0
- package/dist/services/ai/manual-pages/html-actions.md +74 -0
- package/dist/services/ai/manual-pages/html-bridge.md +187 -0
- package/dist/services/ai/manual-pages/html-components.md +66 -0
- package/dist/services/ai/manual-pages/html-examples.md +185 -0
- package/dist/services/ai/manual-pages/html-layouts.md +128 -0
- package/dist/services/ai/manual-pages/html-storage.md +153 -0
- package/dist/services/ai/manual-pages/html-tokens.md +26 -0
- package/dist/services/ai/manual-pages/index.d.ts +10 -0
- package/dist/services/ai/manual-pages/index.d.ts.map +1 -0
- package/dist/services/ai/manual-pages/index.js +186 -0
- package/dist/services/ai/manual-pages/index.js.map +1 -0
- package/dist/services/ai/manual-tool.d.ts +3 -0
- package/dist/services/ai/manual-tool.d.ts.map +1 -0
- package/dist/services/ai/manual-tool.js +104 -0
- package/dist/services/ai/manual-tool.js.map +1 -0
- package/dist/services/ai/pi-session-service.d.ts +101 -0
- package/dist/services/ai/pi-session-service.d.ts.map +1 -0
- package/dist/services/ai/pi-session-service.js +697 -0
- package/dist/services/ai/pi-session-service.js.map +1 -0
- package/dist/services/ai/providers.d.ts +21 -0
- package/dist/services/ai/providers.d.ts.map +1 -0
- package/dist/services/ai/providers.js +93 -0
- package/dist/services/ai/providers.js.map +1 -0
- package/dist/services/ai/redo-edit-tool.d.ts +9 -0
- package/dist/services/ai/redo-edit-tool.d.ts.map +1 -0
- package/dist/services/ai/redo-edit-tool.js +44 -0
- package/dist/services/ai/redo-edit-tool.js.map +1 -0
- package/dist/services/ai/system-prompt.d.ts +7 -0
- package/dist/services/ai/system-prompt.d.ts.map +1 -0
- package/dist/services/ai/system-prompt.js +72 -0
- package/dist/services/ai/system-prompt.js.map +1 -0
- package/dist/services/ai/undo-edit-tool.d.ts +9 -0
- package/dist/services/ai/undo-edit-tool.d.ts.map +1 -0
- package/dist/services/ai/undo-edit-tool.js +44 -0
- package/dist/services/ai/undo-edit-tool.js.map +1 -0
- package/dist/services/ai/window-tools.d.ts +27 -0
- package/dist/services/ai/window-tools.d.ts.map +1 -0
- package/dist/services/ai/window-tools.js +155 -0
- package/dist/services/ai/window-tools.js.map +1 -0
- package/dist/services/backend-host.d.ts +10 -0
- package/dist/services/backend-host.d.ts.map +1 -0
- package/dist/services/backend-host.js +117 -0
- package/dist/services/backend-host.js.map +1 -0
- package/dist/services/backend-ipc.d.ts +60 -0
- package/dist/services/backend-ipc.d.ts.map +1 -0
- package/dist/services/backend-ipc.js +2 -0
- package/dist/services/backend-ipc.js.map +1 -0
- package/dist/services/backend-process-manager.d.ts +60 -0
- package/dist/services/backend-process-manager.d.ts.map +1 -0
- package/dist/services/backend-process-manager.js +203 -0
- package/dist/services/backend-process-manager.js.map +1 -0
- package/dist/services/filesystem.d.ts +8 -0
- package/dist/services/filesystem.d.ts.map +1 -0
- package/dist/services/filesystem.js +94 -0
- package/dist/services/filesystem.js.map +1 -0
- package/dist/services/i18n.d.ts +28 -0
- package/dist/services/i18n.d.ts.map +1 -0
- package/dist/services/i18n.js +76 -0
- package/dist/services/i18n.js.map +1 -0
- package/dist/services/liveapp-icon.d.ts +9 -0
- package/dist/services/liveapp-icon.d.ts.map +1 -0
- package/dist/services/liveapp-icon.js +28 -0
- package/dist/services/liveapp-icon.js.map +1 -0
- package/dist/services/liveapps.d.ts +12 -0
- package/dist/services/liveapps.d.ts.map +1 -0
- package/dist/services/liveapps.js +84 -0
- package/dist/services/liveapps.js.map +1 -0
- package/dist/services/logger.d.ts +34 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +74 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/services/messaging.d.ts +14 -0
- package/dist/services/messaging.d.ts.map +1 -0
- package/dist/services/messaging.js +41 -0
- package/dist/services/messaging.js.map +1 -0
- package/dist/services/miniapp-icon.d.ts +9 -0
- package/dist/services/miniapp-icon.d.ts.map +1 -0
- package/dist/services/miniapp-icon.js +25 -0
- package/dist/services/miniapp-icon.js.map +1 -0
- package/dist/services/miniapp-registry.d.ts +73 -0
- package/dist/services/miniapp-registry.d.ts.map +1 -0
- package/dist/services/miniapp-registry.js +144 -0
- package/dist/services/miniapp-registry.js.map +1 -0
- package/dist/services/onboarding-config.d.ts +37 -0
- package/dist/services/onboarding-config.d.ts.map +1 -0
- package/dist/services/onboarding-config.js +76 -0
- package/dist/services/onboarding-config.js.map +1 -0
- package/dist/services/preferences.d.ts +10 -0
- package/dist/services/preferences.d.ts.map +1 -0
- package/dist/services/preferences.js +48 -0
- package/dist/services/preferences.js.map +1 -0
- package/dist/services/proxy-dispatcher.d.ts +18 -0
- package/dist/services/proxy-dispatcher.d.ts.map +1 -0
- package/dist/services/proxy-dispatcher.js +33 -0
- package/dist/services/proxy-dispatcher.js.map +1 -0
- package/dist/services/storage.d.ts +7 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +55 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/theme-css.d.ts +21 -0
- package/dist/services/theme-css.d.ts.map +1 -0
- package/dist/services/theme-css.js +213 -0
- package/dist/services/theme-css.js.map +1 -0
- package/dist/services/user-db.d.ts +69 -0
- package/dist/services/user-db.d.ts.map +1 -0
- package/dist/services/user-db.js +175 -0
- package/dist/services/user-db.js.map +1 -0
- package/dist/services/voice/audio-format.d.ts +5 -0
- package/dist/services/voice/audio-format.d.ts.map +1 -0
- package/dist/services/voice/audio-format.js +27 -0
- package/dist/services/voice/audio-format.js.map +1 -0
- package/dist/services/voice/azure-openai-whisper-adapter.d.ts +23 -0
- package/dist/services/voice/azure-openai-whisper-adapter.d.ts.map +1 -0
- package/dist/services/voice/azure-openai-whisper-adapter.js +60 -0
- package/dist/services/voice/azure-openai-whisper-adapter.js.map +1 -0
- package/dist/services/voice/openai-whisper-adapter.d.ts +22 -0
- package/dist/services/voice/openai-whisper-adapter.d.ts.map +1 -0
- package/dist/services/voice/openai-whisper-adapter.js +61 -0
- package/dist/services/voice/openai-whisper-adapter.js.map +1 -0
- package/dist/services/voice/stt-adapter.d.ts +31 -0
- package/dist/services/voice/stt-adapter.d.ts.map +1 -0
- package/dist/services/voice/stt-adapter.js +8 -0
- package/dist/services/voice/stt-adapter.js.map +1 -0
- package/dist/services/voice/vad-segmenter.d.ts +68 -0
- package/dist/services/voice/vad-segmenter.d.ts.map +1 -0
- package/dist/services/voice/vad-segmenter.js +159 -0
- package/dist/services/voice/vad-segmenter.js.map +1 -0
- package/dist/services/voice/voice-session.d.ts +54 -0
- package/dist/services/voice/voice-session.d.ts.map +1 -0
- package/dist/services/voice/voice-session.js +137 -0
- package/dist/services/voice/voice-session.js.map +1 -0
- package/dist/services/window-manager.d.ts +94 -0
- package/dist/services/window-manager.d.ts.map +1 -0
- package/dist/services/window-manager.js +282 -0
- package/dist/services/window-manager.js.map +1 -0
- package/dist/services/workspace.d.ts +51 -0
- package/dist/services/workspace.d.ts.map +1 -0
- package/dist/services/workspace.js +144 -0
- package/dist/services/workspace.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { parse as parsePartialJson, Allow } from 'partial-json';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { broadcastEvent } from '../messaging.js';
|
|
4
|
+
import { DEFAULT_THEME_PREFERENCES } from '@desktalk/ui/theme-css';
|
|
5
|
+
import { createHtmlBridgeScript } from './html-bridge-script.js';
|
|
6
|
+
import { createThemeLinkTag } from './html-theme-link.js';
|
|
7
|
+
import { UI_BUNDLE_SCRIPT_TAG } from './html-ui-script.js';
|
|
8
|
+
// ─── Coordinator ─────────────────────────────────────────────────────────────
|
|
9
|
+
let streamCounter = 0;
|
|
10
|
+
function nextStreamId() {
|
|
11
|
+
return `html-stream-${++streamCounter}-${Date.now()}`;
|
|
12
|
+
}
|
|
13
|
+
export class HtmlStreamCoordinator {
|
|
14
|
+
/** The currently active streaming session, or null. */
|
|
15
|
+
activeSession = null;
|
|
16
|
+
/**
|
|
17
|
+
* A promise chain that serializes all async operations (opening the window,
|
|
18
|
+
* sending the preamble, broadcasting chunks). Every call to `onToolcallDelta`
|
|
19
|
+
* appends to this chain so that operations execute in order without races.
|
|
20
|
+
*/
|
|
21
|
+
pendingWork = Promise.resolve();
|
|
22
|
+
sendAiCommand;
|
|
23
|
+
activateMiniApp;
|
|
24
|
+
getPreference;
|
|
25
|
+
log;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.sendAiCommand = options.sendAiCommand;
|
|
28
|
+
this.activateMiniApp = options.activateMiniApp;
|
|
29
|
+
this.getPreference = options.getPreference;
|
|
30
|
+
this.log = options.logger;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the active session, if any.
|
|
34
|
+
*/
|
|
35
|
+
getActiveSession() {
|
|
36
|
+
return this.activeSession;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Called when a `toolcall_start` event is detected for `create_liveapp`.
|
|
40
|
+
* Creates a new streaming session.
|
|
41
|
+
*/
|
|
42
|
+
onToolcallStart() {
|
|
43
|
+
this.pendingWork = Promise.resolve();
|
|
44
|
+
const streamId = nextStreamId();
|
|
45
|
+
this.log.debug({ streamId }, 'onToolcallStart — new session');
|
|
46
|
+
this.activeSession = {
|
|
47
|
+
streamId,
|
|
48
|
+
bridgeToken: randomUUID(),
|
|
49
|
+
state: 'streaming',
|
|
50
|
+
streamedContentLength: 0,
|
|
51
|
+
accumulatedJson: '',
|
|
52
|
+
title: null,
|
|
53
|
+
windowOpened: false,
|
|
54
|
+
preambleSent: false,
|
|
55
|
+
windowId: null,
|
|
56
|
+
contentStartOffset: -1,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Called on each `toolcall_delta` event while a session is active.
|
|
61
|
+
*
|
|
62
|
+
* This method is synchronous — it accumulates the JSON fragment immediately,
|
|
63
|
+
* then queues any async work (window open, preamble, chunk broadcast) onto
|
|
64
|
+
* the serialized `pendingWork` chain so operations never race.
|
|
65
|
+
*/
|
|
66
|
+
onToolcallDelta(delta) {
|
|
67
|
+
const session = this.activeSession;
|
|
68
|
+
if (!session || session.state !== 'streaming')
|
|
69
|
+
return;
|
|
70
|
+
// Skip empty deltas (OpenAI sometimes sends these on the first chunk)
|
|
71
|
+
if (!delta)
|
|
72
|
+
return;
|
|
73
|
+
session.accumulatedJson += delta;
|
|
74
|
+
// Attempt to parse partial JSON from the accumulated argument string
|
|
75
|
+
let parsed = null;
|
|
76
|
+
try {
|
|
77
|
+
parsed = parsePartialJson(session.accumulatedJson, Allow.ALL);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Partial JSON not parseable yet — that's fine, wait for more data
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!parsed)
|
|
84
|
+
return;
|
|
85
|
+
// Extract title as soon as it's available
|
|
86
|
+
if (!session.title && typeof parsed.title === 'string' && parsed.title.length > 0) {
|
|
87
|
+
session.title = parsed.title;
|
|
88
|
+
this.log.debug({ title: session.title }, 'title extracted');
|
|
89
|
+
}
|
|
90
|
+
// Capture the parsed content value for the queued async work
|
|
91
|
+
const parsedTitle = session.title;
|
|
92
|
+
const parsedContent = typeof parsed.content === 'string' ? parsed.content : null;
|
|
93
|
+
const contentLen = parsedContent?.length ?? 0;
|
|
94
|
+
const alreadyStreamed = session.streamedContentLength;
|
|
95
|
+
// Only queue work if there's something new to do
|
|
96
|
+
const needsWindowOpen = parsedTitle && !session.windowOpened;
|
|
97
|
+
const hasNewContent = parsedContent !== null && contentLen > alreadyStreamed;
|
|
98
|
+
if (!needsWindowOpen && !hasNewContent)
|
|
99
|
+
return;
|
|
100
|
+
// Queue async work onto the serialized chain
|
|
101
|
+
this.pendingWork = this.pendingWork
|
|
102
|
+
.then(async () => {
|
|
103
|
+
// Re-check state — a previous step may have failed
|
|
104
|
+
if (session.state !== 'streaming')
|
|
105
|
+
return;
|
|
106
|
+
// Open the preview window once we have a title
|
|
107
|
+
if (parsedTitle && !session.windowOpened) {
|
|
108
|
+
this.log.debug({ title: parsedTitle }, 'opening preview window');
|
|
109
|
+
await this.openPreviewWindow(session);
|
|
110
|
+
if (session.state !== 'streaming') {
|
|
111
|
+
this.log.debug({ state: session.state }, 'openPreviewWindow failed');
|
|
112
|
+
return; // failed to open
|
|
113
|
+
}
|
|
114
|
+
this.log.debug({ windowId: session.windowId }, 'preview window opened');
|
|
115
|
+
}
|
|
116
|
+
// Stream new HTML content
|
|
117
|
+
if (parsedContent !== null && session.windowOpened) {
|
|
118
|
+
// Send the preamble (theme + bridge script) before any content
|
|
119
|
+
if (!session.preambleSent) {
|
|
120
|
+
this.log.debug('sending preamble');
|
|
121
|
+
await this.sendPreamble(session);
|
|
122
|
+
this.log.debug('preamble sent');
|
|
123
|
+
}
|
|
124
|
+
// The preamble already provides `<!DOCTYPE html><html><head>`.
|
|
125
|
+
// Detect the same structural prefix in the LLM content so we
|
|
126
|
+
// can skip it and avoid the browser seeing duplicate tags.
|
|
127
|
+
if (session.contentStartOffset < 0) {
|
|
128
|
+
const headMatch = parsedContent.match(/<head(\s[^>]*)?>|<head>/i);
|
|
129
|
+
if (headMatch && headMatch.index !== undefined) {
|
|
130
|
+
session.contentStartOffset = headMatch.index + headMatch[0].length;
|
|
131
|
+
this.log.debug({ contentStartOffset: session.contentStartOffset }, 'structural prefix detected — skipping');
|
|
132
|
+
}
|
|
133
|
+
else if (parsedContent.length > 200) {
|
|
134
|
+
// Content is long enough that <head> would have appeared by now.
|
|
135
|
+
// Assume there is no <head> and stream from the beginning.
|
|
136
|
+
session.contentStartOffset = 0;
|
|
137
|
+
this.log.debug('no <head> found after 200 chars — streaming from start');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Not enough content yet to decide; wait for more data.
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Jump our cursor past the structural prefix
|
|
144
|
+
session.streamedContentLength = Math.max(session.streamedContentLength, session.contentStartOffset);
|
|
145
|
+
}
|
|
146
|
+
const newContent = parsedContent.slice(session.streamedContentLength);
|
|
147
|
+
if (newContent.length > 0) {
|
|
148
|
+
this.log.debug({
|
|
149
|
+
chunkLength: newContent.length,
|
|
150
|
+
totalStreamed: session.streamedContentLength + newContent.length,
|
|
151
|
+
}, 'broadcasting chunk');
|
|
152
|
+
broadcastEvent('preview', 'preview.html-chunk', {
|
|
153
|
+
streamId: session.streamId,
|
|
154
|
+
chunk: newContent,
|
|
155
|
+
});
|
|
156
|
+
session.streamedContentLength = parsedContent.length;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
.catch((err) => {
|
|
161
|
+
// If any step in the chain fails, mark the session as failed
|
|
162
|
+
this.log.error({ err }, 'error in streaming chain');
|
|
163
|
+
if (session.state === 'streaming') {
|
|
164
|
+
session.state = 'failed';
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Called when the `toolcall_end` event fires. The tool's `execute()` will run
|
|
170
|
+
* next with the complete params, so we just mark the LLM streaming as done.
|
|
171
|
+
*/
|
|
172
|
+
onToolcallEnd() {
|
|
173
|
+
// Don't finalize here — execute() will handle the final flush + done signal.
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Called by `create-liveapp-tool.ts` execute() to finalize the stream.
|
|
177
|
+
*
|
|
178
|
+
* Waits for all pending async work (window open, preamble, chunk sends) to
|
|
179
|
+
* complete, then sends any remaining content and the done signal.
|
|
180
|
+
*
|
|
181
|
+
* Returns the session info so execute() can build its result, or null if
|
|
182
|
+
* no streaming session was active (fallback to non-streaming path).
|
|
183
|
+
*/
|
|
184
|
+
async finalize(finalContent) {
|
|
185
|
+
const session = this.activeSession;
|
|
186
|
+
this.log.debug({
|
|
187
|
+
hasSession: !!session,
|
|
188
|
+
state: session?.state,
|
|
189
|
+
windowOpened: session?.windowOpened,
|
|
190
|
+
streamedContentLength: session?.streamedContentLength,
|
|
191
|
+
finalContentLength: finalContent.length,
|
|
192
|
+
}, 'finalize() called');
|
|
193
|
+
if (!session || (session.state !== 'streaming' && session.state !== 'failed')) {
|
|
194
|
+
this.log.debug('finalize() — no active session or wrong state, returning null');
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// Wait for all queued async work to finish
|
|
198
|
+
await this.pendingWork;
|
|
199
|
+
if (session.state === 'failed' || !session.windowOpened) {
|
|
200
|
+
this.log.debug('finalize() — session failed or window not opened, returning null');
|
|
201
|
+
this.activeSession = null;
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
// Send any remaining content that wasn't streamed during deltas.
|
|
205
|
+
// The `finalContent` here is the raw HTML content (not yet theme-injected),
|
|
206
|
+
// because theme/bridge were sent as a preamble.
|
|
207
|
+
if (session.preambleSent) {
|
|
208
|
+
// If contentStartOffset was never determined during streaming (e.g. very
|
|
209
|
+
// short content), detect it now from the final content.
|
|
210
|
+
if (session.contentStartOffset < 0) {
|
|
211
|
+
const headMatch = finalContent.match(/<head(\s[^>]*)?>|<head>/i);
|
|
212
|
+
session.contentStartOffset =
|
|
213
|
+
headMatch && headMatch.index !== undefined ? headMatch.index + headMatch[0].length : 0;
|
|
214
|
+
session.streamedContentLength = Math.max(session.streamedContentLength, session.contentStartOffset);
|
|
215
|
+
}
|
|
216
|
+
const remaining = finalContent.slice(session.streamedContentLength);
|
|
217
|
+
if (remaining.length > 0) {
|
|
218
|
+
this.log.debug({ remainingLength: remaining.length }, 'finalize() — sending remaining content');
|
|
219
|
+
broadcastEvent('preview', 'preview.html-chunk', {
|
|
220
|
+
streamId: session.streamId,
|
|
221
|
+
chunk: remaining,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Signal that streaming is complete
|
|
226
|
+
this.log.debug({ streamId: session.streamId }, 'finalize() — sending html-done');
|
|
227
|
+
broadcastEvent('preview', 'preview.html-done', {
|
|
228
|
+
streamId: session.streamId,
|
|
229
|
+
html: finalContent,
|
|
230
|
+
});
|
|
231
|
+
session.state = 'done';
|
|
232
|
+
const result = { ...session };
|
|
233
|
+
this.activeSession = null;
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Abort the current session (e.g. on error).
|
|
238
|
+
*/
|
|
239
|
+
abort() {
|
|
240
|
+
const session = this.activeSession;
|
|
241
|
+
if (session && session.windowOpened) {
|
|
242
|
+
broadcastEvent('preview', 'preview.html-done', { streamId: session.streamId });
|
|
243
|
+
}
|
|
244
|
+
if (session) {
|
|
245
|
+
session.state = 'failed';
|
|
246
|
+
}
|
|
247
|
+
this.activeSession = null;
|
|
248
|
+
}
|
|
249
|
+
// ─── Private helpers ────────────────────────────────────────────────────
|
|
250
|
+
async openPreviewWindow(session) {
|
|
251
|
+
this.activateMiniApp('preview');
|
|
252
|
+
const commandResult = await this.sendAiCommand({
|
|
253
|
+
action: 'open',
|
|
254
|
+
miniAppId: 'preview',
|
|
255
|
+
title: session.title ?? 'HTML Preview',
|
|
256
|
+
args: {
|
|
257
|
+
streamId: session.streamId,
|
|
258
|
+
title: session.title ?? 'HTML Preview',
|
|
259
|
+
bridgeToken: session.bridgeToken,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
if (!commandResult.ok) {
|
|
263
|
+
session.state = 'failed';
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
session.windowId = commandResult.windowId ?? null;
|
|
267
|
+
session.windowOpened = true;
|
|
268
|
+
}
|
|
269
|
+
async sendPreamble(session) {
|
|
270
|
+
const themePreferences = await readThemePreferences(this.getPreference);
|
|
271
|
+
const themeLink = createThemeLinkTag(themePreferences.accentColor, themePreferences.theme);
|
|
272
|
+
const bridgeScript = createHtmlBridgeScript(session.streamId, session.bridgeToken);
|
|
273
|
+
// We send the preamble as the very first chunk, *before* the LLM's HTML.
|
|
274
|
+
// The LLM content typically starts with `<!DOCTYPE html><html><head>…`.
|
|
275
|
+
// To avoid the browser's parser creating implicit structure around our
|
|
276
|
+
// preamble (which would clash with the LLM's structural tags), we wrap
|
|
277
|
+
// the preamble in a structure the browser can merge cleanly:
|
|
278
|
+
//
|
|
279
|
+
// <!DOCTYPE html><html><head>
|
|
280
|
+
// <link rel="stylesheet" href="/api/ui/desktalk-theme.css?…">
|
|
281
|
+
// <script>…bridge…</script>
|
|
282
|
+
// <!-- preamble end -->
|
|
283
|
+
//
|
|
284
|
+
// Then, when the LLM's `<!DOCTYPE html><html><head>` arrives, the parser
|
|
285
|
+
// sees duplicate structural tags and simply ignores them (per the HTML
|
|
286
|
+
// spec, duplicate <html>/<head> tags are dropped). The LLM's <head>
|
|
287
|
+
// children (like <title>, <meta>, additional <style>) are adopted into
|
|
288
|
+
// the already-open <head>.
|
|
289
|
+
const preamble = '<!DOCTYPE html><html><head>\n' +
|
|
290
|
+
themeLink +
|
|
291
|
+
'\n' +
|
|
292
|
+
UI_BUNDLE_SCRIPT_TAG +
|
|
293
|
+
'\n' +
|
|
294
|
+
bridgeScript +
|
|
295
|
+
'\n';
|
|
296
|
+
broadcastEvent('preview', 'preview.html-chunk', {
|
|
297
|
+
streamId: session.streamId,
|
|
298
|
+
chunk: preamble,
|
|
299
|
+
});
|
|
300
|
+
session.preambleSent = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ─── Utility (shared with create-liveapp-tool) ───────────────────────────────
|
|
304
|
+
async function readThemePreferences(getPreference) {
|
|
305
|
+
const accentColor = await getPreference('general.accentColor');
|
|
306
|
+
const theme = await getPreference('general.theme');
|
|
307
|
+
return {
|
|
308
|
+
accentColor: typeof accentColor === 'string' && accentColor
|
|
309
|
+
? accentColor
|
|
310
|
+
: DEFAULT_THEME_PREFERENCES.accentColor,
|
|
311
|
+
theme: theme === 'light' ? 'light' : DEFAULT_THEME_PREFERENCES.theme,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=html-stream-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-stream-coordinator.js","sourceRoot":"","sources":["../../../src/services/ai/html-stream-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a `<link>` tag that loads the DeskTalk theme + base stylesheet from
|
|
3
|
+
* the core server's `/api/ui/desktalk-theme.css` endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Query parameters encode the user's current accent color and theme mode so
|
|
6
|
+
* the browser can cache each configuration independently.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createThemeLinkTag(accentColor: string, theme: string): string;
|
|
9
|
+
//# sourceMappingURL=html-theme-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-theme-link.d.ts","sourceRoot":"","sources":["../../../src/services/ai/html-theme-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG7E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a `<link>` tag that loads the DeskTalk theme + base stylesheet from
|
|
3
|
+
* the core server's `/api/ui/desktalk-theme.css` endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Query parameters encode the user's current accent color and theme mode so
|
|
6
|
+
* the browser can cache each configuration independently.
|
|
7
|
+
*/
|
|
8
|
+
export function createThemeLinkTag(accentColor, theme) {
|
|
9
|
+
const params = new URLSearchParams({ accent: accentColor, theme });
|
|
10
|
+
return `<link rel="stylesheet" href="/api/ui/desktalk-theme.css?${params.toString()}" data-dt-theme>`;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=html-theme-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-theme-link.js","sourceRoot":"","sources":["../../../src/services/ai/html-theme-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,KAAa;IACnE,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,OAAO,2DAA2D,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;AACxG,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an inline `<script>` tag that loads the @desktalk/ui UMD bundle
|
|
3
|
+
* from the core server's `/api/ui/desktalk-ui.js` endpoint.
|
|
4
|
+
*
|
|
5
|
+
* This registers all DeskTalk web components (`<dt-card>`, `<dt-tooltip>`,
|
|
6
|
+
* etc.) inside the generated HTML iframe so AI-produced markup can use them.
|
|
7
|
+
*/
|
|
8
|
+
export declare const UI_BUNDLE_SCRIPT_TAG = "<script src=\"/api/ui/desktalk-ui.js\" data-dt-ui></script>";
|
|
9
|
+
//# sourceMappingURL=html-ui-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-ui-script.d.ts","sourceRoot":"","sources":["../../../src/services/ai/html-ui-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,gEAA8D,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an inline `<script>` tag that loads the @desktalk/ui UMD bundle
|
|
3
|
+
* from the core server's `/api/ui/desktalk-ui.js` endpoint.
|
|
4
|
+
*
|
|
5
|
+
* This registers all DeskTalk web components (`<dt-card>`, `<dt-tooltip>`,
|
|
6
|
+
* etc.) inside the generated HTML iframe so AI-produced markup can use them.
|
|
7
|
+
*/
|
|
8
|
+
export const UI_BUNDLE_SCRIPT_TAG = '<script src="/api/ui/desktalk-ui.js" data-dt-ui></script>';
|
|
9
|
+
//# sourceMappingURL=html-ui-script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-ui-script.js","sourceRoot":"","sources":["../../../src/services/ai/html-ui-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,2DAA2D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type pino from 'pino';
|
|
2
|
+
import { type PreferenceReader } from './providers.js';
|
|
3
|
+
import type { ModelRegistry } from '@mariozechner/pi-coding-agent';
|
|
4
|
+
export interface GeneratedIconResult {
|
|
5
|
+
image: Buffer;
|
|
6
|
+
provider: string;
|
|
7
|
+
model: string;
|
|
8
|
+
}
|
|
9
|
+
interface ImageGenerationServiceOptions {
|
|
10
|
+
modelRegistry: ModelRegistry;
|
|
11
|
+
getPreference: PreferenceReader;
|
|
12
|
+
logger: pino.Logger;
|
|
13
|
+
}
|
|
14
|
+
declare function getPrompt(description: string): string;
|
|
15
|
+
export declare class ImageGenerationService {
|
|
16
|
+
private readonly modelRegistry;
|
|
17
|
+
private readonly getPreference;
|
|
18
|
+
private readonly log;
|
|
19
|
+
constructor(options: ImageGenerationServiceOptions);
|
|
20
|
+
generateIcon(description: string): Promise<GeneratedIconResult>;
|
|
21
|
+
private generateOpenAiCompatibleIcon;
|
|
22
|
+
private generateGoogleIcon;
|
|
23
|
+
private generateOpenRouterIcon;
|
|
24
|
+
}
|
|
25
|
+
export { getPrompt as buildIconPrompt };
|
|
26
|
+
//# sourceMappingURL=image-generation-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-generation-service.d.ts","sourceRoot":"","sources":["../../../src/services/ai/image-generation-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAkD,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AASnE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,6BAA6B;IACrC,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,gBAAgB,CAAC;IAChC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB;AAED,iBAAS,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9C;AA0CD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAET,OAAO,EAAE,6BAA6B;IAM5C,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAyDvD,4BAA4B;YA4D5B,kBAAkB;YAoDlB,sBAAsB;CAyDrC;AAED,OAAO,EAAE,SAAS,IAAI,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import { getAiProviderPreferences, getDefaultAiProvider } from './providers.js';
|
|
3
|
+
const ICON_PROMPT_PREFIX = 'A minimal flat-design app icon: ${description}. Single centered symbol, solid color background, rounded square, clean and modern, no text, 256x256 pixels.';
|
|
4
|
+
const OPENAI_ICON_MODEL = 'dall-e-3';
|
|
5
|
+
const OPENROUTER_ICON_MODEL = 'google/gemini-3.1-flash-image-preview';
|
|
6
|
+
const GOOGLE_ICON_MODEL = 'gemini-2.0-flash-preview-image-generation';
|
|
7
|
+
function getPrompt(description) {
|
|
8
|
+
return ICON_PROMPT_PREFIX.replace('${description}', description.trim());
|
|
9
|
+
}
|
|
10
|
+
function resolveBaseUrl(provider, baseUrl) {
|
|
11
|
+
if (baseUrl)
|
|
12
|
+
return baseUrl.replace(/\/+$/, '');
|
|
13
|
+
switch (provider) {
|
|
14
|
+
case 'openai':
|
|
15
|
+
return 'https://api.openai.com/v1';
|
|
16
|
+
case 'openrouter':
|
|
17
|
+
return 'https://openrouter.ai/api/v1';
|
|
18
|
+
case 'google':
|
|
19
|
+
return 'https://generativelanguage.googleapis.com/v1beta';
|
|
20
|
+
default:
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function resizeToIconPng(input) {
|
|
25
|
+
return sharp(input).resize({ width: 256, height: 256, fit: 'cover' }).png().toBuffer();
|
|
26
|
+
}
|
|
27
|
+
function decodeBase64Image(data) {
|
|
28
|
+
return Buffer.from(data, 'base64');
|
|
29
|
+
}
|
|
30
|
+
function decodeDataUrlImage(dataUrl) {
|
|
31
|
+
const match = dataUrl.match(/^data:[^;]+;base64,(.+)$/);
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error('Provider returned an unsupported image URL format.');
|
|
34
|
+
}
|
|
35
|
+
return decodeBase64Image(match[1]);
|
|
36
|
+
}
|
|
37
|
+
function summarizeErrorBody(body) {
|
|
38
|
+
const trimmed = body.trim();
|
|
39
|
+
if (trimmed.length <= 1000) {
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
42
|
+
return `${trimmed.slice(0, 1000)}...`;
|
|
43
|
+
}
|
|
44
|
+
export class ImageGenerationService {
|
|
45
|
+
modelRegistry;
|
|
46
|
+
getPreference;
|
|
47
|
+
log;
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.modelRegistry = options.modelRegistry;
|
|
50
|
+
this.getPreference = options.getPreference;
|
|
51
|
+
this.log = options.logger;
|
|
52
|
+
}
|
|
53
|
+
async generateIcon(description) {
|
|
54
|
+
const prompt = getPrompt(description);
|
|
55
|
+
const provider = await getDefaultAiProvider(this.getPreference);
|
|
56
|
+
const providerPreferences = await getAiProviderPreferences(this.getPreference, provider);
|
|
57
|
+
const apiKey = await this.modelRegistry.getApiKeyForProvider(provider);
|
|
58
|
+
const baseUrl = resolveBaseUrl(provider, providerPreferences.baseUrl);
|
|
59
|
+
if (provider !== 'openai' && provider !== 'openrouter' && provider !== 'google') {
|
|
60
|
+
throw new Error(`Current AI provider does not support icon generation yet: ${provider}`);
|
|
61
|
+
}
|
|
62
|
+
if (!apiKey) {
|
|
63
|
+
throw new Error(`No API key configured for ${provider}.`);
|
|
64
|
+
}
|
|
65
|
+
if (!baseUrl) {
|
|
66
|
+
throw new Error(`No base URL available for ${provider}.`);
|
|
67
|
+
}
|
|
68
|
+
this.log.debug({
|
|
69
|
+
provider,
|
|
70
|
+
baseUrl,
|
|
71
|
+
promptLength: prompt.length,
|
|
72
|
+
}, 'generating liveapp icon');
|
|
73
|
+
switch (provider) {
|
|
74
|
+
case 'openai':
|
|
75
|
+
return {
|
|
76
|
+
image: await this.generateOpenAiCompatibleIcon(baseUrl, apiKey, OPENAI_ICON_MODEL, prompt),
|
|
77
|
+
provider,
|
|
78
|
+
model: OPENAI_ICON_MODEL,
|
|
79
|
+
};
|
|
80
|
+
case 'openrouter':
|
|
81
|
+
return {
|
|
82
|
+
image: await this.generateOpenRouterIcon(baseUrl, apiKey, prompt),
|
|
83
|
+
provider,
|
|
84
|
+
model: OPENROUTER_ICON_MODEL,
|
|
85
|
+
};
|
|
86
|
+
case 'google':
|
|
87
|
+
return {
|
|
88
|
+
image: await this.generateGoogleIcon(baseUrl, apiKey, prompt),
|
|
89
|
+
provider,
|
|
90
|
+
model: GOOGLE_ICON_MODEL,
|
|
91
|
+
};
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`Unsupported icon provider: ${provider}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async generateOpenAiCompatibleIcon(baseUrl, apiKey, model, prompt, isOpenRouter = false) {
|
|
97
|
+
const endpoint = `${baseUrl}/images/generations`;
|
|
98
|
+
this.log.debug({ provider: isOpenRouter ? 'openrouter' : 'openai', endpoint, model }, 'requesting icon image');
|
|
99
|
+
const headers = {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
Authorization: `Bearer ${apiKey}`,
|
|
102
|
+
};
|
|
103
|
+
if (isOpenRouter) {
|
|
104
|
+
headers['HTTP-Referer'] = 'https://desktalk.local';
|
|
105
|
+
headers['X-Title'] = 'DeskTalk';
|
|
106
|
+
}
|
|
107
|
+
const response = await fetch(endpoint, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers,
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
model,
|
|
112
|
+
prompt,
|
|
113
|
+
size: '1024x1024',
|
|
114
|
+
response_format: 'b64_json',
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const errorText = summarizeErrorBody(await response.text());
|
|
119
|
+
this.log.error({
|
|
120
|
+
provider: isOpenRouter ? 'openrouter' : 'openai',
|
|
121
|
+
endpoint,
|
|
122
|
+
model,
|
|
123
|
+
status: response.status,
|
|
124
|
+
errorText,
|
|
125
|
+
}, 'icon image request failed');
|
|
126
|
+
throw new Error(`Image request failed (${response.status}): ${errorText}`);
|
|
127
|
+
}
|
|
128
|
+
const result = (await response.json());
|
|
129
|
+
const base64Image = result.data?.[0]?.b64_json;
|
|
130
|
+
if (!base64Image) {
|
|
131
|
+
throw new Error('No image data returned by the provider.');
|
|
132
|
+
}
|
|
133
|
+
return resizeToIconPng(decodeBase64Image(base64Image));
|
|
134
|
+
}
|
|
135
|
+
async generateGoogleIcon(baseUrl, apiKey, prompt) {
|
|
136
|
+
const endpoint = `${baseUrl}/models/${encodeURIComponent(GOOGLE_ICON_MODEL)}:generateContent?key=${encodeURIComponent(apiKey)}`;
|
|
137
|
+
this.log.debug({ provider: 'google', endpoint, model: GOOGLE_ICON_MODEL }, 'requesting icon image');
|
|
138
|
+
const response = await fetch(endpoint, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
145
|
+
generationConfig: {
|
|
146
|
+
responseModalities: ['TEXT', 'IMAGE'],
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const errorText = summarizeErrorBody(await response.text());
|
|
152
|
+
this.log.error({
|
|
153
|
+
provider: 'google',
|
|
154
|
+
endpoint,
|
|
155
|
+
model: GOOGLE_ICON_MODEL,
|
|
156
|
+
status: response.status,
|
|
157
|
+
errorText,
|
|
158
|
+
}, 'icon image request failed');
|
|
159
|
+
throw new Error(`Image request failed (${response.status}): ${errorText}`);
|
|
160
|
+
}
|
|
161
|
+
const result = (await response.json());
|
|
162
|
+
const base64Image = result.candidates?.[0]?.content?.parts?.find((part) => part.inlineData?.data)?.inlineData?.data;
|
|
163
|
+
if (!base64Image) {
|
|
164
|
+
throw new Error('No image data returned by Google.');
|
|
165
|
+
}
|
|
166
|
+
return resizeToIconPng(decodeBase64Image(base64Image));
|
|
167
|
+
}
|
|
168
|
+
async generateOpenRouterIcon(baseUrl, apiKey, prompt) {
|
|
169
|
+
const endpoint = `${baseUrl}/chat/completions`;
|
|
170
|
+
this.log.debug({ provider: 'openrouter', endpoint, model: OPENROUTER_ICON_MODEL }, 'requesting icon image');
|
|
171
|
+
const response = await fetch(endpoint, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: {
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
Authorization: `Bearer ${apiKey}`,
|
|
176
|
+
'HTTP-Referer': 'https://desktalk.local',
|
|
177
|
+
'X-Title': 'DeskTalk',
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
model: OPENROUTER_ICON_MODEL,
|
|
181
|
+
messages: [{ role: 'user', content: prompt }],
|
|
182
|
+
modalities: ['image', 'text'],
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const errorText = summarizeErrorBody(await response.text());
|
|
187
|
+
this.log.error({
|
|
188
|
+
provider: 'openrouter',
|
|
189
|
+
endpoint,
|
|
190
|
+
model: OPENROUTER_ICON_MODEL,
|
|
191
|
+
status: response.status,
|
|
192
|
+
errorText,
|
|
193
|
+
}, 'icon image request failed');
|
|
194
|
+
throw new Error(`Image request failed (${response.status}): ${errorText}`);
|
|
195
|
+
}
|
|
196
|
+
const result = (await response.json());
|
|
197
|
+
const imageUrl = result.choices?.[0]?.message?.images?.[0]?.image_url?.url;
|
|
198
|
+
if (!imageUrl) {
|
|
199
|
+
throw new Error('No image data returned by OpenRouter.');
|
|
200
|
+
}
|
|
201
|
+
return resizeToIconPng(decodeDataUrlImage(imageUrl));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export { getPrompt as buildIconPrompt };
|
|
205
|
+
//# sourceMappingURL=image-generation-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-generation-service.js","sourceRoot":"","sources":["../../../src/services/ai/image-generation-service.ts"],"names":[],"mappings":"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"}
|