openvoiceui 1.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/.env.example +104 -0
- package/Dockerfile +30 -0
- package/LICENSE +21 -0
- package/README.md +638 -0
- package/SETUP.md +360 -0
- package/app.py +232 -0
- package/auto-approve-devices.js +111 -0
- package/cli/index.js +372 -0
- package/config/__init__.py +4 -0
- package/config/default.yaml +43 -0
- package/config/flags.yaml +67 -0
- package/config/loader.py +203 -0
- package/config/providers.yaml +71 -0
- package/config/speech_normalization.yaml +182 -0
- package/config/theme.json +4 -0
- package/data/greetings.json +25 -0
- package/default-pages/ai-image-creator.html +915 -0
- package/default-pages/bulk-image-uploader.html +492 -0
- package/default-pages/desktop.html +2865 -0
- package/default-pages/file-explorer.html +854 -0
- package/default-pages/interactive-map.html +655 -0
- package/default-pages/style-guide.html +1005 -0
- package/default-pages/website-setup.html +1623 -0
- package/deploy/openclaw/Dockerfile +46 -0
- package/deploy/openvoiceui.service +30 -0
- package/deploy/setup-nginx.sh +50 -0
- package/deploy/setup-sudo.sh +306 -0
- package/deploy/skill-runner/Dockerfile +19 -0
- package/deploy/skill-runner/requirements.txt +14 -0
- package/deploy/skill-runner/server.py +269 -0
- package/deploy/supertonic/Dockerfile +22 -0
- package/deploy/supertonic/server.py +79 -0
- package/docker-compose.pinokio.yml +11 -0
- package/docker-compose.yml +59 -0
- package/greetings.json +25 -0
- package/index.html +65 -0
- package/inject-device-identity.js +142 -0
- package/package.json +82 -0
- package/profiles/default.json +114 -0
- package/profiles/manager.py +354 -0
- package/profiles/schema.json +337 -0
- package/prompts/voice-system-prompt.md +149 -0
- package/providers/__init__.py +39 -0
- package/providers/base.py +63 -0
- package/providers/llm/__init__.py +12 -0
- package/providers/llm/base.py +71 -0
- package/providers/llm/clawdbot_provider.py +112 -0
- package/providers/llm/zai_provider.py +115 -0
- package/providers/registry.py +320 -0
- package/providers/stt/__init__.py +12 -0
- package/providers/stt/base.py +58 -0
- package/providers/stt/webspeech_provider.py +49 -0
- package/providers/stt/whisper_provider.py +100 -0
- package/providers/tts/__init__.py +20 -0
- package/providers/tts/base.py +91 -0
- package/providers/tts/groq_provider.py +74 -0
- package/providers/tts/supertonic_provider.py +72 -0
- package/requirements.txt +38 -0
- package/routes/__init__.py +10 -0
- package/routes/admin.py +515 -0
- package/routes/canvas.py +1315 -0
- package/routes/chat.py +51 -0
- package/routes/conversation.py +2158 -0
- package/routes/elevenlabs_hybrid.py +306 -0
- package/routes/greetings.py +98 -0
- package/routes/icons.py +279 -0
- package/routes/image_gen.py +364 -0
- package/routes/instructions.py +190 -0
- package/routes/music.py +838 -0
- package/routes/onboarding.py +43 -0
- package/routes/pi.py +62 -0
- package/routes/profiles.py +215 -0
- package/routes/report_issue.py +68 -0
- package/routes/static_files.py +533 -0
- package/routes/suno.py +664 -0
- package/routes/theme.py +81 -0
- package/routes/transcripts.py +199 -0
- package/routes/vision.py +348 -0
- package/routes/workspace.py +288 -0
- package/server.py +1510 -0
- package/services/__init__.py +1 -0
- package/services/auth.py +143 -0
- package/services/canvas_versioning.py +239 -0
- package/services/db_pool.py +107 -0
- package/services/gateway.py +16 -0
- package/services/gateway_manager.py +333 -0
- package/services/gateways/__init__.py +12 -0
- package/services/gateways/base.py +110 -0
- package/services/gateways/compat.py +264 -0
- package/services/gateways/openclaw.py +1134 -0
- package/services/health.py +100 -0
- package/services/memory_client.py +455 -0
- package/services/paths.py +26 -0
- package/services/speech_normalizer.py +285 -0
- package/services/tts.py +270 -0
- package/setup-config.js +262 -0
- package/sounds/air_horn.mp3 +0 -0
- package/sounds/bruh.mp3 +0 -0
- package/sounds/crowd_cheer.mp3 +0 -0
- package/sounds/gunshot.mp3 +0 -0
- package/sounds/impact.mp3 +0 -0
- package/sounds/lets_go.mp3 +0 -0
- package/sounds/record_stop.mp3 +0 -0
- package/sounds/rewind.mp3 +0 -0
- package/sounds/sad_trombone.mp3 +0 -0
- package/sounds/scratch_long.mp3 +0 -0
- package/sounds/yeah.mp3 +0 -0
- package/src/adapters/ClawdBotAdapter.js +264 -0
- package/src/adapters/_template.js +133 -0
- package/src/adapters/elevenlabs-classic.js +841 -0
- package/src/adapters/elevenlabs-hybrid.js +812 -0
- package/src/adapters/hume-evi.js +676 -0
- package/src/admin.html +1339 -0
- package/src/app.js +8802 -0
- package/src/core/Config.js +173 -0
- package/src/core/EmotionEngine.js +307 -0
- package/src/core/EventBridge.js +180 -0
- package/src/core/EventBus.js +117 -0
- package/src/core/VoiceSession.js +607 -0
- package/src/face/BaseFace.js +259 -0
- package/src/face/EyeFace.js +208 -0
- package/src/face/HaloSmokeFace.js +509 -0
- package/src/face/manifest.json +27 -0
- package/src/face/previews/eyes.svg +16 -0
- package/src/face/previews/orb.svg +29 -0
- package/src/features/MusicPlayer.js +620 -0
- package/src/features/Soundboard.js +128 -0
- package/src/providers/DeepgramSTT.js +472 -0
- package/src/providers/DeepgramStreamingSTT.js +766 -0
- package/src/providers/GroqSTT.js +559 -0
- package/src/providers/TTSPlayer.js +323 -0
- package/src/providers/WebSpeechSTT.js +479 -0
- package/src/providers/tts/BaseTTSProvider.js +81 -0
- package/src/providers/tts/HumeProvider.js +77 -0
- package/src/providers/tts/SupertonicProvider.js +174 -0
- package/src/providers/tts/index.js +140 -0
- package/src/shell/adapter-registry.js +154 -0
- package/src/shell/caller-bridge.js +35 -0
- package/src/shell/camera-bridge.js +28 -0
- package/src/shell/canvas-bridge.js +32 -0
- package/src/shell/commercial-bridge.js +44 -0
- package/src/shell/face-bridge.js +44 -0
- package/src/shell/music-bridge.js +60 -0
- package/src/shell/orchestrator.js +233 -0
- package/src/shell/profile-discovery.js +303 -0
- package/src/shell/sounds-bridge.js +28 -0
- package/src/shell/transcript-bridge.js +61 -0
- package/src/shell/waveform-bridge.js +33 -0
- package/src/styles/base.css +2862 -0
- package/src/styles/face.css +417 -0
- package/src/styles/pi-overrides.css +89 -0
- package/src/styles/theme-dark.css +67 -0
- package/src/test-tts.html +175 -0
- package/src/ui/AppShell.js +544 -0
- package/src/ui/ProfileSwitcher.js +228 -0
- package/src/ui/SessionControl.js +240 -0
- package/src/ui/face/FacePicker.js +195 -0
- package/src/ui/face/FaceRenderer.js +309 -0
- package/src/ui/settings/PlaylistEditor.js +366 -0
- package/src/ui/settings/SettingsPanel.css +684 -0
- package/src/ui/settings/SettingsPanel.js +419 -0
- package/src/ui/settings/TTSVoicePreview.js +210 -0
- package/src/ui/themes/ThemeManager.js +213 -0
- package/src/ui/visualizers/BaseVisualizer.js +29 -0
- package/src/ui/visualizers/PartyFXVisualizer.css +291 -0
- package/src/ui/visualizers/PartyFXVisualizer.js +637 -0
- package/static/emulators/jsdos/js-dos.css +1 -0
- package/static/emulators/jsdos/js-dos.js +22 -0
- package/static/favicon.svg +55 -0
- package/static/icons/apple-touch-icon.png +0 -0
- package/static/icons/favicon-32.png +0 -0
- package/static/icons/icon-192.png +0 -0
- package/static/icons/icon-512.png +0 -0
- package/static/install.html +449 -0
- package/static/manifest.json +26 -0
- package/static/sw.js +21 -0
- package/tts_providers/__init__.py +136 -0
- package/tts_providers/base_provider.py +319 -0
- package/tts_providers/groq_provider.py +155 -0
- package/tts_providers/hume_provider.py +226 -0
- package/tts_providers/providers_config.json +119 -0
- package/tts_providers/qwen3_provider.py +371 -0
- package/tts_providers/resemble_provider.py +315 -0
- package/tts_providers/supertonic_provider.py +557 -0
- package/tts_providers/supertonic_tts.py +399 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// auto-approve-devices.js — Approve all pending OpenClaw devices.
|
|
3
|
+
// Runs on the host, execs into the openclaw container to move pending → paired.
|
|
4
|
+
// Called by start.js after containers are healthy.
|
|
5
|
+
//
|
|
6
|
+
// Polls for up to 2 minutes (24 retries × 5s) because OpenVoiceUI doesn't
|
|
7
|
+
// connect to OpenClaw until the user opens the browser — which happens AFTER
|
|
8
|
+
// Pinokio shows the "Open" button. We need to wait for that.
|
|
9
|
+
|
|
10
|
+
const { execSync } = require("child_process");
|
|
11
|
+
|
|
12
|
+
const COMPOSE = "docker compose -f docker-compose.yml -f docker-compose.pinokio.yml";
|
|
13
|
+
const MAX_ATTEMPTS = 24; // 24 × 5s = 2 minutes
|
|
14
|
+
const DELAY_MS = 5000;
|
|
15
|
+
|
|
16
|
+
// MSYS_NO_PATHCONV=1 prevents Git Bash on Windows from converting /container/paths
|
|
17
|
+
const EXEC_ENV = Object.assign({}, process.env, { MSYS_NO_PATHCONV: "1" });
|
|
18
|
+
|
|
19
|
+
// Node one-liner that runs INSIDE the openclaw container
|
|
20
|
+
const script = `
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
try {
|
|
23
|
+
const pendingPath = '/root/.openclaw/devices/pending.json';
|
|
24
|
+
const pairedPath = '/root/.openclaw/devices/paired.json';
|
|
25
|
+
let pending = {};
|
|
26
|
+
let paired = {};
|
|
27
|
+
try { pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8')); } catch(e) {}
|
|
28
|
+
try { paired = JSON.parse(fs.readFileSync(pairedPath, 'utf8')); } catch(e) {}
|
|
29
|
+
const pairedCount = Object.keys(paired).length;
|
|
30
|
+
let count = 0;
|
|
31
|
+
for (const entry of Object.values(pending)) {
|
|
32
|
+
if (entry.deviceId && entry.publicKey) {
|
|
33
|
+
paired[entry.deviceId] = {
|
|
34
|
+
publicKey: entry.publicKey,
|
|
35
|
+
name: entry.name || 'auto-approved',
|
|
36
|
+
role: entry.role || 'operator',
|
|
37
|
+
roles: entry.roles || ['operator'],
|
|
38
|
+
scopes: entry.scopes || ['operator.read', 'operator.write'],
|
|
39
|
+
paired: true,
|
|
40
|
+
pairedAt: new Date().toISOString(),
|
|
41
|
+
autoApproved: true,
|
|
42
|
+
};
|
|
43
|
+
count++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (count > 0) {
|
|
47
|
+
fs.writeFileSync(pairedPath, JSON.stringify(paired, null, 2));
|
|
48
|
+
fs.writeFileSync(pendingPath, '{}');
|
|
49
|
+
console.log('APPROVED:' + count);
|
|
50
|
+
} else if (pairedCount > 0) {
|
|
51
|
+
console.log('ALREADY_PAIRED:' + pairedCount);
|
|
52
|
+
} else {
|
|
53
|
+
console.log('APPROVED:0');
|
|
54
|
+
}
|
|
55
|
+
} catch(e) {
|
|
56
|
+
console.log('ERROR:' + e.message);
|
|
57
|
+
}
|
|
58
|
+
`.replace(/\n/g, " ").trim();
|
|
59
|
+
|
|
60
|
+
function sleep(ms) {
|
|
61
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function run() {
|
|
65
|
+
console.log(" Waiting for browser to connect (up to 2 minutes)...");
|
|
66
|
+
|
|
67
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
68
|
+
try {
|
|
69
|
+
const result = execSync(
|
|
70
|
+
`${COMPOSE} exec -T openclaw node -e "${script.replace(/"/g, '\\"')}"`,
|
|
71
|
+
{ encoding: "utf8", timeout: 15000, env: EXEC_ENV }
|
|
72
|
+
).trim();
|
|
73
|
+
|
|
74
|
+
// New device(s) approved
|
|
75
|
+
const approvedMatch = result.match(/APPROVED:(\d+)/);
|
|
76
|
+
if (approvedMatch && parseInt(approvedMatch[1]) > 0) {
|
|
77
|
+
console.log(` Auto-approved ${approvedMatch[1]} device(s) — ready to use!`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Device already paired from a previous session
|
|
82
|
+
const pairedMatch = result.match(/ALREADY_PAIRED:(\d+)/);
|
|
83
|
+
if (pairedMatch && parseInt(pairedMatch[1]) > 0) {
|
|
84
|
+
console.log(` Device already paired (${pairedMatch[1]} device(s)) — ready to use!`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Nothing yet — keep polling
|
|
89
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
90
|
+
// Only log every 4th attempt to avoid spam
|
|
91
|
+
if (attempt % 4 === 0) {
|
|
92
|
+
const remaining = Math.round((MAX_ATTEMPTS - attempt) * DELAY_MS / 1000);
|
|
93
|
+
console.log(` Still waiting for browser connection... (${remaining}s remaining)`);
|
|
94
|
+
}
|
|
95
|
+
await sleep(DELAY_MS);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(" Timeout: No device connected after 2 minutes.");
|
|
98
|
+
console.log(" If you see NOT_PAIRED errors, open the browser and restart the app.");
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
102
|
+
// Container might still be starting — retry silently
|
|
103
|
+
await sleep(DELAY_MS);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(" Device auto-approve failed:", e.message.split("\n")[0]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
run();
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// OpenVoiceUI CLI — zero-dependency entry point
|
|
3
|
+
// Usage: openvoiceui <command> [options]
|
|
4
|
+
|
|
5
|
+
const { execSync, spawn } = require("child_process");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
const VERSION = require("../package.json").version;
|
|
10
|
+
const PROJECT_DIR = path.resolve(__dirname, "..");
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function run(cmd, opts = {}) {
|
|
17
|
+
return execSync(cmd, {
|
|
18
|
+
cwd: PROJECT_DIR,
|
|
19
|
+
stdio: opts.silent ? "pipe" : "inherit",
|
|
20
|
+
...opts,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runLive(cmd, args = []) {
|
|
25
|
+
const child = spawn(cmd, args, {
|
|
26
|
+
cwd: PROJECT_DIR,
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
shell: true,
|
|
29
|
+
});
|
|
30
|
+
child.on("error", (err) => {
|
|
31
|
+
console.error(`Failed to run: ${cmd} ${args.join(" ")}`);
|
|
32
|
+
console.error(err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
return child;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasDocker() {
|
|
39
|
+
try {
|
|
40
|
+
execSync("docker --version", { stdio: "pipe" });
|
|
41
|
+
execSync("docker compose version", { stdio: "pipe" });
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isRunning() {
|
|
49
|
+
try {
|
|
50
|
+
const out = execSync("docker compose ps --format json", {
|
|
51
|
+
cwd: PROJECT_DIR,
|
|
52
|
+
stdio: "pipe",
|
|
53
|
+
}).toString();
|
|
54
|
+
// docker compose ps returns one JSON object per line
|
|
55
|
+
const lines = out.trim().split("\n").filter(Boolean);
|
|
56
|
+
return lines.length > 0;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getPort() {
|
|
63
|
+
const envPath = path.join(PROJECT_DIR, ".env");
|
|
64
|
+
if (fs.existsSync(envPath)) {
|
|
65
|
+
const content = fs.readFileSync(envPath, "utf8");
|
|
66
|
+
const match = content.match(/^PORT=(\d+)/m);
|
|
67
|
+
if (match) return match[1];
|
|
68
|
+
}
|
|
69
|
+
return "5001";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printBanner() {
|
|
73
|
+
console.log(`
|
|
74
|
+
╔══════════════════════════════════════╗
|
|
75
|
+
║ OpenVoiceUI v${VERSION.padEnd(14)}║
|
|
76
|
+
║ Voice-Powered AI Assistant ║
|
|
77
|
+
╚══════════════════════════════════════╝
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Commands
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
const commands = {
|
|
86
|
+
setup: {
|
|
87
|
+
desc: "Interactive setup — configure API keys and build Docker images",
|
|
88
|
+
run: cmdSetup,
|
|
89
|
+
},
|
|
90
|
+
start: {
|
|
91
|
+
desc: "Start OpenVoiceUI (Docker Compose)",
|
|
92
|
+
run: cmdStart,
|
|
93
|
+
},
|
|
94
|
+
stop: {
|
|
95
|
+
desc: "Stop OpenVoiceUI",
|
|
96
|
+
run: cmdStop,
|
|
97
|
+
},
|
|
98
|
+
restart: {
|
|
99
|
+
desc: "Restart OpenVoiceUI",
|
|
100
|
+
run: cmdRestart,
|
|
101
|
+
},
|
|
102
|
+
status: {
|
|
103
|
+
desc: "Show container status",
|
|
104
|
+
run: cmdStatus,
|
|
105
|
+
},
|
|
106
|
+
logs: {
|
|
107
|
+
desc: "Stream container logs (Ctrl+C to stop)",
|
|
108
|
+
run: cmdLogs,
|
|
109
|
+
},
|
|
110
|
+
update: {
|
|
111
|
+
desc: "Pull latest source and rebuild images",
|
|
112
|
+
run: cmdUpdate,
|
|
113
|
+
},
|
|
114
|
+
config: {
|
|
115
|
+
desc: "Open the OpenClaw control panel (localhost:18791)",
|
|
116
|
+
run: cmdConfig,
|
|
117
|
+
},
|
|
118
|
+
help: {
|
|
119
|
+
desc: "Show this help message",
|
|
120
|
+
run: cmdHelp,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// --- setup ---
|
|
125
|
+
async function cmdSetup() {
|
|
126
|
+
printBanner();
|
|
127
|
+
console.log(" Setting up OpenVoiceUI...\n");
|
|
128
|
+
|
|
129
|
+
if (!hasDocker()) {
|
|
130
|
+
console.error(" ERROR: Docker and Docker Compose are required.");
|
|
131
|
+
console.error(" Install Docker: https://docs.docker.com/get-docker/\n");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
console.log(" [OK] Docker found\n");
|
|
135
|
+
|
|
136
|
+
// Interactive key collection
|
|
137
|
+
const readline = require("readline");
|
|
138
|
+
const rl = readline.createInterface({
|
|
139
|
+
input: process.stdin,
|
|
140
|
+
output: process.stdout,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const ask = (q) =>
|
|
144
|
+
new Promise((resolve) =>
|
|
145
|
+
rl.question(q, (a) => resolve(a.trim()))
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
console.log(" ── Required Keys ──────────────────────────────────────");
|
|
149
|
+
console.log(" These are needed for the app to function.\n");
|
|
150
|
+
|
|
151
|
+
const groqKey = await ask(
|
|
152
|
+
" Groq API Key (console.groq.com) [REQUIRED]: "
|
|
153
|
+
);
|
|
154
|
+
if (!groqKey) {
|
|
155
|
+
console.error("\n ERROR: Groq API key is required for TTS.\n");
|
|
156
|
+
rl.close();
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const deepgramKey = await ask(
|
|
161
|
+
" Deepgram API Key (console.deepgram.com) [REQUIRED]: "
|
|
162
|
+
);
|
|
163
|
+
if (!deepgramKey) {
|
|
164
|
+
console.error("\n ERROR: Deepgram API key is required for STT.\n");
|
|
165
|
+
rl.close();
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log("\n ── AI Provider Keys (pick at least one) ──────────────");
|
|
170
|
+
console.log(" Press Enter to skip any provider.\n");
|
|
171
|
+
|
|
172
|
+
const zaiKey = await ask(" Z.AI API Key (z.ai): ");
|
|
173
|
+
const anthropicKey = await ask(" Anthropic API Key (console.anthropic.com): ");
|
|
174
|
+
const openaiKey = await ask(" OpenAI API Key (platform.openai.com): ");
|
|
175
|
+
|
|
176
|
+
if (!zaiKey && !anthropicKey && !openaiKey) {
|
|
177
|
+
console.log(
|
|
178
|
+
"\n WARNING: No AI provider key set. You can add one later in the .env file.\n"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log("\n ── Optional Keys ─────────────────────────────────────");
|
|
183
|
+
console.log(" Press Enter to skip.\n");
|
|
184
|
+
|
|
185
|
+
const geminiKey = await ask(" Gemini API Key (aistudio.google.com): ");
|
|
186
|
+
const openrouterKey = await ask(" OpenRouter API Key (openrouter.ai): ");
|
|
187
|
+
const sunoKey = await ask(" Suno API Key — AI Music (sunoapi.org): ");
|
|
188
|
+
|
|
189
|
+
const portAnswer = await ask(" Port (default 5001): ");
|
|
190
|
+
const port = portAnswer || "5001";
|
|
191
|
+
|
|
192
|
+
rl.close();
|
|
193
|
+
|
|
194
|
+
// Set env vars for setup-config.js
|
|
195
|
+
const setupEnv = {
|
|
196
|
+
PINOKIO_PORT: port,
|
|
197
|
+
PINOKIO_GROQ_API_KEY: groqKey,
|
|
198
|
+
PINOKIO_DEEPGRAM_API_KEY: deepgramKey,
|
|
199
|
+
PINOKIO_ZAI_API_KEY: zaiKey,
|
|
200
|
+
PINOKIO_ANTHROPIC_API_KEY: anthropicKey,
|
|
201
|
+
PINOKIO_OPENAI_API_KEY: openaiKey,
|
|
202
|
+
PINOKIO_GEMINI_API_KEY: geminiKey,
|
|
203
|
+
PINOKIO_OPENROUTER_API_KEY: openrouterKey,
|
|
204
|
+
PINOKIO_SUNO_API_KEY: sunoKey,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
console.log("\n Generating configuration files...\n");
|
|
208
|
+
run("node setup-config.js", {
|
|
209
|
+
env: { ...process.env, ...setupEnv },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log("\n Building Docker images (this may take a few minutes)...\n");
|
|
213
|
+
run("docker compose build");
|
|
214
|
+
|
|
215
|
+
console.log(`
|
|
216
|
+
════════════════════════════════════════════════════════
|
|
217
|
+
Setup complete!
|
|
218
|
+
|
|
219
|
+
Start OpenVoiceUI: openvoiceui start
|
|
220
|
+
Open in browser: http://localhost:${port}
|
|
221
|
+
OpenClaw control: http://localhost:18791
|
|
222
|
+
View logs: openvoiceui logs
|
|
223
|
+
════════════════════════════════════════════════════════
|
|
224
|
+
`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- start ---
|
|
228
|
+
function cmdStart() {
|
|
229
|
+
printBanner();
|
|
230
|
+
if (!hasDocker()) {
|
|
231
|
+
console.error(" ERROR: Docker not found. Run: openvoiceui setup\n");
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const envPath = path.join(PROJECT_DIR, ".env");
|
|
236
|
+
if (!fs.existsSync(envPath)) {
|
|
237
|
+
console.error(" ERROR: No .env file found. Run: openvoiceui setup\n");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(" Starting OpenVoiceUI...\n");
|
|
242
|
+
run("docker compose up -d");
|
|
243
|
+
|
|
244
|
+
// Inject pre-paired device identity if available
|
|
245
|
+
const prePairedPath = path.join(
|
|
246
|
+
PROJECT_DIR,
|
|
247
|
+
"openclaw-data",
|
|
248
|
+
"pre-paired-device.json"
|
|
249
|
+
);
|
|
250
|
+
if (fs.existsSync(prePairedPath)) {
|
|
251
|
+
try {
|
|
252
|
+
run("node inject-device-identity.js", { silent: true });
|
|
253
|
+
} catch {
|
|
254
|
+
// Non-fatal — device pairing may already be done
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const port = getPort();
|
|
259
|
+
console.log(`
|
|
260
|
+
OpenVoiceUI is running!
|
|
261
|
+
|
|
262
|
+
App: http://localhost:${port}
|
|
263
|
+
OpenClaw: http://localhost:18791
|
|
264
|
+
Stop: openvoiceui stop
|
|
265
|
+
Logs: openvoiceui logs
|
|
266
|
+
`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- stop ---
|
|
270
|
+
function cmdStop() {
|
|
271
|
+
console.log(" Stopping OpenVoiceUI...\n");
|
|
272
|
+
run("docker compose down");
|
|
273
|
+
console.log(" Stopped.\n");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// --- restart ---
|
|
277
|
+
function cmdRestart() {
|
|
278
|
+
console.log(" Restarting OpenVoiceUI...\n");
|
|
279
|
+
run("docker compose down");
|
|
280
|
+
run("docker compose up -d");
|
|
281
|
+
|
|
282
|
+
const port = getPort();
|
|
283
|
+
console.log(`\n Restarted. App: http://localhost:${port}\n`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// --- status ---
|
|
287
|
+
function cmdStatus() {
|
|
288
|
+
printBanner();
|
|
289
|
+
if (!hasDocker()) {
|
|
290
|
+
console.error(" Docker not found.\n");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
run("docker compose ps");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// --- logs ---
|
|
297
|
+
function cmdLogs() {
|
|
298
|
+
const child = runLive("docker", ["compose", "logs", "-f", "--tail", "100"]);
|
|
299
|
+
process.on("SIGINT", () => {
|
|
300
|
+
child.kill("SIGINT");
|
|
301
|
+
process.exit(0);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --- update ---
|
|
306
|
+
function cmdUpdate() {
|
|
307
|
+
printBanner();
|
|
308
|
+
console.log(" Updating OpenVoiceUI...\n");
|
|
309
|
+
|
|
310
|
+
// Check if this is a git repo (cloned install) or npm install
|
|
311
|
+
const gitDir = path.join(PROJECT_DIR, ".git");
|
|
312
|
+
if (fs.existsSync(gitDir)) {
|
|
313
|
+
console.log(" Pulling latest source...\n");
|
|
314
|
+
run("git pull --ff-only");
|
|
315
|
+
} else {
|
|
316
|
+
console.log(" Installed via npm — update with: npm update -g openvoiceui\n");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(" Rebuilding Docker images...\n");
|
|
320
|
+
run("docker compose build --pull");
|
|
321
|
+
|
|
322
|
+
if (isRunning()) {
|
|
323
|
+
console.log(" Restarting containers...\n");
|
|
324
|
+
run("docker compose up -d");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(" Update complete!\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --- config ---
|
|
331
|
+
function cmdConfig() {
|
|
332
|
+
console.log(" OpenClaw control panel: http://localhost:18791\n");
|
|
333
|
+
console.log(" Open this URL in your browser to change AI models,");
|
|
334
|
+
console.log(" add provider keys, and configure agent behavior.\n");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// --- help ---
|
|
338
|
+
function cmdHelp() {
|
|
339
|
+
printBanner();
|
|
340
|
+
console.log(" Usage: openvoiceui <command>\n");
|
|
341
|
+
console.log(" Commands:\n");
|
|
342
|
+
for (const [name, cmd] of Object.entries(commands)) {
|
|
343
|
+
console.log(` ${name.padEnd(12)} ${cmd.desc}`);
|
|
344
|
+
}
|
|
345
|
+
console.log(`
|
|
346
|
+
Quick start:
|
|
347
|
+
openvoiceui setup # configure keys + build images
|
|
348
|
+
openvoiceui start # launch the app
|
|
349
|
+
open http://localhost:5001
|
|
350
|
+
`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Main
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
const command = process.argv[2] || "help";
|
|
358
|
+
|
|
359
|
+
if (commands[command]) {
|
|
360
|
+
const result = commands[command].run();
|
|
361
|
+
// Handle async commands (setup uses readline)
|
|
362
|
+
if (result && typeof result.catch === "function") {
|
|
363
|
+
result.catch((err) => {
|
|
364
|
+
console.error(`\n ERROR: ${err.message}\n`);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
console.error(` Unknown command: ${command}\n`);
|
|
370
|
+
cmdHelp();
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# OpenVoiceUI Default Configuration
|
|
2
|
+
# All values here can be overridden by environment variables.
|
|
3
|
+
# Env var naming: SECTION__KEY (double underscore), e.g. SERVER__PORT=5002
|
|
4
|
+
# Or use the direct env var names listed in comments.
|
|
5
|
+
|
|
6
|
+
# ── Server ────────────────────────────────────────────────────────────────────
|
|
7
|
+
server:
|
|
8
|
+
host: "0.0.0.0"
|
|
9
|
+
port: 5001 # env: PORT
|
|
10
|
+
debug: false
|
|
11
|
+
threaded: true
|
|
12
|
+
|
|
13
|
+
# ── Gateway (OpenClaw) ────────────────────────────────────────────────────────
|
|
14
|
+
gateway:
|
|
15
|
+
url: "ws://127.0.0.1:18791" # env: CLAWDBOT_GATEWAY_URL
|
|
16
|
+
auth_token: "" # env: CLAWDBOT_AUTH_TOKEN (required)
|
|
17
|
+
session_key: "voice-main-1" # env: GATEWAY_SESSION_KEY
|
|
18
|
+
|
|
19
|
+
# ── LLM Models ────────────────────────────────────────────────────────────────
|
|
20
|
+
models:
|
|
21
|
+
whisper: "tiny" # Faster-Whisper model size
|
|
22
|
+
whisper_device: "cpu" # cpu or cuda
|
|
23
|
+
whisper_compute: "float32" # float32, float16, int8
|
|
24
|
+
|
|
25
|
+
# ── TTS ───────────────────────────────────────────────────────────────────────
|
|
26
|
+
tts:
|
|
27
|
+
provider: "groq" # env: TTS_PROVIDER (groq or supertonic)
|
|
28
|
+
|
|
29
|
+
# ── Conversation ──────────────────────────────────────────────────────────────
|
|
30
|
+
conversation:
|
|
31
|
+
max_history_messages: 20 # env: MAX_HISTORY_MESSAGES
|
|
32
|
+
active_profile: "default" # must match a filename in profiles/
|
|
33
|
+
|
|
34
|
+
# ── Logging ───────────────────────────────────────────────────────────────────
|
|
35
|
+
logging:
|
|
36
|
+
level: "INFO" # env: LOG_LEVEL
|
|
37
|
+
|
|
38
|
+
# ── Features (runtime toggles) ────────────────────────────────────────────────
|
|
39
|
+
features:
|
|
40
|
+
enable_fts: true # env: ENABLE_FTS
|
|
41
|
+
enable_briefing: true # env: ENABLE_BRIEFING
|
|
42
|
+
enable_history_reload: true # env: ENABLE_HISTORY_RELOAD
|
|
43
|
+
memory_client: true # whether to use memory_client if available
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Feature Flags — ai-eyes2 refactor
|
|
2
|
+
#
|
|
3
|
+
# These flags gate each refactor phase so changes can be rolled out gradually.
|
|
4
|
+
# All flags default to false (old behaviour preserved until explicitly enabled).
|
|
5
|
+
#
|
|
6
|
+
# Override any flag via env var:
|
|
7
|
+
# FEATURE_USE_BLUEPRINTS=true
|
|
8
|
+
# FEATURE_PERSISTENT_WEBSOCKET=true
|
|
9
|
+
# etc.
|
|
10
|
+
#
|
|
11
|
+
# Usage in code:
|
|
12
|
+
# from config.loader import config
|
|
13
|
+
# if config.flag('use_blueprints'):
|
|
14
|
+
# ...
|
|
15
|
+
|
|
16
|
+
flags:
|
|
17
|
+
# ── Phase 1: Foundation ───────────────────────────────────────────────────
|
|
18
|
+
use_yaml_config: true # P1-T2 — YAML config system active
|
|
19
|
+
use_feature_flags: true # P1-T3 — this flag system itself (meta)
|
|
20
|
+
use_health_probes: true # P1-T4 — /health/live and /health/ready endpoints
|
|
21
|
+
use_sqlite_wal: true # P1-T5 — WAL mode + connection pooling
|
|
22
|
+
use_session_reset: false # P1-T7 — POST /api/session/reset endpoint
|
|
23
|
+
|
|
24
|
+
# ── Phase 2: Backend Blueprint Split ─────────────────────────────────────
|
|
25
|
+
use_blueprints: false # P2-T1 — Flask app factory + blueprints
|
|
26
|
+
use_gateway_service: false # P2-T2 — GatewayConnection as services/gateway.py
|
|
27
|
+
use_conversation_blueprint: false # P2-T3 — conversation routes in blueprint
|
|
28
|
+
use_music_blueprint: false # P2-T4 — music routes in blueprint
|
|
29
|
+
use_canvas_blueprint: false # P2-T5 — canvas routes in blueprint
|
|
30
|
+
use_admin_blueprint: false # P2-T6 — admin routes in blueprint
|
|
31
|
+
use_tts_service: false # P2-T7 — TTS providers as service module
|
|
32
|
+
|
|
33
|
+
# ── Phase G: Gateway & Transport ─────────────────────────────────────────
|
|
34
|
+
use_persistent_websocket: false # PG-T2 — persistent WS to Gateway
|
|
35
|
+
use_streaming_events: false # PG-T3 — stream delta events to frontend
|
|
36
|
+
use_webchat_mode: true # PG-T5 — Gateway client mode WEBCHAT
|
|
37
|
+
|
|
38
|
+
# ── Phase 3: Frontend Module Extraction ──────────────────────────────────
|
|
39
|
+
use_eventbus: false # P3-T1 — frontend EventBus pub/sub
|
|
40
|
+
use_css_modules: false # P3-T2 — extracted CSS files
|
|
41
|
+
use_stt_module: false # P3-T3 — WebSpeechSTT as module
|
|
42
|
+
use_tts_player_module: false # P3-T4 — TTSPlayer as module
|
|
43
|
+
use_face_module: false # P3-T5 — face system as module
|
|
44
|
+
use_music_module: false # P3-T6 — MusicPlayer as module
|
|
45
|
+
use_voice_session: false # P3-T7 — VoiceSession orchestrator
|
|
46
|
+
use_emotion_engine: false # P3-T8 — EmotionEngine for mood → face
|
|
47
|
+
use_thin_shell: false # P3-T9 — slim index.html shell
|
|
48
|
+
|
|
49
|
+
# ── Phase 4: Admin UI ────────────────────────────────────────────────────
|
|
50
|
+
use_session_control_ui: false # P4-T1 — Reset Conversation button
|
|
51
|
+
use_playlist_editor: false # P4-T2 — PlaylistEditor UI
|
|
52
|
+
use_face_picker: false # P4-T3 — face picker gallery
|
|
53
|
+
use_theme_editor: false # P4-T4 — color picker / theme editor
|
|
54
|
+
use_tts_preview: false # P4-T5 — TTS voice preview
|
|
55
|
+
use_profile_switcher_ui: false # P4-T6 — profile switcher UI
|
|
56
|
+
|
|
57
|
+
# ── Phase 5: Agent Profiles & Provider Registry ───────────────────────────
|
|
58
|
+
use_provider_registry: false # P5-T2 — provider auto-discovery
|
|
59
|
+
use_profile_system: false # P5-T3 — JSON profile schema
|
|
60
|
+
use_profile_manager: false # P5-T4 — ProfileManager + API
|
|
61
|
+
use_speech_normalization: false # P5-T5 — config-driven speech normalisation
|
|
62
|
+
|
|
63
|
+
# ── Phase 6: Multi-Agent Framework ───────────────────────────────────────
|
|
64
|
+
use_eventbridge: false # P6-T1 — EventBridge implementation
|
|
65
|
+
use_clawdbot_adapter: false # P6-T2 — ClawdBotAdapter module
|
|
66
|
+
use_hume_adapter: false # P6-T3 — Hume EVI adapter
|
|
67
|
+
use_capability_ui: false # P6-T4 — capability-driven UI shell
|