@bonginkan/maria-lite 8.0.2 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/assets/vui/avatar/live2d/live2dcubismcore.min.js +18211 -0
- package/dist/cli.cjs +7697 -3463
- package/dist/desktop-client.js +631 -30
- package/dist/ext.cjs +1 -1
- package/dist/ext.d.cts +1 -1
- package/dist/vui-avatar-client.js +51078 -0
- package/origin/index.meta.json +1 -1
- package/package.json +4 -2
package/dist/desktop-client.js
CHANGED
|
@@ -403,6 +403,7 @@ var init_helpers = __esm({
|
|
|
403
403
|
"dev-adviser": "user-check",
|
|
404
404
|
"dev-decision": "compass",
|
|
405
405
|
estimate: "ruler",
|
|
406
|
+
analytics: "bar-chart-big",
|
|
406
407
|
// CXO Agents
|
|
407
408
|
ceo: "crown",
|
|
408
409
|
coo: "target",
|
|
@@ -498,6 +499,7 @@ var init_helpers = __esm({
|
|
|
498
499
|
config: "settings",
|
|
499
500
|
"notification-router": "bell-ring",
|
|
500
501
|
events: "radio",
|
|
502
|
+
vui: "mic",
|
|
501
503
|
"task-manager": "activity",
|
|
502
504
|
// Games
|
|
503
505
|
minesweeper: "bomb",
|
|
@@ -660,7 +662,7 @@ var init_state = __esm({
|
|
|
660
662
|
label: "Daily Intelligence",
|
|
661
663
|
emoji: "\u{1F4CA}",
|
|
662
664
|
serverCategories: ["daily"],
|
|
663
|
-
commandIds: ["daily-report", "daily-news", "daily-papers", "daily-insights", "weekly-report", "discord-setup", "billing-pl"]
|
|
665
|
+
commandIds: ["daily-report", "daily-news", "daily-papers", "daily-insights", "weekly-report", "discord-setup", "billing-pl", "analytics"]
|
|
664
666
|
},
|
|
665
667
|
{
|
|
666
668
|
id: "gtm",
|
|
@@ -767,7 +769,7 @@ var init_state = __esm({
|
|
|
767
769
|
label: "System",
|
|
768
770
|
emoji: "\u2699\uFE0F",
|
|
769
771
|
serverCategories: [],
|
|
770
|
-
commandIds: ["help", "version", "gui", "desktop", "login", "logout", "account", "vup", "schedule", "config", "notification-router", "events"]
|
|
772
|
+
commandIds: ["help", "version", "gui", "desktop", "login", "logout", "account", "vup", "schedule", "config", "notification-router", "events", "vui"]
|
|
771
773
|
}
|
|
772
774
|
];
|
|
773
775
|
state = {
|
|
@@ -2428,6 +2430,54 @@ var init_uni_editor = __esm({
|
|
|
2428
2430
|
}
|
|
2429
2431
|
});
|
|
2430
2432
|
|
|
2433
|
+
// services/desktop/client/modules/desktop-upload.ts
|
|
2434
|
+
function normalizeUploadRelativePath(rawPath, fallbackName) {
|
|
2435
|
+
const parts = String(rawPath || "").replace(/\\/g, "/").split("/").map((part) => part.trim()).filter((part) => part && part !== "." && part !== "..");
|
|
2436
|
+
if (parts.length === 0) {
|
|
2437
|
+
return { dir: "", filename: fallbackName };
|
|
2438
|
+
}
|
|
2439
|
+
const filename = parts.pop() || fallbackName;
|
|
2440
|
+
return { dir: parts.join("/"), filename };
|
|
2441
|
+
}
|
|
2442
|
+
function joinDesktopDirs(...parts) {
|
|
2443
|
+
return parts.map((part) => String(part || "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
2444
|
+
}
|
|
2445
|
+
async function uploadFileToDesktopAtWithPath(dir, file, relativePath = "") {
|
|
2446
|
+
const normalized = normalizeUploadRelativePath(relativePath, file.name || "upload.bin");
|
|
2447
|
+
const targetDir = joinDesktopDirs(dir, normalized.dir);
|
|
2448
|
+
const formData = new FormData();
|
|
2449
|
+
formData.append("file", file, normalized.filename);
|
|
2450
|
+
if (targetDir) formData.append("dir", targetDir);
|
|
2451
|
+
try {
|
|
2452
|
+
const res = await api("/api/desktop/upload", { method: "POST", body: formData });
|
|
2453
|
+
if (!res.ok) {
|
|
2454
|
+
const body = await res.text().catch(() => "");
|
|
2455
|
+
console.error(`[upload] failed for ${file.name}: HTTP ${res.status}`, body);
|
|
2456
|
+
return "";
|
|
2457
|
+
}
|
|
2458
|
+
const json = await res.json().catch(() => null);
|
|
2459
|
+
if (json?.ok && json.files?.[0]?.path) return json.files[0].path;
|
|
2460
|
+
return "";
|
|
2461
|
+
} catch (err) {
|
|
2462
|
+
console.error(`[upload] error for ${file.name}:`, err);
|
|
2463
|
+
return "";
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
async function uploadFileListToDesktopAt(dir, files, options) {
|
|
2467
|
+
const out = [];
|
|
2468
|
+
for (const file of files) {
|
|
2469
|
+
const relativePath = options?.preserveRelativePaths ? file.webkitRelativePath || file.name : file.name;
|
|
2470
|
+
const uploaded = await uploadFileToDesktopAtWithPath(dir, file, relativePath);
|
|
2471
|
+
if (uploaded) out.push(uploaded);
|
|
2472
|
+
}
|
|
2473
|
+
return out;
|
|
2474
|
+
}
|
|
2475
|
+
var init_desktop_upload = __esm({
|
|
2476
|
+
"services/desktop/client/modules/desktop-upload.ts"() {
|
|
2477
|
+
init_helpers();
|
|
2478
|
+
}
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2431
2481
|
// services/desktop/client/modules/file-manager.ts
|
|
2432
2482
|
function fmHistoryPush(dir) {
|
|
2433
2483
|
if (state.fmHistoryIdx < state.fmHistory.length - 1) {
|
|
@@ -2516,6 +2566,7 @@ function renderFileManagerContent(win) {
|
|
|
2516
2566
|
html += '<button class="fm-refresh-btn" title="Refresh">\u21BB</button>';
|
|
2517
2567
|
html += '<button class="fm-newdir-btn" title="New folder">\u{1F4C1}+</button>';
|
|
2518
2568
|
html += '<label class="fm-upload-btn" title="Upload">\u2B06\uFE0F<input type="file" class="fm-upload-input" multiple style="display:none" /></label>';
|
|
2569
|
+
html += '<label class="fm-upload-btn" title="Upload folder">\u{1F4C1}\u2B06\uFE0F<input type="file" class="fm-upload-dir-input" webkitdirectory directory multiple style="display:none" /></label>';
|
|
2519
2570
|
html += "</div>";
|
|
2520
2571
|
html += "</div>";
|
|
2521
2572
|
html += '<div class="fm-list">';
|
|
@@ -2632,9 +2683,19 @@ function attachFileManagerListeners(win) {
|
|
|
2632
2683
|
uploadInput.addEventListener("change", () => {
|
|
2633
2684
|
const files = Array.from(uploadInput.files || []);
|
|
2634
2685
|
void (async () => {
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2686
|
+
await uploadFileListToDesktopAt(state.currentDir, files);
|
|
2687
|
+
await loadDesktopFiles(state.currentDir);
|
|
2688
|
+
refreshFileManagerContent(win);
|
|
2689
|
+
renderDesktopFiles();
|
|
2690
|
+
})();
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
const uploadDirInput = el.querySelector(".fm-upload-dir-input");
|
|
2694
|
+
if (uploadDirInput) {
|
|
2695
|
+
uploadDirInput.addEventListener("change", () => {
|
|
2696
|
+
const files = Array.from(uploadDirInput.files || []);
|
|
2697
|
+
void (async () => {
|
|
2698
|
+
await uploadFileListToDesktopAt(state.currentDir, files, { preserveRelativePaths: true });
|
|
2638
2699
|
await loadDesktopFiles(state.currentDir);
|
|
2639
2700
|
refreshFileManagerContent(win);
|
|
2640
2701
|
renderDesktopFiles();
|
|
@@ -2733,6 +2794,7 @@ function showFileManagerContextMenu(x, y, name, kind, win) {
|
|
|
2733
2794
|
} else {
|
|
2734
2795
|
html += `<div class="ctx-item" data-action="fm-newdir"><span class="ctx-icon">\u{1F4C1}</span><span>New Folder</span></div>`;
|
|
2735
2796
|
html += `<div class="ctx-item" data-action="fm-upload"><span class="ctx-icon">\u{1F4C4}</span><span>Upload File</span></div>`;
|
|
2797
|
+
html += `<div class="ctx-item" data-action="fm-upload-folder"><span class="ctx-icon">\u{1F4C1}</span><span>Upload Folder</span></div>`;
|
|
2736
2798
|
html += '<div class="ctx-sep"></div>';
|
|
2737
2799
|
html += `<div class="ctx-item" data-action="fm-refresh"><span class="ctx-icon">\u{1F504}</span><span>Refresh</span></div>`;
|
|
2738
2800
|
}
|
|
@@ -2825,6 +2887,27 @@ function showFileManagerContextMenu(x, y, name, kind, win) {
|
|
|
2825
2887
|
inp.click();
|
|
2826
2888
|
break;
|
|
2827
2889
|
}
|
|
2890
|
+
case "fm-upload-folder": {
|
|
2891
|
+
const inp = document.createElement("input");
|
|
2892
|
+
inp.type = "file";
|
|
2893
|
+
inp.multiple = true;
|
|
2894
|
+
inp.style.display = "none";
|
|
2895
|
+
inp.setAttribute("webkitdirectory", "");
|
|
2896
|
+
inp.setAttribute("directory", "");
|
|
2897
|
+
document.body.appendChild(inp);
|
|
2898
|
+
inp.addEventListener("change", () => {
|
|
2899
|
+
const files = Array.from(inp.files || []);
|
|
2900
|
+
void (async () => {
|
|
2901
|
+
await uploadFileListToDesktopAt(state.currentDir, files, { preserveRelativePaths: true });
|
|
2902
|
+
inp.remove();
|
|
2903
|
+
await loadDesktopFiles(state.currentDir);
|
|
2904
|
+
refreshFileManagerContent(win);
|
|
2905
|
+
renderDesktopFiles();
|
|
2906
|
+
})();
|
|
2907
|
+
});
|
|
2908
|
+
inp.click();
|
|
2909
|
+
break;
|
|
2910
|
+
}
|
|
2828
2911
|
case "fm-refresh":
|
|
2829
2912
|
void loadDesktopFiles(state.currentDir).then(() => {
|
|
2830
2913
|
refreshFileManagerContent(win);
|
|
@@ -3668,25 +3751,6 @@ async function uploadFileToDesktop(file) {
|
|
|
3668
3751
|
async function uploadFileToDesktopAt(dir, file) {
|
|
3669
3752
|
await uploadFileToDesktopAtWithPath(dir, file);
|
|
3670
3753
|
}
|
|
3671
|
-
async function uploadFileToDesktopAtWithPath(dir, file) {
|
|
3672
|
-
const formData = new FormData();
|
|
3673
|
-
formData.append("file", file);
|
|
3674
|
-
if (dir) formData.append("dir", dir);
|
|
3675
|
-
try {
|
|
3676
|
-
const res = await api("/api/desktop/upload", { method: "POST", body: formData });
|
|
3677
|
-
if (!res.ok) {
|
|
3678
|
-
const body = await res.text().catch(() => "");
|
|
3679
|
-
console.error(`[upload] failed for ${file.name}: HTTP ${res.status}`, body);
|
|
3680
|
-
return "";
|
|
3681
|
-
}
|
|
3682
|
-
const json = await res.json().catch(() => null);
|
|
3683
|
-
if (json?.ok && json.files?.[0]?.path) return json.files[0].path;
|
|
3684
|
-
return "";
|
|
3685
|
-
} catch (err) {
|
|
3686
|
-
console.error(`[upload] error for ${file.name}:`, err);
|
|
3687
|
-
return "";
|
|
3688
|
-
}
|
|
3689
|
-
}
|
|
3690
3754
|
async function createFolder(name) {
|
|
3691
3755
|
await createFolderAt(state.currentDir, name);
|
|
3692
3756
|
}
|
|
@@ -3728,6 +3792,7 @@ var init_file_manager = __esm({
|
|
|
3728
3792
|
init_context_menu();
|
|
3729
3793
|
init_slide_viewer();
|
|
3730
3794
|
init_uni_editor();
|
|
3795
|
+
init_desktop_upload();
|
|
3731
3796
|
OFFICE_BASE_CSS = `
|
|
3732
3797
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3733
3798
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1e293b; background: #f8fafc; }
|
|
@@ -3816,6 +3881,482 @@ function buildPreview(style, layout) {
|
|
|
3816
3881
|
html += `</div>`;
|
|
3817
3882
|
return html;
|
|
3818
3883
|
}
|
|
3884
|
+
function fieldId(winId, key) {
|
|
3885
|
+
return `settings_${winId}_${key}`;
|
|
3886
|
+
}
|
|
3887
|
+
function fieldEl(winId, key) {
|
|
3888
|
+
return document.getElementById(fieldId(winId, key));
|
|
3889
|
+
}
|
|
3890
|
+
function renderOptionTags(options, selectedValue, emptyLabel) {
|
|
3891
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
3892
|
+
return `<option value="">${esc(emptyLabel)}</option>`;
|
|
3893
|
+
}
|
|
3894
|
+
return options.map((option) => `<option value="${esc(option.id)}"${option.id === selectedValue ? " selected" : ""}>${esc(option.label || option.id)}</option>`).join("");
|
|
3895
|
+
}
|
|
3896
|
+
function filteredAvatarModels(models, source) {
|
|
3897
|
+
return (models || []).filter((model) => model.source === source);
|
|
3898
|
+
}
|
|
3899
|
+
function selectedAvatarModelId(models, preferredId) {
|
|
3900
|
+
return models.find((model) => model.id === preferredId)?.id || models[0]?.id || "";
|
|
3901
|
+
}
|
|
3902
|
+
function renderVuiSettingsMarkup(winId, settings, meta) {
|
|
3903
|
+
const vui = settings || {};
|
|
3904
|
+
const tts = vui.tts || {};
|
|
3905
|
+
const avatar = vui.avatar || {};
|
|
3906
|
+
const vrmSource = avatar.vrm?.source === "custom" ? "custom" : "desktop";
|
|
3907
|
+
const vrmaSource = avatar.vrm?.animationSource === "custom" ? "custom" : "desktop";
|
|
3908
|
+
const live2dSource = avatar.live2d?.source === "custom" ? "custom" : "desktop";
|
|
3909
|
+
const vrmModels = filteredAvatarModels(meta.avatar?.vrm?.models, vrmSource);
|
|
3910
|
+
const vrmaModels = filteredAvatarModels(meta.avatar?.vrm?.animations, vrmaSource);
|
|
3911
|
+
const live2dModels = filteredAvatarModels(meta.avatar?.live2d?.models, live2dSource);
|
|
3912
|
+
const vrmSelected = selectedAvatarModelId(vrmModels, avatar.vrm?.selectedId || meta.avatar?.vrm?.current?.optionId || "");
|
|
3913
|
+
const vrmaSelected = selectedAvatarModelId(vrmaModels, avatar.vrm?.selectedAnimationId || meta.avatar?.vrm?.currentAnimation?.optionId || "");
|
|
3914
|
+
const live2dSelected = selectedAvatarModelId(live2dModels, avatar.live2d?.selectedId || meta.avatar?.live2d?.current?.optionId || "");
|
|
3915
|
+
const provider = tts.provider || "browser";
|
|
3916
|
+
const providerOptions = [
|
|
3917
|
+
{ id: "browser", label: "Browser" },
|
|
3918
|
+
{ id: "openai", label: meta.presets?.openai?.label || "OpenAI" },
|
|
3919
|
+
{ id: "google", label: meta.presets?.google?.label || "Google" },
|
|
3920
|
+
{ id: "elevenlabs", label: meta.presets?.elevenlabs?.label || "ElevenLabs" }
|
|
3921
|
+
];
|
|
3922
|
+
const modelOptions = meta.presets?.[provider]?.models || [];
|
|
3923
|
+
const outputFormatOptions = meta.presets?.elevenlabs?.outputFormats || [];
|
|
3924
|
+
const outputFormat = tts.outputFormat || meta.presets?.elevenlabs?.defaults?.outputFormat || "mp3_44100_128";
|
|
3925
|
+
const elevenDefaults = meta.presets?.elevenlabs?.defaults || {};
|
|
3926
|
+
const hasPersonalElevenLabsApiKey = tts.hasPersonalElevenLabsApiKey === true;
|
|
3927
|
+
return [
|
|
3928
|
+
`<div class="uid-vui-stack">`,
|
|
3929
|
+
`<div class="uid-vui-note">Voice, language, and avatar defaults for /vui. Put personal VRM files under <code>~/.maria/desktop/vrm</code>, VRMA motion files under <code>~/.maria/desktop/vrma</code>, and Live2D models under <code>~/.maria/desktop/live2d</code>.</div>`,
|
|
3930
|
+
`<div class="uid-vui-grid">`,
|
|
3931
|
+
`<label class="uid-vui-field"><span>Recognition language</span><select id="${fieldId(winId, "vui-language")}">${renderOptionTags(meta.languages || [], vui.language || "ja-JP", "(no languages)")}</select></label>`,
|
|
3932
|
+
`<label class="uid-vui-field"><span>Default avatar mode</span><select id="${fieldId(winId, "avatar-default-mode")}">${renderOptionTags(meta.avatarModes || [], avatar.defaultMode || meta.avatar?.defaultMode || "orb", "(no modes)")}</select></label>`,
|
|
3933
|
+
`</div>`,
|
|
3934
|
+
`<div class="uid-vui-card">`,
|
|
3935
|
+
`<div class="uid-vui-card-title">Voice Reply</div>`,
|
|
3936
|
+
`<div class="uid-vui-grid">`,
|
|
3937
|
+
`<label class="uid-vui-toggle"><input id="${fieldId(winId, "tts-enabled")}" type="checkbox"${tts.enabled !== false ? " checked" : ""}><span>Enabled</span></label>`,
|
|
3938
|
+
`<label class="uid-vui-field"><span>Provider</span><select id="${fieldId(winId, "tts-provider")}">${renderOptionTags(providerOptions, provider, "(no providers)")}</select></label>`,
|
|
3939
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "tts-model-wrap")}"><span>Model</span><select id="${fieldId(winId, "tts-model")}">${renderOptionTags(modelOptions, tts.model || "", "(no models)")}</select></label>`,
|
|
3940
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "tts-language-wrap")}"><span>TTS language</span><select id="${fieldId(winId, "tts-language-code")}">${renderOptionTags(meta.languages || [], tts.languageCode || vui.language || "ja-JP", "(no languages)")}</select></label>`,
|
|
3941
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "tts-voice-select-wrap")}"><span>Voice</span><select id="${fieldId(winId, "tts-voice-select")}"><option value="">Loading...</option></select></label>`,
|
|
3942
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "tts-voice-custom-wrap")}"><span>Custom voice ID or share URL</span><input id="${fieldId(winId, "tts-voice-custom")}" type="text" value="" placeholder="Optional override for shared/private voices"></label>`,
|
|
3943
|
+
`<label class="uid-vui-field uid-vui-span"><span>Instructions</span><input id="${fieldId(winId, "tts-instructions")}" type="text" value="${esc(tts.instructions || "")}" placeholder="Optional speaking style instructions"></label>`,
|
|
3944
|
+
`</div>`,
|
|
3945
|
+
`<div id="${fieldId(winId, "elevenlabs-panel")}" class="uid-vui-grid uid-vui-subgrid">`,
|
|
3946
|
+
`<label class="uid-vui-field uid-vui-span"><span>Personal ElevenLabs API key (optional)</span><input id="${fieldId(winId, "tts-eleven-api-key")}" type="password" value="" autocomplete="off" placeholder="${esc(hasPersonalElevenLabsApiKey ? "Stored locally on this device. Enter a new key to replace it." : "Set your own key to use private/shared voices in your account.")}"></label>`,
|
|
3947
|
+
`<div class="uid-vui-hint uid-vui-span" id="${fieldId(winId, "tts-eleven-api-key-status")}">${esc(hasPersonalElevenLabsApiKey ? "A personal ElevenLabs API key is already stored locally on this device." : "Optional. Use this when a private or shared voice is not available from the shared MARIA server key.")}</div>`,
|
|
3948
|
+
`<label class="uid-vui-field"><span>Output format</span><select id="${fieldId(winId, "tts-eleven-output")}">${renderOptionTags(outputFormatOptions, outputFormat, "(no formats)")}</select></label>`,
|
|
3949
|
+
`<label class="uid-vui-field"><span>Stability</span><input id="${fieldId(winId, "tts-eleven-stability")}" type="number" min="0" max="1" step="0.01" value="${String(tts.elevenLabsSettings?.stability ?? elevenDefaults.stability ?? 0.5)}"></label>`,
|
|
3950
|
+
`<label class="uid-vui-field"><span>Similarity</span><input id="${fieldId(winId, "tts-eleven-similarity")}" type="number" min="0" max="1" step="0.01" value="${String(tts.elevenLabsSettings?.similarityBoost ?? elevenDefaults.similarityBoost ?? 0.75)}"></label>`,
|
|
3951
|
+
`<label class="uid-vui-field"><span>Style</span><input id="${fieldId(winId, "tts-eleven-style")}" type="number" min="0" max="1" step="0.01" value="${String(tts.elevenLabsSettings?.style ?? elevenDefaults.style ?? 0)}"></label>`,
|
|
3952
|
+
`<label class="uid-vui-field"><span>Speed</span><input id="${fieldId(winId, "tts-eleven-speed")}" type="number" min="0.7" max="1.2" step="0.01" value="${String(tts.elevenLabsSettings?.speed ?? elevenDefaults.speed ?? 1)}"></label>`,
|
|
3953
|
+
`<label class="uid-vui-toggle"><input id="${fieldId(winId, "tts-eleven-boost")}" type="checkbox"${tts.elevenLabsSettings?.useSpeakerBoost !== false ? " checked" : ""}><span>Speaker boost</span></label>`,
|
|
3954
|
+
`</div>`,
|
|
3955
|
+
`</div>`,
|
|
3956
|
+
`<div class="uid-vui-card">`,
|
|
3957
|
+
`<div class="uid-vui-card-title">VRM</div>`,
|
|
3958
|
+
`<div class="uid-vui-grid">`,
|
|
3959
|
+
`<label class="uid-vui-field"><span>Source</span><select id="${fieldId(winId, "vrm-source")}"><option value="desktop"${vrmSource === "desktop" ? " selected" : ""}>MARIA OS library</option><option value="custom"${vrmSource === "custom" ? " selected" : ""}>Custom path</option></select></label>`,
|
|
3960
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "vrm-select-wrap")}"><span>Model</span><select id="${fieldId(winId, "vrm-select")}">${renderOptionTags(vrmModels, vrmSelected, "(no VRM models)")}</select></label>`,
|
|
3961
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "vrm-custom-wrap")}"><span>Custom VRM path</span><input id="${fieldId(winId, "vrm-custom-path")}" type="text" value="${esc(avatar.vrm?.customPath || meta.avatar?.vrm?.current?.customPath || "")}" placeholder="/absolute/path/model.vrm"></label>`,
|
|
3962
|
+
`<label class="uid-vui-field"><span>Scale</span><input id="${fieldId(winId, "vrm-scale")}" type="number" min="0.5" max="3" step="0.05" value="${String(avatar.vrm?.scale ?? meta.avatar?.vrm?.scale ?? 1.5)}"></label>`,
|
|
3963
|
+
`<div class="uid-vui-actions uid-vui-span"><button type="button" class="uid-vui-btn" id="${fieldId(winId, "vrm-upload")}">Upload VRM</button><span class="uid-vui-hint">Copies selected .vrm files into <code>~/.maria/desktop/vrm</code>.</span></div>`,
|
|
3964
|
+
`<label class="uid-vui-field"><span>Idle / Motion source</span><select id="${fieldId(winId, "vrma-source")}"><option value="desktop"${vrmaSource === "desktop" ? " selected" : ""}>MARIA OS library</option><option value="custom"${vrmaSource === "custom" ? " selected" : ""}>Custom path</option></select></label>`,
|
|
3965
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "vrma-select-wrap")}"><span>VRMA motion</span><select id="${fieldId(winId, "vrma-select")}">${renderOptionTags(vrmaModels, vrmaSelected, "(procedural fallback)")}</select></label>`,
|
|
3966
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "vrma-custom-wrap")}"><span>Custom VRMA path</span><input id="${fieldId(winId, "vrma-custom-path")}" type="text" value="${esc(avatar.vrm?.customAnimationPath || meta.avatar?.vrm?.currentAnimation?.customPath || "")}" placeholder="/absolute/path/idle.vrma"></label>`,
|
|
3967
|
+
`<div class="uid-vui-actions uid-vui-span"><button type="button" class="uid-vui-btn" id="${fieldId(winId, "vrma-upload")}">Upload VRMA</button><span class="uid-vui-hint">Optional. When absent, MARIA OS falls back to procedural idle motion.</span></div>`,
|
|
3968
|
+
`</div>`,
|
|
3969
|
+
`</div>`,
|
|
3970
|
+
`<div class="uid-vui-card">`,
|
|
3971
|
+
`<div class="uid-vui-card-title">Live2D</div>`,
|
|
3972
|
+
`<div class="uid-vui-grid">`,
|
|
3973
|
+
`<label class="uid-vui-field"><span>Source</span><select id="${fieldId(winId, "live2d-source")}"><option value="desktop"${live2dSource === "desktop" ? " selected" : ""}>MARIA OS library</option><option value="custom"${live2dSource === "custom" ? " selected" : ""}>Custom directory</option></select></label>`,
|
|
3974
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "live2d-select-wrap")}"><span>Model</span><select id="${fieldId(winId, "live2d-select")}">${renderOptionTags(live2dModels, live2dSelected, "(no Live2D models)")}</select></label>`,
|
|
3975
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "live2d-custom-dir-wrap")}"><span>Custom model dir/file</span><input id="${fieldId(winId, "live2d-custom-dir")}" type="text" value="${esc(avatar.live2d?.customModelDir || meta.avatar?.live2d?.current?.customModelDir || "")}" placeholder="/absolute/path/modeldir or *.model3.json"></label>`,
|
|
3976
|
+
`<label class="uid-vui-field" id="${fieldId(winId, "live2d-custom-name-wrap")}"><span>Custom model name</span><input id="${fieldId(winId, "live2d-custom-name")}" type="text" value="${esc(avatar.live2d?.customModelName || meta.avatar?.live2d?.current?.customModelName || "")}" placeholder="Optional model name"></label>`,
|
|
3977
|
+
`<label class="uid-vui-field"><span>Scale</span><input id="${fieldId(winId, "live2d-scale")}" type="number" min="0.5" max="3" step="0.05" value="${String(avatar.live2d?.scale ?? meta.avatar?.live2d?.scale ?? 2.1)}"></label>`,
|
|
3978
|
+
`<label class="uid-vui-field"><span>Offset X</span><input id="${fieldId(winId, "live2d-offset-x")}" type="number" min="-4" max="4" step="0.05" value="${String(avatar.live2d?.offsetX ?? meta.avatar?.live2d?.offsetX ?? 0)}"></label>`,
|
|
3979
|
+
`<label class="uid-vui-field"><span>Offset Y</span><input id="${fieldId(winId, "live2d-offset-y")}" type="number" min="-4" max="4" step="0.05" value="${String(avatar.live2d?.offsetY ?? meta.avatar?.live2d?.offsetY ?? 1.1)}"></label>`,
|
|
3980
|
+
`<div class="uid-vui-actions uid-vui-span"><button type="button" class="uid-vui-btn" id="${fieldId(winId, "live2d-upload")}">Upload Live2D Folder</button><span class="uid-vui-hint">Preserves subdirectories under <code>~/.maria/desktop/live2d</code>.</span></div>`,
|
|
3981
|
+
`</div>`,
|
|
3982
|
+
`</div>`,
|
|
3983
|
+
`<div id="${fieldId(winId, "vui-status")}" class="uid-vui-status">Changes save automatically.</div>`,
|
|
3984
|
+
`</div>`
|
|
3985
|
+
].join("");
|
|
3986
|
+
}
|
|
3987
|
+
function toFiniteNumber(raw, fallback) {
|
|
3988
|
+
const value = Number(raw);
|
|
3989
|
+
return Number.isFinite(value) ? value : fallback;
|
|
3990
|
+
}
|
|
3991
|
+
function notifyVuiSettingsChanged() {
|
|
3992
|
+
const detail = { updatedAt: Date.now() };
|
|
3993
|
+
try {
|
|
3994
|
+
localStorage.setItem("maria.desktop.vui.settings.updatedAt", String(detail.updatedAt));
|
|
3995
|
+
} catch {
|
|
3996
|
+
}
|
|
3997
|
+
try {
|
|
3998
|
+
const channel = new BroadcastChannel("maria.desktop.vui.settings");
|
|
3999
|
+
channel.postMessage(detail);
|
|
4000
|
+
channel.close();
|
|
4001
|
+
} catch {
|
|
4002
|
+
}
|
|
4003
|
+
try {
|
|
4004
|
+
window.dispatchEvent(new CustomEvent("maria:vui-settings-updated", { detail }));
|
|
4005
|
+
} catch {
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
function pickLocalFiles(options) {
|
|
4009
|
+
return new Promise((resolve) => {
|
|
4010
|
+
const input = document.createElement("input");
|
|
4011
|
+
input.type = "file";
|
|
4012
|
+
input.style.display = "none";
|
|
4013
|
+
input.multiple = options?.multiple !== false;
|
|
4014
|
+
if (options?.accept) input.accept = options.accept;
|
|
4015
|
+
if (options?.directory) {
|
|
4016
|
+
input.setAttribute("webkitdirectory", "");
|
|
4017
|
+
input.setAttribute("directory", "");
|
|
4018
|
+
}
|
|
4019
|
+
document.body.appendChild(input);
|
|
4020
|
+
input.addEventListener("change", () => {
|
|
4021
|
+
const files = Array.from(input.files || []);
|
|
4022
|
+
input.remove();
|
|
4023
|
+
resolve(files);
|
|
4024
|
+
}, { once: true });
|
|
4025
|
+
input.click();
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
async function loadVoiceOptionsForSettings(winId, meta, selectedVoice) {
|
|
4029
|
+
const provider = fieldEl(winId, "tts-provider")?.value || "browser";
|
|
4030
|
+
const model = fieldEl(winId, "tts-model")?.value || "";
|
|
4031
|
+
const languageCode = fieldEl(winId, "tts-language-code")?.value || "";
|
|
4032
|
+
const voiceSelectWrap = fieldEl(winId, "tts-voice-select-wrap");
|
|
4033
|
+
const voiceCustomWrap = fieldEl(winId, "tts-voice-custom-wrap");
|
|
4034
|
+
const voiceSelect = fieldEl(winId, "tts-voice-select");
|
|
4035
|
+
const voiceCustom = fieldEl(winId, "tts-voice-custom");
|
|
4036
|
+
if (!voiceSelectWrap || !voiceCustomWrap || !voiceSelect || !voiceCustom) return;
|
|
4037
|
+
if (provider === "browser") {
|
|
4038
|
+
voiceSelectWrap.style.display = "none";
|
|
4039
|
+
voiceCustomWrap.style.display = "none";
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
const presetVoices = meta.presets?.[provider]?.voices || [];
|
|
4043
|
+
let voices = presetVoices;
|
|
4044
|
+
try {
|
|
4045
|
+
const params = new URLSearchParams();
|
|
4046
|
+
params.set("provider", provider);
|
|
4047
|
+
if (model) params.set("model", model);
|
|
4048
|
+
if (languageCode) params.set("languageCode", languageCode);
|
|
4049
|
+
const result = await apiJson(`/api/vui/tts/voices?${params.toString()}`);
|
|
4050
|
+
if (Array.isArray(result.voices) && result.voices.length > 0) {
|
|
4051
|
+
voices = result.voices.map((voice) => ({
|
|
4052
|
+
id: voice.id,
|
|
4053
|
+
label: voice.label || voice.name || voice.id
|
|
4054
|
+
}));
|
|
4055
|
+
}
|
|
4056
|
+
} catch {
|
|
4057
|
+
}
|
|
4058
|
+
if (!voices.length) {
|
|
4059
|
+
voiceSelectWrap.style.display = "none";
|
|
4060
|
+
voiceCustomWrap.style.display = "grid";
|
|
4061
|
+
if (selectedVoice) voiceCustom.value = selectedVoice;
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
voiceSelect.innerHTML = renderOptionTags(voices, selectedVoice || voiceCustom.value || "", "(no voices)");
|
|
4065
|
+
voiceSelectWrap.style.display = "grid";
|
|
4066
|
+
voiceCustomWrap.style.display = "grid";
|
|
4067
|
+
if (selectedVoice && voices.some((voice) => voice.id === selectedVoice)) {
|
|
4068
|
+
voiceCustom.value = "";
|
|
4069
|
+
} else if (selectedVoice && !voices.some((voice) => voice.id === selectedVoice)) {
|
|
4070
|
+
voiceCustom.value = selectedVoice;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
function syncVuiSettingsVisibility(winId, meta) {
|
|
4074
|
+
const provider = fieldEl(winId, "tts-provider")?.value || "browser";
|
|
4075
|
+
const isBrowser = provider === "browser";
|
|
4076
|
+
const isGoogle = provider === "google";
|
|
4077
|
+
const isElevenLabs = provider === "elevenlabs";
|
|
4078
|
+
const ttsModelWrap = fieldEl(winId, "tts-model-wrap");
|
|
4079
|
+
const ttsLanguageWrap = fieldEl(winId, "tts-language-wrap");
|
|
4080
|
+
const ttsVoiceSelectWrap = fieldEl(winId, "tts-voice-select-wrap");
|
|
4081
|
+
const ttsVoiceCustomWrap = fieldEl(winId, "tts-voice-custom-wrap");
|
|
4082
|
+
const elevenPanel = fieldEl(winId, "elevenlabs-panel");
|
|
4083
|
+
if (ttsModelWrap) ttsModelWrap.style.display = isBrowser ? "none" : "grid";
|
|
4084
|
+
if (ttsLanguageWrap) ttsLanguageWrap.style.display = isGoogle ? "grid" : "none";
|
|
4085
|
+
if (ttsVoiceSelectWrap) ttsVoiceSelectWrap.style.display = isBrowser ? "none" : "grid";
|
|
4086
|
+
if (ttsVoiceCustomWrap) ttsVoiceCustomWrap.style.display = isBrowser ? "none" : "grid";
|
|
4087
|
+
if (elevenPanel) elevenPanel.style.display = isElevenLabs ? "grid" : "none";
|
|
4088
|
+
const modelOptions = meta.presets?.[provider]?.models || [];
|
|
4089
|
+
const modelSelect = fieldEl(winId, "tts-model");
|
|
4090
|
+
if (modelSelect) {
|
|
4091
|
+
const currentModel = modelSelect.value;
|
|
4092
|
+
modelSelect.innerHTML = renderOptionTags(modelOptions, currentModel, "(no models)");
|
|
4093
|
+
}
|
|
4094
|
+
const vrmSource = fieldEl(winId, "vrm-source")?.value || "desktop";
|
|
4095
|
+
const vrmaSource = fieldEl(winId, "vrma-source")?.value || "desktop";
|
|
4096
|
+
const live2dSource = fieldEl(winId, "live2d-source")?.value || "desktop";
|
|
4097
|
+
const vrmSelectWrap = fieldEl(winId, "vrm-select-wrap");
|
|
4098
|
+
const vrmCustomWrap = fieldEl(winId, "vrm-custom-wrap");
|
|
4099
|
+
const vrmaSelectWrap = fieldEl(winId, "vrma-select-wrap");
|
|
4100
|
+
const vrmaCustomWrap = fieldEl(winId, "vrma-custom-wrap");
|
|
4101
|
+
const live2dSelectWrap = fieldEl(winId, "live2d-select-wrap");
|
|
4102
|
+
const live2dCustomDirWrap = fieldEl(winId, "live2d-custom-dir-wrap");
|
|
4103
|
+
const live2dCustomNameWrap = fieldEl(winId, "live2d-custom-name-wrap");
|
|
4104
|
+
if (vrmSelectWrap) vrmSelectWrap.style.display = vrmSource === "custom" ? "none" : "grid";
|
|
4105
|
+
if (vrmCustomWrap) vrmCustomWrap.style.display = vrmSource === "custom" ? "grid" : "none";
|
|
4106
|
+
if (vrmaSelectWrap) vrmaSelectWrap.style.display = vrmaSource === "custom" ? "none" : "grid";
|
|
4107
|
+
if (vrmaCustomWrap) vrmaCustomWrap.style.display = vrmaSource === "custom" ? "grid" : "none";
|
|
4108
|
+
if (live2dSelectWrap) live2dSelectWrap.style.display = live2dSource === "custom" ? "none" : "grid";
|
|
4109
|
+
if (live2dCustomDirWrap) live2dCustomDirWrap.style.display = live2dSource === "custom" ? "grid" : "none";
|
|
4110
|
+
if (live2dCustomNameWrap) live2dCustomNameWrap.style.display = live2dSource === "custom" ? "grid" : "none";
|
|
4111
|
+
const vrmSelect = fieldEl(winId, "vrm-select");
|
|
4112
|
+
if (vrmSelect && vrmSource !== "custom") {
|
|
4113
|
+
const models = filteredAvatarModels(meta.avatar?.vrm?.models, vrmSource);
|
|
4114
|
+
vrmSelect.innerHTML = renderOptionTags(models, vrmSelect.value, "(no VRM models)");
|
|
4115
|
+
}
|
|
4116
|
+
const vrmaSelect = fieldEl(winId, "vrma-select");
|
|
4117
|
+
if (vrmaSelect && vrmaSource !== "custom") {
|
|
4118
|
+
const models = filteredAvatarModels(meta.avatar?.vrm?.animations, vrmaSource);
|
|
4119
|
+
vrmaSelect.innerHTML = renderOptionTags(models, vrmaSelect.value, "(procedural fallback)");
|
|
4120
|
+
}
|
|
4121
|
+
const live2dSelect = fieldEl(winId, "live2d-select");
|
|
4122
|
+
if (live2dSelect && live2dSource !== "custom") {
|
|
4123
|
+
const models = filteredAvatarModels(meta.avatar?.live2d?.models, live2dSource);
|
|
4124
|
+
live2dSelect.innerHTML = renderOptionTags(models, live2dSelect.value, "(no Live2D models)");
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
async function hydrateVuiSettingsSection(winId) {
|
|
4128
|
+
const mount = document.getElementById(fieldId(winId, "vui-root"));
|
|
4129
|
+
if (!mount) return;
|
|
4130
|
+
mount.innerHTML = `<div class="uid-vui-note">Loading VUI settings...</div>`;
|
|
4131
|
+
try {
|
|
4132
|
+
const [settingsRes, metaRes] = await Promise.all([
|
|
4133
|
+
apiJson("/api/vui/settings"),
|
|
4134
|
+
apiJson("/api/vui/settings/meta")
|
|
4135
|
+
]);
|
|
4136
|
+
mount.innerHTML = renderVuiSettingsMarkup(winId, settingsRes.vui, metaRes);
|
|
4137
|
+
const providerSelect = fieldEl(winId, "tts-provider");
|
|
4138
|
+
const modelSelect = fieldEl(winId, "tts-model");
|
|
4139
|
+
const languageSelect = fieldEl(winId, "tts-language-code");
|
|
4140
|
+
const vrmSourceSelect = fieldEl(winId, "vrm-source");
|
|
4141
|
+
const live2dSourceSelect = fieldEl(winId, "live2d-source");
|
|
4142
|
+
const statusEl = fieldEl(winId, "vui-status");
|
|
4143
|
+
let saveTimer = 0;
|
|
4144
|
+
let saveSerial = 0;
|
|
4145
|
+
const selectedVoiceValue = () => {
|
|
4146
|
+
const voiceSelect = fieldEl(winId, "tts-voice-select");
|
|
4147
|
+
const voiceCustom = fieldEl(winId, "tts-voice-custom");
|
|
4148
|
+
return (voiceCustom?.value || "").trim() || (voiceSelect?.value || "");
|
|
4149
|
+
};
|
|
4150
|
+
const setStatus = (text, state2 = "idle") => {
|
|
4151
|
+
if (!statusEl) return;
|
|
4152
|
+
statusEl.textContent = text;
|
|
4153
|
+
statusEl.dataset.state = state2;
|
|
4154
|
+
};
|
|
4155
|
+
const setElevenLabsApiKeyStatus = (stored) => {
|
|
4156
|
+
const input = fieldEl(winId, "tts-eleven-api-key");
|
|
4157
|
+
const status = fieldEl(winId, "tts-eleven-api-key-status");
|
|
4158
|
+
if (input) {
|
|
4159
|
+
input.value = "";
|
|
4160
|
+
input.placeholder = stored ? "Stored locally on this device. Enter a new key to replace it." : "Set your own key to use private/shared voices in your account.";
|
|
4161
|
+
}
|
|
4162
|
+
if (status) {
|
|
4163
|
+
status.textContent = stored ? "A personal ElevenLabs API key is stored locally on this device." : "Optional. Use this when a private or shared voice is not available from the shared MARIA server key.";
|
|
4164
|
+
}
|
|
4165
|
+
};
|
|
4166
|
+
const sync = async (selectedVoice) => {
|
|
4167
|
+
syncVuiSettingsVisibility(winId, metaRes);
|
|
4168
|
+
await loadVoiceOptionsForSettings(winId, metaRes, selectedVoice || settingsRes.vui?.tts?.voice || "");
|
|
4169
|
+
};
|
|
4170
|
+
const saveNow = async () => {
|
|
4171
|
+
const serial = ++saveSerial;
|
|
4172
|
+
setStatus("Saving\u2026", "saving");
|
|
4173
|
+
const voiceSelect = fieldEl(winId, "tts-voice-select");
|
|
4174
|
+
const voiceCustom = fieldEl(winId, "tts-voice-custom");
|
|
4175
|
+
const personalElevenLabsApiKeyRaw = (fieldEl(winId, "tts-eleven-api-key")?.value || "").trim();
|
|
4176
|
+
const customVoice = (voiceCustom?.value || "").trim();
|
|
4177
|
+
const selectedVoice = customVoice || (voiceSelect?.value || "");
|
|
4178
|
+
const providerValue = providerSelect?.value || "browser";
|
|
4179
|
+
const personalElevenLabsApiKey = providerValue === "elevenlabs" ? personalElevenLabsApiKeyRaw : "";
|
|
4180
|
+
const payload = {
|
|
4181
|
+
vui: {
|
|
4182
|
+
language: fieldEl(winId, "vui-language")?.value || "ja-JP",
|
|
4183
|
+
tts: {
|
|
4184
|
+
enabled: Boolean(fieldEl(winId, "tts-enabled")?.checked),
|
|
4185
|
+
provider: providerValue,
|
|
4186
|
+
voice: selectedVoice,
|
|
4187
|
+
model: modelSelect?.value || "",
|
|
4188
|
+
languageCode: languageSelect?.value || "",
|
|
4189
|
+
instructions: fieldEl(winId, "tts-instructions")?.value || "",
|
|
4190
|
+
outputFormat: fieldEl(winId, "tts-eleven-output")?.value || "",
|
|
4191
|
+
...personalElevenLabsApiKey ? { personalElevenLabsApiKey } : {},
|
|
4192
|
+
elevenLabsSettings: {
|
|
4193
|
+
stability: toFiniteNumber(fieldEl(winId, "tts-eleven-stability")?.value || "", 0.5),
|
|
4194
|
+
similarityBoost: toFiniteNumber(fieldEl(winId, "tts-eleven-similarity")?.value || "", 0.75),
|
|
4195
|
+
style: toFiniteNumber(fieldEl(winId, "tts-eleven-style")?.value || "", 0),
|
|
4196
|
+
speed: toFiniteNumber(fieldEl(winId, "tts-eleven-speed")?.value || "", 1),
|
|
4197
|
+
useSpeakerBoost: Boolean(fieldEl(winId, "tts-eleven-boost")?.checked)
|
|
4198
|
+
}
|
|
4199
|
+
},
|
|
4200
|
+
avatar: {
|
|
4201
|
+
defaultMode: fieldEl(winId, "avatar-default-mode")?.value || "orb",
|
|
4202
|
+
vrm: {
|
|
4203
|
+
source: vrmSourceSelect?.value || "desktop",
|
|
4204
|
+
selectedId: fieldEl(winId, "vrm-select")?.value || "",
|
|
4205
|
+
customPath: fieldEl(winId, "vrm-custom-path")?.value || "",
|
|
4206
|
+
scale: toFiniteNumber(fieldEl(winId, "vrm-scale")?.value || "", 1.5),
|
|
4207
|
+
animationSource: fieldEl(winId, "vrma-source")?.value || "desktop",
|
|
4208
|
+
selectedAnimationId: fieldEl(winId, "vrma-select")?.value || "",
|
|
4209
|
+
customAnimationPath: fieldEl(winId, "vrma-custom-path")?.value || ""
|
|
4210
|
+
},
|
|
4211
|
+
live2d: {
|
|
4212
|
+
source: live2dSourceSelect?.value || "desktop",
|
|
4213
|
+
selectedId: fieldEl(winId, "live2d-select")?.value || "",
|
|
4214
|
+
customModelDir: fieldEl(winId, "live2d-custom-dir")?.value || "",
|
|
4215
|
+
customModelName: fieldEl(winId, "live2d-custom-name")?.value || "",
|
|
4216
|
+
scale: toFiniteNumber(fieldEl(winId, "live2d-scale")?.value || "", 2.1),
|
|
4217
|
+
offsetX: toFiniteNumber(fieldEl(winId, "live2d-offset-x")?.value || "", 0),
|
|
4218
|
+
offsetY: toFiniteNumber(fieldEl(winId, "live2d-offset-y")?.value || "", 1.1)
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
try {
|
|
4224
|
+
await apiJson("/api/vui/settings", {
|
|
4225
|
+
method: "POST",
|
|
4226
|
+
headers: { "content-type": "application/json" },
|
|
4227
|
+
body: JSON.stringify(payload)
|
|
4228
|
+
});
|
|
4229
|
+
if (personalElevenLabsApiKey) {
|
|
4230
|
+
setElevenLabsApiKeyStatus(true);
|
|
4231
|
+
await loadVoiceOptionsForSettings(winId, metaRes, selectedVoice);
|
|
4232
|
+
}
|
|
4233
|
+
notifyVuiSettingsChanged();
|
|
4234
|
+
if (serial === saveSerial) setStatus("Saved.", "saved");
|
|
4235
|
+
} catch (error) {
|
|
4236
|
+
if (serial === saveSerial) {
|
|
4237
|
+
setStatus(`Save failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
};
|
|
4241
|
+
const queueSave = (delayMs = 180) => {
|
|
4242
|
+
if (saveTimer) window.clearTimeout(saveTimer);
|
|
4243
|
+
saveTimer = window.setTimeout(() => {
|
|
4244
|
+
void saveNow();
|
|
4245
|
+
}, delayMs);
|
|
4246
|
+
};
|
|
4247
|
+
providerSelect?.addEventListener("change", () => {
|
|
4248
|
+
const selectedVoice = selectedVoiceValue();
|
|
4249
|
+
void sync(selectedVoice).then(() => queueSave(0));
|
|
4250
|
+
});
|
|
4251
|
+
modelSelect?.addEventListener("change", () => {
|
|
4252
|
+
const selectedVoice = selectedVoiceValue();
|
|
4253
|
+
void loadVoiceOptionsForSettings(winId, metaRes, selectedVoice).then(() => queueSave(0));
|
|
4254
|
+
});
|
|
4255
|
+
languageSelect?.addEventListener("change", () => {
|
|
4256
|
+
const selectedVoice = selectedVoiceValue();
|
|
4257
|
+
void loadVoiceOptionsForSettings(winId, metaRes, selectedVoice).then(() => queueSave(0));
|
|
4258
|
+
});
|
|
4259
|
+
fieldEl(winId, "tts-voice-select")?.addEventListener("change", () => {
|
|
4260
|
+
const custom = fieldEl(winId, "tts-voice-custom");
|
|
4261
|
+
if (custom) custom.value = "";
|
|
4262
|
+
queueSave(0);
|
|
4263
|
+
});
|
|
4264
|
+
vrmSourceSelect?.addEventListener("change", () => {
|
|
4265
|
+
syncVuiSettingsVisibility(winId, metaRes);
|
|
4266
|
+
queueSave(0);
|
|
4267
|
+
});
|
|
4268
|
+
fieldEl(winId, "vrma-source")?.addEventListener("change", () => {
|
|
4269
|
+
syncVuiSettingsVisibility(winId, metaRes);
|
|
4270
|
+
queueSave(0);
|
|
4271
|
+
});
|
|
4272
|
+
live2dSourceSelect?.addEventListener("change", () => {
|
|
4273
|
+
syncVuiSettingsVisibility(winId, metaRes);
|
|
4274
|
+
queueSave(0);
|
|
4275
|
+
});
|
|
4276
|
+
[
|
|
4277
|
+
"vui-language",
|
|
4278
|
+
"avatar-default-mode",
|
|
4279
|
+
"tts-enabled",
|
|
4280
|
+
"tts-eleven-output",
|
|
4281
|
+
"tts-eleven-boost",
|
|
4282
|
+
"vrm-select",
|
|
4283
|
+
"vrma-select",
|
|
4284
|
+
"live2d-select"
|
|
4285
|
+
].forEach((key) => fieldEl(winId, key)?.addEventListener("change", () => queueSave(0)));
|
|
4286
|
+
[
|
|
4287
|
+
"tts-voice-custom",
|
|
4288
|
+
"tts-instructions",
|
|
4289
|
+
"tts-eleven-api-key",
|
|
4290
|
+
"tts-eleven-stability",
|
|
4291
|
+
"tts-eleven-similarity",
|
|
4292
|
+
"tts-eleven-style",
|
|
4293
|
+
"tts-eleven-speed",
|
|
4294
|
+
"vrm-custom-path",
|
|
4295
|
+
"vrma-custom-path",
|
|
4296
|
+
"vrm-scale",
|
|
4297
|
+
"live2d-custom-dir",
|
|
4298
|
+
"live2d-custom-name",
|
|
4299
|
+
"live2d-scale",
|
|
4300
|
+
"live2d-offset-x",
|
|
4301
|
+
"live2d-offset-y"
|
|
4302
|
+
].forEach((key) => fieldEl(winId, key)?.addEventListener("input", () => queueSave(240)));
|
|
4303
|
+
fieldEl(winId, "vrm-upload")?.addEventListener("click", () => {
|
|
4304
|
+
void (async () => {
|
|
4305
|
+
const files = await pickLocalFiles({ accept: ".vrm", multiple: true });
|
|
4306
|
+
if (!files.length) return;
|
|
4307
|
+
setStatus("Uploading VRM files\u2026", "saving");
|
|
4308
|
+
const uploaded = [];
|
|
4309
|
+
for (const file of files) {
|
|
4310
|
+
const relPath = await uploadFileToDesktopAtWithPath("vrm", file);
|
|
4311
|
+
if (relPath) uploaded.push(relPath);
|
|
4312
|
+
}
|
|
4313
|
+
if (!uploaded.length) {
|
|
4314
|
+
setStatus("VRM upload failed.", "error");
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4317
|
+
await hydrateVuiSettingsSection(winId);
|
|
4318
|
+
})();
|
|
4319
|
+
});
|
|
4320
|
+
fieldEl(winId, "live2d-upload")?.addEventListener("click", () => {
|
|
4321
|
+
void (async () => {
|
|
4322
|
+
const files = await pickLocalFiles({ directory: true, multiple: true });
|
|
4323
|
+
if (!files.length) return;
|
|
4324
|
+
const hasRelativePath = files.some((file) => Boolean(file.webkitRelativePath && file.webkitRelativePath.includes("/")));
|
|
4325
|
+
if (!hasRelativePath) {
|
|
4326
|
+
setStatus("Folder upload is not supported in this browser.", "error");
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
setStatus("Uploading Live2D folder\u2026", "saving");
|
|
4330
|
+
const uploaded = await uploadFileListToDesktopAt("live2d", files, { preserveRelativePaths: true });
|
|
4331
|
+
if (!uploaded.length) {
|
|
4332
|
+
setStatus("Live2D folder upload failed.", "error");
|
|
4333
|
+
return;
|
|
4334
|
+
}
|
|
4335
|
+
await hydrateVuiSettingsSection(winId);
|
|
4336
|
+
})();
|
|
4337
|
+
});
|
|
4338
|
+
fieldEl(winId, "vrma-upload")?.addEventListener("click", () => {
|
|
4339
|
+
void (async () => {
|
|
4340
|
+
const files = await pickLocalFiles({ accept: ".vrma", multiple: true });
|
|
4341
|
+
if (!files.length) return;
|
|
4342
|
+
setStatus("Uploading VRMA files\u2026", "saving");
|
|
4343
|
+
const uploaded = [];
|
|
4344
|
+
for (const file of files) {
|
|
4345
|
+
const relPath = await uploadFileToDesktopAtWithPath("vrma", file);
|
|
4346
|
+
if (relPath) uploaded.push(relPath);
|
|
4347
|
+
}
|
|
4348
|
+
if (!uploaded.length) {
|
|
4349
|
+
setStatus("VRMA upload failed.", "error");
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4352
|
+
await hydrateVuiSettingsSection(winId);
|
|
4353
|
+
})();
|
|
4354
|
+
});
|
|
4355
|
+
await sync();
|
|
4356
|
+
} catch (error) {
|
|
4357
|
+
mount.innerHTML = `<div class="uid-vui-note" style="color:var(--danger);">Failed to load VUI settings: ${esc(error instanceof Error ? error.message : String(error))}</div>`;
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
3819
4360
|
function updateAllWindowIcons() {
|
|
3820
4361
|
const useLucide = state.iconStyle === "modern" || state.iconStyle === "minimal";
|
|
3821
4362
|
for (const win of state.windows) {
|
|
@@ -3896,18 +4437,28 @@ function updateAllWindowIcons() {
|
|
|
3896
4437
|
function openUiDesign() {
|
|
3897
4438
|
const useLucide = state.iconStyle === "modern" || state.iconStyle === "minimal";
|
|
3898
4439
|
const winIcon = useLucide ? lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide") : "\u{1F3A8}";
|
|
4440
|
+
const viewportWidth = Math.max(640, window.innerWidth || 1280);
|
|
4441
|
+
const viewportHeight = Math.max(520, window.innerHeight || 900);
|
|
4442
|
+
const width = Math.min(720, Math.max(520, viewportWidth - 96));
|
|
4443
|
+
const height = Math.min(760, Math.max(520, viewportHeight - 140));
|
|
3899
4444
|
const win = createWindow({
|
|
3900
4445
|
appId: "__ui-design",
|
|
3901
|
-
title: "
|
|
4446
|
+
title: "Settings",
|
|
3902
4447
|
icon: winIcon,
|
|
3903
4448
|
content: "app",
|
|
3904
|
-
width
|
|
3905
|
-
height
|
|
4449
|
+
width,
|
|
4450
|
+
height
|
|
3906
4451
|
});
|
|
4452
|
+
win.x = Math.max(16, Math.min(win.x, viewportWidth - width - 16));
|
|
4453
|
+
win.y = Math.max(16, Math.min(win.y, viewportHeight - height - 64));
|
|
4454
|
+
const el = getWinEl(win.id);
|
|
4455
|
+
if (el) applyWindowStyles(el, win);
|
|
3907
4456
|
renderUiDesignContent(win.id);
|
|
3908
4457
|
}
|
|
3909
4458
|
function renderUiDesignContent(winId) {
|
|
3910
4459
|
const body = getWinBody(winId);
|
|
4460
|
+
body.style.overflowY = "auto";
|
|
4461
|
+
body.style.paddingRight = "4px";
|
|
3911
4462
|
const curStyle = state.iconStyle;
|
|
3912
4463
|
const curLayout = state.iconLayout;
|
|
3913
4464
|
let html = '<div class="uid-container">';
|
|
@@ -3938,6 +4489,10 @@ function renderUiDesignContent(winId) {
|
|
|
3938
4489
|
html += `<div class="uid-preview-wrap" id="uidPreview_${winId}">`;
|
|
3939
4490
|
html += buildPreview(curStyle, curLayout);
|
|
3940
4491
|
html += "</div></div>";
|
|
4492
|
+
html += '<div class="uid-section">';
|
|
4493
|
+
html += '<div class="uid-section-title">VUI</div>';
|
|
4494
|
+
html += `<div id="${fieldId(winId, "vui-root")}"><div style="font-size:12px; color:#667085;">Loading VUI settings...</div></div>`;
|
|
4495
|
+
html += "</div>";
|
|
3941
4496
|
html += "</div>";
|
|
3942
4497
|
body.innerHTML = html;
|
|
3943
4498
|
body.querySelectorAll(".uid-option").forEach((btn) => {
|
|
@@ -3960,6 +4515,7 @@ function renderUiDesignContent(winId) {
|
|
|
3960
4515
|
renderUiDesignContent(winId);
|
|
3961
4516
|
});
|
|
3962
4517
|
});
|
|
4518
|
+
void hydrateVuiSettingsSection(winId);
|
|
3963
4519
|
}
|
|
3964
4520
|
var _renderDesktopIcons, _renderStartMenu, _renderTaskbarPins, _renderTaskbarWindows, _refreshOpenFolderWindows, _renderDesktopFiles, _refreshAllFileManagerWindows, ICON_STYLES, ICON_LAYOUTS, LOGO_ASSETS;
|
|
3965
4521
|
var init_ui_design = __esm({
|
|
@@ -3967,6 +4523,7 @@ var init_ui_design = __esm({
|
|
|
3967
4523
|
init_helpers();
|
|
3968
4524
|
init_state();
|
|
3969
4525
|
init_window_manager();
|
|
4526
|
+
init_desktop_upload();
|
|
3970
4527
|
_renderDesktopIcons = null;
|
|
3971
4528
|
_renderStartMenu = null;
|
|
3972
4529
|
_renderTaskbarPins = null;
|
|
@@ -17556,7 +18113,7 @@ function openCategoryFolder(folderId) {
|
|
|
17556
18113
|
} else {
|
|
17557
18114
|
settingsIcon = "\u{1F3A8}";
|
|
17558
18115
|
}
|
|
17559
|
-
html += `<div class="folder-app-item" data-app-id="__ui-design" title="
|
|
18116
|
+
html += `<div class="folder-app-item" data-app-id="__ui-design" title="Desktop, VUI, and appearance settings">`;
|
|
17560
18117
|
html += `<div class="folder-app-icon">${settingsIcon}</div>`;
|
|
17561
18118
|
html += `<div class="folder-app-label">Settings</div>`;
|
|
17562
18119
|
html += `</div>`;
|
|
@@ -17878,7 +18435,7 @@ function refreshOpenFolderWindows() {
|
|
|
17878
18435
|
} else {
|
|
17879
18436
|
settingsIcon = "\u{1F3A8}";
|
|
17880
18437
|
}
|
|
17881
|
-
html += `<div class="folder-app-item" data-app-id="__ui-design" title="
|
|
18438
|
+
html += `<div class="folder-app-item" data-app-id="__ui-design" title="Desktop, VUI, and appearance settings">`;
|
|
17882
18439
|
html += `<div class="folder-app-icon">${settingsIcon}</div>`;
|
|
17883
18440
|
html += `<div class="folder-app-label">Settings</div>`;
|
|
17884
18441
|
html += `</div>`;
|
|
@@ -18439,7 +18996,7 @@ function injectTaskbarDeps(deps) {
|
|
|
18439
18996
|
function getInternalApps() {
|
|
18440
18997
|
return [
|
|
18441
18998
|
{ id: "__file_manager", label: "Files", description: "File Manager", lucideIcon: LUCIDE_SYSTEM_ICONS["file-manager"] || "folder-open", emoji: "\u{1F4C2}", open: () => _openFileManager?.() },
|
|
18442
|
-
{ id: "__ui-design", label: "Settings", description: "
|
|
18999
|
+
{ id: "__ui-design", label: "Settings", description: "Desktop, VUI, and appearance settings", lucideIcon: LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", emoji: "\u{1F3A8}", open: () => _openUiDesign?.() },
|
|
18443
19000
|
{ id: "__task_manager", label: "Concurrency", description: "View running jobs", lucideIcon: "activity", emoji: "\u{1F4CA}", open: () => _openTaskManager3?.() }
|
|
18444
19001
|
];
|
|
18445
19002
|
}
|
|
@@ -19292,6 +19849,45 @@ function attachChatPanelListeners() {
|
|
|
19292
19849
|
renderMessages();
|
|
19293
19850
|
}
|
|
19294
19851
|
|
|
19852
|
+
// services/desktop/client/modules/vui-panel.ts
|
|
19853
|
+
var _panelOpen2 = false;
|
|
19854
|
+
var _iframeLoaded = false;
|
|
19855
|
+
function toggleVuiPanel() {
|
|
19856
|
+
if (_panelOpen2) closeVuiPanel();
|
|
19857
|
+
else openVuiPanel();
|
|
19858
|
+
}
|
|
19859
|
+
function openVuiPanel() {
|
|
19860
|
+
_panelOpen2 = true;
|
|
19861
|
+
const panel = document.getElementById("vuiPanel");
|
|
19862
|
+
if (panel) panel.classList.add("open");
|
|
19863
|
+
document.getElementById("desktop")?.classList.add("vui-panel-open");
|
|
19864
|
+
if (!_iframeLoaded) {
|
|
19865
|
+
const frame = document.getElementById("vuiFrame");
|
|
19866
|
+
if (frame) {
|
|
19867
|
+
frame.src = "/vui";
|
|
19868
|
+
_iframeLoaded = true;
|
|
19869
|
+
}
|
|
19870
|
+
}
|
|
19871
|
+
document.getElementById("vuiToggleBtn")?.classList.add("active");
|
|
19872
|
+
}
|
|
19873
|
+
function closeVuiPanel() {
|
|
19874
|
+
_panelOpen2 = false;
|
|
19875
|
+
const panel = document.getElementById("vuiPanel");
|
|
19876
|
+
if (panel) panel.classList.remove("open");
|
|
19877
|
+
document.getElementById("desktop")?.classList.remove("vui-panel-open");
|
|
19878
|
+
document.getElementById("vuiToggleBtn")?.classList.remove("active");
|
|
19879
|
+
}
|
|
19880
|
+
function attachVuiPanelListeners() {
|
|
19881
|
+
const closeBtn = document.getElementById("vuiPanelClose");
|
|
19882
|
+
if (closeBtn) closeBtn.addEventListener("click", closeVuiPanel);
|
|
19883
|
+
document.addEventListener("keydown", (e) => {
|
|
19884
|
+
if (e.altKey && e.code === "KeyV") {
|
|
19885
|
+
e.preventDefault();
|
|
19886
|
+
toggleVuiPanel();
|
|
19887
|
+
}
|
|
19888
|
+
});
|
|
19889
|
+
}
|
|
19890
|
+
|
|
19295
19891
|
// services/desktop/client/desktop-client.ts
|
|
19296
19892
|
init_dialogs();
|
|
19297
19893
|
setUpdateRunBtnState(updateRunBtnState);
|
|
@@ -19469,6 +20065,10 @@ function attachGlobalListeners() {
|
|
|
19469
20065
|
if (chatToggleBtn) {
|
|
19470
20066
|
chatToggleBtn.addEventListener("click", () => toggleChatPanel());
|
|
19471
20067
|
}
|
|
20068
|
+
const vuiToggleBtn = document.getElementById("vuiToggleBtn");
|
|
20069
|
+
if (vuiToggleBtn) {
|
|
20070
|
+
vuiToggleBtn.addEventListener("click", () => toggleVuiPanel());
|
|
20071
|
+
}
|
|
19472
20072
|
document.addEventListener("click", (e) => {
|
|
19473
20073
|
const target = e.target;
|
|
19474
20074
|
if (state.startMenuOpen) {
|
|
@@ -19732,6 +20332,7 @@ async function boot() {
|
|
|
19732
20332
|
attachDesktopDragDrop();
|
|
19733
20333
|
attachDesktopIconDrag();
|
|
19734
20334
|
attachChatPanelListeners();
|
|
20335
|
+
attachVuiPanelListeners();
|
|
19735
20336
|
void requestNotificationPermission();
|
|
19736
20337
|
}
|
|
19737
20338
|
void boot();
|