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>