api cleanup, microphone permission
frdel committed
Jul 15, 2025 at 21:04 UTC
99b4accd7b5e569647f09eb5b4e55efa89c3a55d
35 files changed
+81
-95
python/api/backup_create.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response, send_file
1
+from python.helpers.api import ApiHandler, Request, Response, send_file
2
from python.helpers.backup import BackupService
3
from python.helpers.persist_chat import save_tmp_chats
4
python/api/backup_get_defaults.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers.backup import BackupService
3
4
python/api/backup_inspect.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers.backup import BackupService
3
from werkzeug.datastructures import FileStorage
4
python/api/backup_preview_grouped.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers.backup import BackupService
3
from typing import Dict, Any
4
python/api/backup_restore.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from werkzeug.datastructures import FileStorage
3
from python.helpers.backup import BackupService
4
from python.helpers.persist_chat import load_tmp_chats
python/api/backup_restore_preview.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from werkzeug.datastructures import FileStorage
3
from python.helpers.backup import BackupService
4
import json
python/api/backup_test.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers.backup import BackupService
3
4
python/api/download_work_dir_file.py
+1
-3
@@ -1,9 +1,7 @@
1
import base64
2
from io import BytesIO
3
4
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
5
-from flask import send_file
6
-
4
+from python.helpers.api import ApiHandler, Input, Output, Request, Response, send_file
5
from python.helpers import files, runtime
6
from python.api import file_info
7
import os
python/api/get_work_dir_files.py
+2
-4
@@ -1,8 +1,6 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
3
-
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers.file_browser import FileBrowser
5
-from python.helpers import files, runtime
3
+from python.helpers import runtime
4
5
6
class GetWorkDirFiles(ApiHandler):
python/api/health.py
+2
-5
@@ -1,8 +1,5 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
3
-from python.helpers import errors
4
-
5
-from python.helpers import git
1
+from python.helpers.api import ApiHandler, Request, Response
2
+from python.helpers import errors, git
3
4
class HealthCheck(ApiHandler):
5
python/api/history_get.py
+1
-3
@@ -1,6 +1,4 @@
1
-from python.helpers import tokens
2
-from python.helpers.api import ApiHandler
3
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
4
class GetHistory(ApiHandler):
python/api/image_get.py
+2
-6
@@ -1,11 +1,7 @@
1
import base64
2
import os
3
-import re
4
-from typing import override
5
-from python.helpers.api import ApiHandler
6
-from python.helpers import files
7
-from flask import Request, Response, send_file
8
-from python.helpers import runtime
3
+from python.helpers.api import ApiHandler, Request, Response, send_file
4
+from python.helpers import files, runtime
5
import io
6
from mimetypes import guess_type
7
python/api/import_knowledge.py
+1
-4
@@ -1,7 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
3
-
4
-from python.helpers.file_browser import FileBrowser
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers import files, memory
3
import os
4
from werkzeug.utils import secure_filename
python/api/mcp_server_get_detail.py
+1
-4
@@ -1,7 +1,4 @@
1
-from math import log
2
-from python.helpers.api import ApiHandler
3
-from flask import Request, Response
4
-
1
+from python.helpers.api import ApiHandler, Request, Response
2
from typing import Any
3
4
from python.helpers.mcp_handler import MCPConfig
python/api/mcp_server_get_log.py
+1
-3
@@ -1,6 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
3
-
1
+from python.helpers.api import ApiHandler, Request, Response
2
from typing import Any
3
4
from python.helpers.mcp_handler import MCPConfig
python/api/mcp_servers_apply.py
+1
-2
@@ -1,6 +1,5 @@
1
import time
2
-from python.helpers.api import ApiHandler
3
-from flask import Request, Response
2
+from python.helpers.api import ApiHandler, Request, Response
3
4
from typing import Any
5
python/api/mcp_servers_status.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from typing import Any
4
python/api/message.py
+1
-2
@@ -1,6 +1,5 @@
1
from agent import AgentContext, UserMessage
2
-from python.helpers.api import ApiHandler
3
-from flask import Request, Response
2
+from python.helpers.api import ApiHandler, Request, Response
3
4
from python.helpers import files
5
import os
python/api/message_async.py
-6
@@ -1,10 +1,4 @@
1
from agent import AgentContext
2
-from python.helpers.api import ApiHandler
3
-from flask import Request, Response
4
-
5
-from python.helpers import files
6
-import os
7
-from werkzeug.utils import secure_filename
2
from python.helpers.defer import DeferredTask
3
from python.api.message import Message
4
python/api/nudge.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
class Nudge(ApiHandler):
4
async def process(self, input: dict, request: Request) -> dict | Response:
python/api/pause.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
4
class Pause(ApiHandler):
python/api/poll.py
+1
-4
@@ -1,7 +1,4 @@
1
-import time
2
-from datetime import datetime
3
-from python.helpers.api import ApiHandler
4
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from agent import AgentContext
4
python/api/restart.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from python.helpers import process
4
python/api/rfc.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from python.helpers import runtime
4
python/api/settings_get.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from python.helpers import settings
4
python/api/settings_set.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from python.helpers import settings
4
python/api/synthesize.py
+1
-3
@@ -1,8 +1,6 @@
1
# api/synthesize.py
2
3
-import re
4
-from python.helpers.api import ApiHandler
5
-from flask import Request, Response
3
+from python.helpers.api import ApiHandler, Request, Response
4
5
from python.helpers import runtime, settings, kokoro_tts
6
python/api/transcribe.py
+1
-2
@@ -1,5 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
3
from python.helpers import runtime, settings, whisper
4
python/api/tunnel.py
+1
-2
@@ -1,6 +1,5 @@
1
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers import runtime
3
-from python.helpers.api import ApiHandler
3
from python.helpers.tunnel_manager import TunnelManager
4
5
class Tunnel(ApiHandler):
python/api/tunnel_proxy.py
+1
-2
@@ -1,6 +1,5 @@
1
-from flask import Request, Response
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers import dotenv, runtime
3
-from python.helpers.api import ApiHandler
3
from python.helpers.tunnel_manager import TunnelManager
4
import requests
5
python/api/upload.py
+1
-3
@@ -1,6 +1,4 @@
1
-from python.helpers.api import ApiHandler
2
-from flask import Request, Response
3
-
1
+from python.helpers.api import ApiHandler, Request, Response
2
from python.helpers import files
3
from werkzeug.utils import secure_filename
4
python/api/upload_work_dir_files.py
+1
-3
@@ -1,8 +1,6 @@
1
import base64
2
from werkzeug.datastructures import FileStorage
3
-from python.helpers.api import ApiHandler
4
-from flask import Request, Response, send_file
5
-
3
+from python.helpers.api import ApiHandler, Request, Response
4
from python.helpers.file_browser import FileBrowser
5
from python.helpers import files, runtime
6
from python.api import get_work_dir_files
python/helpers/api.py
+1
-1
@@ -3,7 +3,7 @@ import json
3
import threading
4
from typing import Union, TypedDict, Dict, Any
5
from attr import dataclass
6
-from flask import Request, Response, jsonify, Flask, session, request
6
+from flask import Request, Response, jsonify, Flask, session, request, send_file
7
from agent import AgentContext
8
from initialize import initialize_agent
9
from python.helpers.print_style import PrintStyle
webui/components/settings/speech/microphone-setting-store.js
+38
-3
@@ -24,9 +24,44 @@ const model = {
24
this.selectedDevice = this.devices.length > 0 ? this.devices[0].deviceId : "";
25
},
26
27
- requestPermission() {
28
- navigator.mediaDevices.getUserMedia({ audio: true });
29
- setTimeout(() => this.loadDevices(), 1000);
27
+ // track permission request state
28
+ requestingPermission: false,
29
+ permissionTimer: null,
30
+ permissionAttempts: 0,
31
+
32
+ // request microphone permission and poll for devices
33
+ async requestPermission() {
34
+ // set flag first so UI can update immediately
35
+ clearTimeout(this.permissionTimer);
36
+ this.requestingPermission = true;
37
+ this.permissionAttempts = 0;
38
+
39
+ // request permission in next tick to allow UI to update
40
+ setTimeout(async () => {
41
+ try {
42
+ await navigator.mediaDevices.getUserMedia({ audio: true });
43
+ // start polling for devices
44
+ this.pollForDevices();
45
+ } catch (err) {
46
+ console.error("Microphone permission denied");
47
+ this.requestingPermission = false;
48
+ }
49
+ }, 0);
50
+ },
51
+
52
+ // poll for devices until found or timeout (60s)
53
+ async pollForDevices() {
54
+ await this.loadDevices();
55
+
56
+ // check if we found devices with valid IDs
57
+ if (this.devices.some(d => d.deviceId && d.deviceId !== "") || this.permissionAttempts >= 60) {
58
+ this.requestingPermission = false;
59
+ return;
60
+ }
61
+
62
+ // continue polling
63
+ this.permissionAttempts++;
64
+ this.permissionTimer = setTimeout(() => this.pollForDevices(), 1000);
65
},
66
67
async selectDevice(deviceId) {
webui/components/settings/speech/microphone.html
+8
-2
@@ -25,8 +25,14 @@
25
:selected="option.deviceId === $store.microphoneSetting.selectedDevice"></option>
26
</template>
27
</select>
28
- <button class="btn btn-field" x-show="$store.microphoneSetting.devices.length == 0"
29
- @click="$store.microphoneSetting.requestPermission()">Request permission</button>
28
+ <button class="btn btn-field"
29
+ x-show="$store.microphoneSetting.devices.length == 0 && !$store.microphoneSetting.requestingPermission"
30
+ @click="$store.microphoneSetting.requestPermission()">Request permission to select device</button>
31
+ <button class="btn btn-field"
32
+ x-show="$store.microphoneSetting.requestingPermission"
33
+ @click="$store.microphoneSetting.requestPermission()">
34
+ <span>Waiting for devices... [retry]</span>
35
+ </button>
36
</div>
37
38
</template>