@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,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared theme CSS generator — works in both Node.js (server) and browser (frontend).
|
|
3
|
+
*
|
|
4
|
+
* Produces the full `:root { --dt-* }` CSS token block from an accent color and
|
|
5
|
+
* theme mode. Uses `culori` for hex→OKLCH conversion; all other logic is pure math.
|
|
6
|
+
*
|
|
7
|
+
* This module has NO DOM dependency.
|
|
8
|
+
*/
|
|
9
|
+
import { converter, parse } from 'culori';
|
|
10
|
+
const DEFAULT_ACCENT_COLOR = '#7c6ff7';
|
|
11
|
+
const toOklch = converter('oklch');
|
|
12
|
+
const SCALE = [
|
|
13
|
+
['50', 0.98, 0.005],
|
|
14
|
+
['100', 0.94, 0.01],
|
|
15
|
+
['150', 0.88, 0.015],
|
|
16
|
+
['200', 0.8, 0.02],
|
|
17
|
+
['300', 0.7, 0.025],
|
|
18
|
+
['400', 0.6, 0.03],
|
|
19
|
+
['500', 0.5, 0.04],
|
|
20
|
+
['600', 0.4, 0.03],
|
|
21
|
+
['700', 0.32, 0.025],
|
|
22
|
+
['800', 0.24, 0.03],
|
|
23
|
+
['850', 0.2, 0.035],
|
|
24
|
+
['900', 0.16, 0.04],
|
|
25
|
+
['950', 0.12, 0.05],
|
|
26
|
+
];
|
|
27
|
+
const STATUS_HUES = {
|
|
28
|
+
danger: 25,
|
|
29
|
+
success: 155,
|
|
30
|
+
warning: 80,
|
|
31
|
+
info: 250,
|
|
32
|
+
};
|
|
33
|
+
export const DEFAULT_THEME_PREFERENCES = {
|
|
34
|
+
accentColor: DEFAULT_ACCENT_COLOR,
|
|
35
|
+
theme: 'dark',
|
|
36
|
+
};
|
|
37
|
+
function clamp(value, min, max) {
|
|
38
|
+
return Math.min(Math.max(value, min), max);
|
|
39
|
+
}
|
|
40
|
+
function wrapHue(value) {
|
|
41
|
+
const wrapped = value % 360;
|
|
42
|
+
return wrapped < 0 ? wrapped + 360 : wrapped;
|
|
43
|
+
}
|
|
44
|
+
function formatNumber(value) {
|
|
45
|
+
return value
|
|
46
|
+
.toFixed(3)
|
|
47
|
+
.replace(/\.0+$/, '')
|
|
48
|
+
.replace(/(\.\d*?)0+$/, '$1');
|
|
49
|
+
}
|
|
50
|
+
function oklchValue(lightness, chroma, hue, alpha) {
|
|
51
|
+
const base = `oklch(${formatNumber(clamp(lightness, 0, 1))} ${formatNumber(clamp(chroma, 0, 0.4))} ${formatNumber(wrapHue(hue))}`;
|
|
52
|
+
return alpha === undefined ? `${base})` : `${base} / ${formatNumber(clamp(alpha, 0, 1))})`;
|
|
53
|
+
}
|
|
54
|
+
function normalizeMode(value) {
|
|
55
|
+
return value === 'dark' ? 'dark' : 'light';
|
|
56
|
+
}
|
|
57
|
+
function getAccentOklch(accentColor) {
|
|
58
|
+
const parsed = parse(accentColor) ?? parse(DEFAULT_ACCENT_COLOR);
|
|
59
|
+
const converted = parsed ? toOklch(parsed) : null;
|
|
60
|
+
if (!converted) {
|
|
61
|
+
return { l: 0.623, c: 0.234, h: 286 };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
l: clamp(converted.l ?? 0.623, 0, 1),
|
|
65
|
+
c: clamp(converted.c ?? 0.234, 0, 0.37),
|
|
66
|
+
h: wrapHue(converted.h ?? 286),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Generate the full `:root { --dt-* }` CSS text for the given theme preferences.
|
|
71
|
+
*
|
|
72
|
+
* This is the single source of truth for the DeskTalk color system.
|
|
73
|
+
*/
|
|
74
|
+
export function generateThemeCSS(preferences) {
|
|
75
|
+
const mode = normalizeMode(preferences.theme);
|
|
76
|
+
const dark = mode === 'dark';
|
|
77
|
+
const { l, c, h } = getAccentOklch(preferences.accentColor);
|
|
78
|
+
const lines = [];
|
|
79
|
+
const put = (name, value) => {
|
|
80
|
+
lines.push(` ${name}: ${value};`);
|
|
81
|
+
};
|
|
82
|
+
const scaleVar = (name) => `var(--dt-scale-${name})`;
|
|
83
|
+
put('--dt-accent-h', formatNumber(h));
|
|
84
|
+
put('--dt-accent-c', formatNumber(c));
|
|
85
|
+
put('--dt-accent-l', formatNumber(l));
|
|
86
|
+
put('--dt-mode-sign', dark ? '1' : '-1');
|
|
87
|
+
for (const [name, lightness, chroma] of SCALE) {
|
|
88
|
+
put(`--dt-scale-${name}`, oklchValue(lightness, chroma, h));
|
|
89
|
+
}
|
|
90
|
+
put('--dt-bg', dark ? scaleVar('950') : scaleVar('50'));
|
|
91
|
+
put('--dt-bg-subtle', dark ? scaleVar('900') : scaleVar('100'));
|
|
92
|
+
put('--dt-surface', dark ? scaleVar('850') : scaleVar('100'));
|
|
93
|
+
put('--dt-surface-hover', dark ? scaleVar('800') : scaleVar('150'));
|
|
94
|
+
put('--dt-surface-active', dark ? scaleVar('700') : scaleVar('200'));
|
|
95
|
+
put('--dt-overlay', dark ? oklchValue(0.16, 0.04, h, 0.85) : oklchValue(0.98, 0.005, h, 0.85));
|
|
96
|
+
put('--dt-text', dark ? scaleVar('50') : scaleVar('950'));
|
|
97
|
+
put('--dt-text-secondary', dark ? scaleVar('300') : scaleVar('600'));
|
|
98
|
+
put('--dt-text-muted', dark ? scaleVar('400') : scaleVar('500'));
|
|
99
|
+
put('--dt-border', dark ? scaleVar('800') : scaleVar('200'));
|
|
100
|
+
put('--dt-border-subtle', dark ? oklchValue(0.2, 0.035, h, 0.6) : oklchValue(0.88, 0.015, h, 0.6));
|
|
101
|
+
put('--dt-border-strong', dark ? scaleVar('700') : scaleVar('300'));
|
|
102
|
+
const accentHoverShift = dark ? 0.05 : -0.05;
|
|
103
|
+
const accentActiveShift = dark ? 0.1 : -0.1;
|
|
104
|
+
put('--dt-accent', oklchValue(l, c, h));
|
|
105
|
+
put('--dt-accent-hover', oklchValue(l + accentHoverShift, c, h));
|
|
106
|
+
put('--dt-accent-active', oklchValue(l + accentActiveShift, c, h));
|
|
107
|
+
put('--dt-accent-subtle', oklchValue(l, c, h, 0.15));
|
|
108
|
+
put('--dt-accent-ghost', oklchValue(l, c, h, 0.08));
|
|
109
|
+
put('--dt-text-on-accent', l > 0.6 ? scaleVar('950') : scaleVar('50'));
|
|
110
|
+
for (const [name, hue] of Object.entries(STATUS_HUES)) {
|
|
111
|
+
const lightness = dark ? 0.7 : 0.45;
|
|
112
|
+
put(`--dt-${name}`, oklchValue(lightness, 0.18, hue));
|
|
113
|
+
put(`--dt-${name}-subtle`, oklchValue(lightness, 0.18, hue, 0.15));
|
|
114
|
+
}
|
|
115
|
+
put('--dt-dock-bg', 'var(--dt-overlay)');
|
|
116
|
+
put('--dt-actions-bar-bg', 'var(--dt-bg-subtle)');
|
|
117
|
+
put('--dt-window-chrome', 'var(--dt-surface)');
|
|
118
|
+
put('--dt-window-body', 'var(--dt-bg)');
|
|
119
|
+
put('--dt-info-panel-bg', 'var(--dt-bg-subtle)');
|
|
120
|
+
put('--dt-wallpaper', dark
|
|
121
|
+
? `linear-gradient(135deg, ${oklchValue(0.25, 0.08, h)} 0%, ${oklchValue(0.3, 0.1, h)} 50%, ${oklchValue(0.4, 0.04, h + 30)} 100%)`
|
|
122
|
+
: `linear-gradient(135deg, ${oklchValue(0.85, 0.06, h)} 0%, ${oklchValue(0.8, 0.08, h)} 50%, ${oklchValue(0.75, 0.03, h + 30)} 100%)`);
|
|
123
|
+
put('--dt-glass', dark ? oklchValue(0.2, 0.035, h, 0.7) : oklchValue(0.94, 0.01, h, 0.7));
|
|
124
|
+
put('--dt-glass-border', dark ? oklchValue(0.98, 0.005, h, 0.1) : oklchValue(0.12, 0.05, h, 0.12));
|
|
125
|
+
put('--dt-glass-highlight', dark ? oklchValue(0.98, 0.005, h, 0.05) : oklchValue(0.98, 0.005, h, 0.45));
|
|
126
|
+
put('--dt-shadow-color', dark ? oklchValue(0.12, 0.05, h, 0.4) : oklchValue(0.12, 0.05, h, 0.15));
|
|
127
|
+
return `:root {\n${lines.join('\n')}\n}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Base stylesheet for AI-generated HTML.
|
|
131
|
+
*
|
|
132
|
+
* Provides sensible defaults for body, headings, tables, cards, buttons, and
|
|
133
|
+
* code blocks — all using semantic `--dt-*` tokens. This ensures generated HTML
|
|
134
|
+
* looks native to DeskTalk without the AI needing to emit its own reset/base styles.
|
|
135
|
+
*/
|
|
136
|
+
export const HTML_BASE_STYLESHEET = `
|
|
137
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
138
|
+
body {
|
|
139
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
140
|
+
background: var(--dt-bg);
|
|
141
|
+
color: var(--dt-text);
|
|
142
|
+
line-height: 1.6;
|
|
143
|
+
padding: 24px;
|
|
144
|
+
}
|
|
145
|
+
h1, h2, h3, h4, h5, h6 {
|
|
146
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif !important;
|
|
147
|
+
color: var(--dt-text) !important;
|
|
148
|
+
line-height: 1.3 !important;
|
|
149
|
+
margin-top: 0 !important;
|
|
150
|
+
margin-bottom: 0.5em !important;
|
|
151
|
+
font-style: normal !important;
|
|
152
|
+
text-transform: none !important;
|
|
153
|
+
letter-spacing: -0.01em !important;
|
|
154
|
+
}
|
|
155
|
+
h1 { font-size: 2rem !important; font-weight: 700 !important; letter-spacing: -0.02em !important; }
|
|
156
|
+
h2 { font-size: 1.5rem !important; font-weight: 600 !important; }
|
|
157
|
+
h3 { font-size: 1.25rem !important; font-weight: 600 !important; }
|
|
158
|
+
h4 { font-size: 1.125rem !important; font-weight: 600 !important; }
|
|
159
|
+
h5 { font-size: 1rem !important; font-weight: 600 !important; }
|
|
160
|
+
h6 { font-size: 0.875rem !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.05em !important; }
|
|
161
|
+
p {
|
|
162
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif !important;
|
|
163
|
+
font-size: 1rem !important;
|
|
164
|
+
font-weight: 400 !important;
|
|
165
|
+
line-height: 1.6 !important;
|
|
166
|
+
color: var(--dt-text-secondary) !important;
|
|
167
|
+
margin-top: 0 !important;
|
|
168
|
+
margin-bottom: 1em !important;
|
|
169
|
+
}
|
|
170
|
+
a { color: var(--dt-accent); text-decoration: none; }
|
|
171
|
+
a:hover { color: var(--dt-accent-hover); text-decoration: underline; }
|
|
172
|
+
hr {
|
|
173
|
+
border: none;
|
|
174
|
+
border-top: 1px solid var(--dt-border);
|
|
175
|
+
margin: 1.5em 0;
|
|
176
|
+
}
|
|
177
|
+
table {
|
|
178
|
+
width: 100%;
|
|
179
|
+
border-collapse: collapse;
|
|
180
|
+
margin-bottom: 1em;
|
|
181
|
+
}
|
|
182
|
+
th, td {
|
|
183
|
+
padding: 8px 12px;
|
|
184
|
+
text-align: left;
|
|
185
|
+
border-bottom: 1px solid var(--dt-border);
|
|
186
|
+
}
|
|
187
|
+
th {
|
|
188
|
+
background: var(--dt-surface);
|
|
189
|
+
color: var(--dt-text);
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
}
|
|
192
|
+
tr:hover td { background: var(--dt-surface-hover); }
|
|
193
|
+
code {
|
|
194
|
+
font-family: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
|
|
195
|
+
background: var(--dt-surface);
|
|
196
|
+
padding: 2px 6px;
|
|
197
|
+
border-radius: 4px;
|
|
198
|
+
font-size: 0.875em;
|
|
199
|
+
}
|
|
200
|
+
pre {
|
|
201
|
+
background: var(--dt-surface);
|
|
202
|
+
border: 1px solid var(--dt-border);
|
|
203
|
+
border-radius: 8px;
|
|
204
|
+
padding: 16px;
|
|
205
|
+
overflow-x: auto;
|
|
206
|
+
margin-bottom: 1em;
|
|
207
|
+
}
|
|
208
|
+
pre code { background: none; padding: 0; }
|
|
209
|
+
.text-muted { color: var(--dt-text-muted); }
|
|
210
|
+
.text-secondary { color: var(--dt-text-secondary); }
|
|
211
|
+
.accent-bg { background: var(--dt-accent-subtle); }
|
|
212
|
+
`.trim();
|
|
213
|
+
//# sourceMappingURL=theme-css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-css.js","sourceRoot":"","sources":["../../src/services/theme-css.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User database backed by SQLite (better-sqlite3).
|
|
3
|
+
*
|
|
4
|
+
* Manages the `users` and `sessions` tables in `<data>/users.db`.
|
|
5
|
+
* Passwords are hashed with bcrypt (bcryptjs). Sessions are opaque
|
|
6
|
+
* random tokens stored server-side.
|
|
7
|
+
*/
|
|
8
|
+
export type UserRole = 'admin' | 'user';
|
|
9
|
+
export interface UserRecord {
|
|
10
|
+
username: string;
|
|
11
|
+
display_name: string;
|
|
12
|
+
password: string;
|
|
13
|
+
role: UserRole;
|
|
14
|
+
created_at: string;
|
|
15
|
+
updated_at: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SessionRecord {
|
|
18
|
+
token: string;
|
|
19
|
+
username: string;
|
|
20
|
+
expires_at: string;
|
|
21
|
+
created_at: string;
|
|
22
|
+
}
|
|
23
|
+
/** Public user info returned to clients (no password hash). */
|
|
24
|
+
export interface PublicUser {
|
|
25
|
+
username: string;
|
|
26
|
+
displayName: string;
|
|
27
|
+
role: UserRole;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Open (or create) the users database at `<dataDir>/users.db`.
|
|
33
|
+
* Creates tables if they don't exist. No default accounts are seeded —
|
|
34
|
+
* the admin account is created during the onboarding flow.
|
|
35
|
+
*/
|
|
36
|
+
export declare function initUserDb(dataDir: string): void;
|
|
37
|
+
/** Close the database (for graceful shutdown). */
|
|
38
|
+
export declare function closeUserDb(): void;
|
|
39
|
+
/** Find a user by username, or undefined if not found. */
|
|
40
|
+
export declare function findUser(username: string): PublicUser | undefined;
|
|
41
|
+
/** List all users (public info only). */
|
|
42
|
+
export declare function listUsers(): PublicUser[];
|
|
43
|
+
/** Create a new user. Throws if username already exists. */
|
|
44
|
+
export declare function createUser(username: string, password: string, role?: UserRole, displayName?: string): PublicUser;
|
|
45
|
+
/** Delete a user and all their sessions (CASCADE). */
|
|
46
|
+
export declare function deleteUser(username: string): boolean;
|
|
47
|
+
/** Update a user's role. */
|
|
48
|
+
export declare function updateUserRole(username: string, role: UserRole): boolean;
|
|
49
|
+
/** Update a user's password (caller must verify the old password first). */
|
|
50
|
+
export declare function updateUserPassword(username: string, newPassword: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Check whether any admin user exists in the database.
|
|
53
|
+
* Used to decide if the system needs the onboarding flow (first-time setup).
|
|
54
|
+
*/
|
|
55
|
+
export declare function hasAdmin(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Verify a plaintext password against the stored bcrypt hash.
|
|
58
|
+
* Returns the public user record on success, or undefined on failure.
|
|
59
|
+
*/
|
|
60
|
+
export declare function verifyPassword(username: string, password: string): PublicUser | undefined;
|
|
61
|
+
/** Create a new session for the given user. Returns the token. */
|
|
62
|
+
export declare function createSession(username: string): string;
|
|
63
|
+
/** Validate a session token. Returns the username if valid, or undefined. */
|
|
64
|
+
export declare function validateSession(token: string): PublicUser | undefined;
|
|
65
|
+
/** Delete a session (logout). */
|
|
66
|
+
export declare function deleteSession(token: string): void;
|
|
67
|
+
/** Delete all sessions for a user. */
|
|
68
|
+
export declare function deleteUserSessions(username: string): void;
|
|
69
|
+
//# sourceMappingURL=user-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-db.d.ts","sourceRoot":"","sources":["../../src/services/user-db.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAExC,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA+BhD;AAUD,kDAAkD;AAClD,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAcD,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKjE;AAED,yCAAyC;AACzC,wBAAgB,SAAS,IAAI,UAAU,EAAE,CAGxC;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,QAAiB,EACvB,WAAW,CAAC,EAAE,MAAM,GACnB,UAAU,CAUZ;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED,4BAA4B;AAC5B,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAMxE;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAOjF;AAED;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAKlC;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAOzF;AAID,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYtD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAarE;AAED,iCAAiC;AACjC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,sCAAsC;AACtC,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEzD"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User database backed by SQLite (better-sqlite3).
|
|
3
|
+
*
|
|
4
|
+
* Manages the `users` and `sessions` tables in `<data>/users.db`.
|
|
5
|
+
* Passwords are hashed with bcrypt (bcryptjs). Sessions are opaque
|
|
6
|
+
* random tokens stored server-side.
|
|
7
|
+
*/
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
import bcrypt from 'bcryptjs';
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
13
|
+
const BCRYPT_ROUNDS = 12;
|
|
14
|
+
const SESSION_TOKEN_BYTES = 32;
|
|
15
|
+
const SESSION_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
16
|
+
// ─── Database ────────────────────────────────────────────────────────────────
|
|
17
|
+
let db = null;
|
|
18
|
+
/**
|
|
19
|
+
* Open (or create) the users database at `<dataDir>/users.db`.
|
|
20
|
+
* Creates tables if they don't exist. No default accounts are seeded —
|
|
21
|
+
* the admin account is created during the onboarding flow.
|
|
22
|
+
*/
|
|
23
|
+
export function initUserDb(dataDir) {
|
|
24
|
+
const dbPath = join(dataDir, 'users.db');
|
|
25
|
+
db = new Database(dbPath);
|
|
26
|
+
// Enable WAL mode for better concurrent read performance
|
|
27
|
+
db.pragma('journal_mode = WAL');
|
|
28
|
+
db.pragma('foreign_keys = ON');
|
|
29
|
+
db.exec(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
31
|
+
username TEXT PRIMARY KEY,
|
|
32
|
+
display_name TEXT NOT NULL,
|
|
33
|
+
password TEXT NOT NULL,
|
|
34
|
+
role TEXT NOT NULL DEFAULT 'user',
|
|
35
|
+
created_at TEXT NOT NULL,
|
|
36
|
+
updated_at TEXT NOT NULL
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
40
|
+
token TEXT PRIMARY KEY,
|
|
41
|
+
username TEXT NOT NULL REFERENCES users(username) ON DELETE CASCADE,
|
|
42
|
+
expires_at TEXT NOT NULL,
|
|
43
|
+
created_at TEXT NOT NULL
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
|
48
|
+
`);
|
|
49
|
+
// No default admin account is seeded. The admin is created during
|
|
50
|
+
// the onboarding flow (POST /api/setup).
|
|
51
|
+
}
|
|
52
|
+
/** Get the raw database instance. Throws if not initialized. */
|
|
53
|
+
function getDb() {
|
|
54
|
+
if (!db) {
|
|
55
|
+
throw new Error('User database not initialized. Call initUserDb() first.');
|
|
56
|
+
}
|
|
57
|
+
return db;
|
|
58
|
+
}
|
|
59
|
+
/** Close the database (for graceful shutdown). */
|
|
60
|
+
export function closeUserDb() {
|
|
61
|
+
if (db) {
|
|
62
|
+
db.close();
|
|
63
|
+
db = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ─── User CRUD ───────────────────────────────────────────────────────────────
|
|
67
|
+
function toPublicUser(row) {
|
|
68
|
+
return {
|
|
69
|
+
username: row.username,
|
|
70
|
+
displayName: row.display_name,
|
|
71
|
+
role: row.role,
|
|
72
|
+
createdAt: row.created_at,
|
|
73
|
+
updatedAt: row.updated_at,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Find a user by username, or undefined if not found. */
|
|
77
|
+
export function findUser(username) {
|
|
78
|
+
const row = getDb().prepare('SELECT * FROM users WHERE username = ?').get(username);
|
|
79
|
+
return row ? toPublicUser(row) : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** List all users (public info only). */
|
|
82
|
+
export function listUsers() {
|
|
83
|
+
const rows = getDb().prepare('SELECT * FROM users ORDER BY created_at ASC').all();
|
|
84
|
+
return rows.map(toPublicUser);
|
|
85
|
+
}
|
|
86
|
+
/** Create a new user. Throws if username already exists. */
|
|
87
|
+
export function createUser(username, password, role = 'user', displayName) {
|
|
88
|
+
const now = new Date().toISOString();
|
|
89
|
+
const hash = bcrypt.hashSync(password, BCRYPT_ROUNDS);
|
|
90
|
+
getDb()
|
|
91
|
+
.prepare(`INSERT INTO users (username, display_name, password, role, created_at, updated_at)
|
|
92
|
+
VALUES (?, ?, ?, ?, ?, ?)`)
|
|
93
|
+
.run(username, displayName ?? username, hash, role, now, now);
|
|
94
|
+
return findUser(username);
|
|
95
|
+
}
|
|
96
|
+
/** Delete a user and all their sessions (CASCADE). */
|
|
97
|
+
export function deleteUser(username) {
|
|
98
|
+
const result = getDb().prepare('DELETE FROM users WHERE username = ?').run(username);
|
|
99
|
+
return result.changes > 0;
|
|
100
|
+
}
|
|
101
|
+
/** Update a user's role. */
|
|
102
|
+
export function updateUserRole(username, role) {
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
const result = getDb()
|
|
105
|
+
.prepare('UPDATE users SET role = ?, updated_at = ? WHERE username = ?')
|
|
106
|
+
.run(role, now, username);
|
|
107
|
+
return result.changes > 0;
|
|
108
|
+
}
|
|
109
|
+
/** Update a user's password (caller must verify the old password first). */
|
|
110
|
+
export function updateUserPassword(username, newPassword) {
|
|
111
|
+
const now = new Date().toISOString();
|
|
112
|
+
const hash = bcrypt.hashSync(newPassword, BCRYPT_ROUNDS);
|
|
113
|
+
const result = getDb()
|
|
114
|
+
.prepare('UPDATE users SET password = ?, updated_at = ? WHERE username = ?')
|
|
115
|
+
.run(hash, now, username);
|
|
116
|
+
return result.changes > 0;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check whether any admin user exists in the database.
|
|
120
|
+
* Used to decide if the system needs the onboarding flow (first-time setup).
|
|
121
|
+
*/
|
|
122
|
+
export function hasAdmin() {
|
|
123
|
+
const row = getDb().prepare("SELECT COUNT(*) AS cnt FROM users WHERE role = 'admin'").get();
|
|
124
|
+
return row.cnt > 0;
|
|
125
|
+
}
|
|
126
|
+
// ─── Password verification ──────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Verify a plaintext password against the stored bcrypt hash.
|
|
129
|
+
* Returns the public user record on success, or undefined on failure.
|
|
130
|
+
*/
|
|
131
|
+
export function verifyPassword(username, password) {
|
|
132
|
+
const row = getDb().prepare('SELECT * FROM users WHERE username = ?').get(username);
|
|
133
|
+
if (!row)
|
|
134
|
+
return undefined;
|
|
135
|
+
const valid = bcrypt.compareSync(password, row.password);
|
|
136
|
+
return valid ? toPublicUser(row) : undefined;
|
|
137
|
+
}
|
|
138
|
+
// ─── Sessions ────────────────────────────────────────────────────────────────
|
|
139
|
+
/** Create a new session for the given user. Returns the token. */
|
|
140
|
+
export function createSession(username) {
|
|
141
|
+
const token = randomBytes(SESSION_TOKEN_BYTES).toString('hex');
|
|
142
|
+
const now = new Date();
|
|
143
|
+
const expiresAt = new Date(now.getTime() + SESSION_LIFETIME_MS);
|
|
144
|
+
getDb()
|
|
145
|
+
.prepare('INSERT INTO sessions (token, username, expires_at, created_at) VALUES (?, ?, ?, ?)')
|
|
146
|
+
.run(token, username, expiresAt.toISOString(), now.toISOString());
|
|
147
|
+
// Prune expired sessions opportunistically
|
|
148
|
+
pruneExpiredSessions();
|
|
149
|
+
return token;
|
|
150
|
+
}
|
|
151
|
+
/** Validate a session token. Returns the username if valid, or undefined. */
|
|
152
|
+
export function validateSession(token) {
|
|
153
|
+
const row = getDb().prepare('SELECT * FROM sessions WHERE token = ?').get(token);
|
|
154
|
+
if (!row)
|
|
155
|
+
return undefined;
|
|
156
|
+
// Check expiration
|
|
157
|
+
if (new Date(row.expires_at) <= new Date()) {
|
|
158
|
+
getDb().prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return findUser(row.username);
|
|
162
|
+
}
|
|
163
|
+
/** Delete a session (logout). */
|
|
164
|
+
export function deleteSession(token) {
|
|
165
|
+
getDb().prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
|
166
|
+
}
|
|
167
|
+
/** Delete all sessions for a user. */
|
|
168
|
+
export function deleteUserSessions(username) {
|
|
169
|
+
getDb().prepare('DELETE FROM sessions WHERE username = ?').run(username);
|
|
170
|
+
}
|
|
171
|
+
/** Remove expired session rows. */
|
|
172
|
+
function pruneExpiredSessions() {
|
|
173
|
+
getDb().prepare('DELETE FROM sessions WHERE expires_at <= ?').run(new Date().toISOString());
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=user-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-db.js","sourceRoot":"","sources":["../../src/services/user-db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-format.d.ts","sourceRoot":"","sources":["../../../src/services/voice/audio-format.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAwB7E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a minimal WAV header for PCM s16le audio.
|
|
3
|
+
*/
|
|
4
|
+
export function createWavBuffer(pcmBuffer, sampleRate) {
|
|
5
|
+
const numChannels = 1;
|
|
6
|
+
const bitsPerSample = 16;
|
|
7
|
+
const byteRate = sampleRate * numChannels * (bitsPerSample / 8);
|
|
8
|
+
const blockAlign = numChannels * (bitsPerSample / 8);
|
|
9
|
+
const dataSize = pcmBuffer.length;
|
|
10
|
+
const headerSize = 44;
|
|
11
|
+
const header = Buffer.alloc(headerSize);
|
|
12
|
+
header.write('RIFF', 0);
|
|
13
|
+
header.writeUInt32LE(36 + dataSize, 4);
|
|
14
|
+
header.write('WAVE', 8);
|
|
15
|
+
header.write('fmt ', 12);
|
|
16
|
+
header.writeUInt32LE(16, 16);
|
|
17
|
+
header.writeUInt16LE(1, 20);
|
|
18
|
+
header.writeUInt16LE(numChannels, 22);
|
|
19
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
20
|
+
header.writeUInt32LE(byteRate, 28);
|
|
21
|
+
header.writeUInt16LE(blockAlign, 32);
|
|
22
|
+
header.writeUInt16LE(bitsPerSample, 34);
|
|
23
|
+
header.write('data', 36);
|
|
24
|
+
header.writeUInt32LE(dataSize, 40);
|
|
25
|
+
return Buffer.concat([header, pcmBuffer]);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=audio-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-format.js","sourceRoot":"","sources":["../../../src/services/voice/audio-format.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,UAAkB;IACnE,MAAM,WAAW,GAAG,CAAC,CAAC;IACtB,MAAM,aAAa,GAAG,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,WAAW,GAAG,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;IAClC,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,aAAa,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzB,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7B,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5B,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzB,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEnC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure OpenAI Whisper STT adapter.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Azure OpenAI audio transcriptions endpoint with deployment-based
|
|
5
|
+
* routing and api-key authentication.
|
|
6
|
+
*/
|
|
7
|
+
import type { SttAdapter, SttTranscript } from './stt-adapter.js';
|
|
8
|
+
export declare class AzureOpenAIWhisperAdapter implements SttAdapter {
|
|
9
|
+
readonly name = "azure-openai-whisper";
|
|
10
|
+
private apiKey;
|
|
11
|
+
private endpoint;
|
|
12
|
+
private deployment;
|
|
13
|
+
private apiVersion;
|
|
14
|
+
constructor(options: {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
endpoint: string;
|
|
17
|
+
deployment: string;
|
|
18
|
+
apiVersion?: string;
|
|
19
|
+
});
|
|
20
|
+
transcribe(audioBuffer: Buffer, sampleRate: number, language?: string): Promise<SttTranscript>;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=azure-openai-whisper-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-openai-whisper-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/voice/azure-openai-whisper-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAO/D,qBAAa,yBAA0B,YAAW,UAAU;IAC1D,QAAQ,CAAC,IAAI,0BAA0B;IACvC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IAOK,UAAU,CACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IAsDzB,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure OpenAI Whisper STT adapter.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Azure OpenAI audio transcriptions endpoint with deployment-based
|
|
5
|
+
* routing and api-key authentication.
|
|
6
|
+
*/
|
|
7
|
+
import { createWavBuffer } from './audio-format.js';
|
|
8
|
+
function trimTrailingSlash(value) {
|
|
9
|
+
return value.replace(/\/+$/, '');
|
|
10
|
+
}
|
|
11
|
+
export class AzureOpenAIWhisperAdapter {
|
|
12
|
+
name = 'azure-openai-whisper';
|
|
13
|
+
apiKey;
|
|
14
|
+
endpoint;
|
|
15
|
+
deployment;
|
|
16
|
+
apiVersion;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.apiKey = options.apiKey;
|
|
19
|
+
this.endpoint = trimTrailingSlash(options.endpoint);
|
|
20
|
+
this.deployment = options.deployment;
|
|
21
|
+
this.apiVersion = options.apiVersion ?? '2024-06-01';
|
|
22
|
+
}
|
|
23
|
+
async transcribe(audioBuffer, sampleRate, language) {
|
|
24
|
+
const wavBuffer = createWavBuffer(audioBuffer, sampleRate);
|
|
25
|
+
const durationMs = Math.round((audioBuffer.length / 2 / sampleRate) * 1000);
|
|
26
|
+
const boundary = `----DeskTalkBoundary${Date.now()}`;
|
|
27
|
+
const parts = [];
|
|
28
|
+
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="response_format"\r\n\r\njson\r\n`));
|
|
29
|
+
// language field (ISO-639-1 code, e.g. 'en', 'zh')
|
|
30
|
+
if (language) {
|
|
31
|
+
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="language"\r\n\r\n${language}\r\n`));
|
|
32
|
+
}
|
|
33
|
+
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="audio.wav"\r\nContent-Type: audio/wav\r\n\r\n`));
|
|
34
|
+
parts.push(wavBuffer);
|
|
35
|
+
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
|
36
|
+
const body = Buffer.concat(parts);
|
|
37
|
+
const url = `${this.endpoint}/openai/deployments/${encodeURIComponent(this.deployment)}/audio/transcriptions?api-version=${encodeURIComponent(this.apiVersion)}`;
|
|
38
|
+
const response = await fetch(url, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'api-key': this.apiKey,
|
|
42
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
43
|
+
},
|
|
44
|
+
body,
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const errorText = await response.text();
|
|
48
|
+
throw new Error(`Azure OpenAI Whisper API error (${response.status}): ${errorText}`);
|
|
49
|
+
}
|
|
50
|
+
const result = (await response.json());
|
|
51
|
+
return {
|
|
52
|
+
text: result.text.trim(),
|
|
53
|
+
durationMs,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
close() {
|
|
57
|
+
// No persistent resources to clean up
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=azure-openai-whisper-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-openai-whisper-adapter.js","sourceRoot":"","sources":["../../../src/services/voice/azure-openai-whisper-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,OAAO,yBAAyB;IAC3B,IAAI,GAAG,sBAAsB,CAAC;IAC/B,MAAM,CAAS;IACf,QAAQ,CAAS;IACjB,UAAU,CAAS;IACnB,UAAU,CAAS;IAE3B,YAAY,OAKX;QACC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,YAAY,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,UAAU,CACd,WAAmB,EACnB,UAAkB,EAClB,QAAiB;QAEjB,MAAM,SAAS,GAAG,eAAe,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,uBAAuB,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACrD,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,IAAI,CACT,KAAK,QAAQ,4EAA4E,CAC1F,CACF,CAAC;QAEF,mDAAmD;QACnD,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,IAAI,CACT,KAAK,QAAQ,8DAA8D,QAAQ,MAAM,CAC1F,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,IAAI,CACT,KAAK,QAAQ,0GAA0G,CACxH,CACF,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,QAAQ,QAAQ,CAAC,CAAC,CAAC;QAEnD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,qCAAqC,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAEjK,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,SAAS,EAAE,IAAI,CAAC,MAAM;gBACtB,cAAc,EAAE,iCAAiC,QAAQ,EAAE;aAC5D;YACD,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,CAAC,MAAM,MAAM,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;QAE3D,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;YACxB,UAAU;SACX,CAAC;IACJ,CAAC;IAED,KAAK;QACH,sCAAsC;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Whisper STT adapter.
|
|
3
|
+
*
|
|
4
|
+
* Uses the /v1/audio/transcriptions endpoint to transcribe complete
|
|
5
|
+
* audio buffers. Audio is sent as WAV (PCM s16le, 16kHz, mono) since
|
|
6
|
+
* Whisper requires a file-like format.
|
|
7
|
+
*/
|
|
8
|
+
import type { SttAdapter, SttTranscript } from './stt-adapter.js';
|
|
9
|
+
export declare class OpenAIWhisperAdapter implements SttAdapter {
|
|
10
|
+
readonly name = "openai-whisper";
|
|
11
|
+
private apiKey;
|
|
12
|
+
private model;
|
|
13
|
+
private baseUrl;
|
|
14
|
+
constructor(options: {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
});
|
|
19
|
+
transcribe(audioBuffer: Buffer, sampleRate: number, language?: string): Promise<SttTranscript>;
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=openai-whisper-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-whisper-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/voice/openai-whisper-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG/D,qBAAa,oBAAqB,YAAW,UAAU;IACrD,QAAQ,CAAC,IAAI,oBAAoB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAMnE,UAAU,CACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IAiEzB,KAAK,IAAI,IAAI;CAGd"}
|