mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
refactor: remove screen awareness and capture surfaces (#5186)
This commit is contained in:
@@ -1 +1 @@
|
||||
f09d7e746a44a5187d2abc116e8a4cd7a9f94a67
|
||||
455b47debb9d1f5d0f1527266e48af1670ad4bc5
|
||||
|
||||
@@ -163,7 +163,7 @@ No `UserProvider`/`AIProvider`/`SkillProvider` — auth lives in `CoreStateProvi
|
||||
|
||||
## Tauri shell (`app/src-tauri/`)
|
||||
|
||||
Thin desktop host. Key modules: `core_process`, `core_rpc`, `cdp`, `cef_preflight`, `cef_profile`, `dictation_hotkeys`, `file_logging`, `mascot_native_window`, `screen_capture`, `window_state`, per-provider scanners (`discord_scanner`, `slack_scanner`, `telegram_scanner`, `whatsapp_scanner`, `wechat_scanner`, `gmessages_scanner`, `imessage_scanner`, `meet_scanner`), `meet_audio`/`meet_call`/`meet_video`, `fake_camera`, `webview_accounts`, `webview_apis`.
|
||||
Thin desktop host. Key modules: `core_process`, `core_rpc`, `cdp`, `cef_preflight`, `cef_profile`, `dictation_hotkeys`, `file_logging`, `mascot_native_window`, `window_state`, per-provider scanners (`discord_scanner`, `slack_scanner`, `telegram_scanner`, `whatsapp_scanner`, `wechat_scanner`, `gmessages_scanner`, `imessage_scanner`, `meet_scanner`), `meet_audio`/`meet_call`/`meet_video`, `fake_camera`, `webview_accounts`, `webview_apis`.
|
||||
|
||||
IPC commands (authoritative list: `generate_handler!` in `app/src-tauri/src/lib.rs`): `core_rpc::relay_http_rpc`, `core_rpc_url`, `core_rpc_token`, `start_core_process`/`restart_core_process`, update commands (`check_app_update`, `apply_core_update`, …), window commands (`activate_main_window`, `mascot_window_*`, `notch_window_*`), `webview_accounts::*`, `workspace_paths::*`, `artifact_commands::*`, hotkeys (dictation/PTT/companion), `meet_call::*`, `native_notifications::*`, `mcp_commands::*`, `loopback_oauth::*`.
|
||||
|
||||
|
||||
-225
@@ -834,70 +834,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Update screen intelligence runtime settings.",
|
||||
"function": "update_screen_intelligence_settings",
|
||||
"inputs": [
|
||||
{
|
||||
"comment": "Enable screen intelligence.",
|
||||
"name": "enabled",
|
||||
"required": false,
|
||||
"ty": { "Option": "Bool" }
|
||||
},
|
||||
{
|
||||
"comment": "Capture policy mode.",
|
||||
"name": "capture_policy",
|
||||
"required": false,
|
||||
"ty": { "Option": "String" }
|
||||
},
|
||||
{
|
||||
"comment": "Policy mode override.",
|
||||
"name": "policy_mode",
|
||||
"required": false,
|
||||
"ty": { "Option": "String" }
|
||||
},
|
||||
{
|
||||
"comment": "Baseline capture FPS.",
|
||||
"name": "baseline_fps",
|
||||
"required": false,
|
||||
"ty": { "Option": "F64" }
|
||||
},
|
||||
{
|
||||
"comment": "Enable vision analysis.",
|
||||
"name": "vision_enabled",
|
||||
"required": false,
|
||||
"ty": { "Option": "Bool" }
|
||||
},
|
||||
{
|
||||
"comment": "Enable autocomplete integration.",
|
||||
"name": "autocomplete_enabled",
|
||||
"required": false,
|
||||
"ty": { "Option": "Bool" }
|
||||
},
|
||||
{
|
||||
"comment": "Allowed app list.",
|
||||
"name": "allowlist",
|
||||
"required": false,
|
||||
"ty": { "Option": { "Array": "String" } }
|
||||
},
|
||||
{
|
||||
"comment": "Denied app list.",
|
||||
"name": "denylist",
|
||||
"required": false,
|
||||
"ty": { "Option": { "Array": "String" } }
|
||||
}
|
||||
],
|
||||
"method": "openhuman.config_update_screen_intelligence_settings",
|
||||
"namespace": "config",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Updated config snapshot.",
|
||||
"name": "snapshot",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Replace tunnel settings with provided config payload.",
|
||||
"function": "update_tunnel_settings",
|
||||
@@ -1246,167 +1182,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Capture screenshot and return image ref.",
|
||||
"function": "capture_image_ref",
|
||||
"inputs": [],
|
||||
"method": "openhuman.screen_intelligence_capture_image_ref",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Capture image_ref payload.",
|
||||
"name": "capture",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Trigger immediate screen capture.",
|
||||
"function": "capture_now",
|
||||
"inputs": [],
|
||||
"method": "openhuman.screen_intelligence_capture_now",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{ "comment": "Capture result payload.", "name": "capture", "required": true, "ty": "Json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Perform input action through accessibility automation.",
|
||||
"function": "input_action",
|
||||
"inputs": [
|
||||
{
|
||||
"comment": "Input action payload.",
|
||||
"name": "action",
|
||||
"required": true,
|
||||
"ty": { "Ref": "InputActionParams" }
|
||||
}
|
||||
],
|
||||
"method": "openhuman.screen_intelligence_input_action",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Input action result payload.",
|
||||
"name": "result",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Request one accessibility permission.",
|
||||
"function": "request_permission",
|
||||
"inputs": [
|
||||
{ "comment": "Permission name.", "name": "permission", "required": true, "ty": "String" }
|
||||
],
|
||||
"method": "openhuman.screen_intelligence_request_permission",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Permission status payload.",
|
||||
"name": "permissions",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Request required accessibility permissions.",
|
||||
"function": "request_permissions",
|
||||
"inputs": [],
|
||||
"method": "openhuman.screen_intelligence_request_permissions",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Permission status payload.",
|
||||
"name": "permissions",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Start screen intelligence session.",
|
||||
"function": "start_session",
|
||||
"inputs": [
|
||||
{
|
||||
"comment": "Capture interval in milliseconds.",
|
||||
"name": "sample_interval_ms",
|
||||
"required": false,
|
||||
"ty": { "Option": "U64" }
|
||||
},
|
||||
{
|
||||
"comment": "Capture policy mode.",
|
||||
"name": "capture_policy",
|
||||
"required": false,
|
||||
"ty": { "Option": "String" }
|
||||
}
|
||||
],
|
||||
"method": "openhuman.screen_intelligence_start_session",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{ "comment": "Session status payload.", "name": "session", "required": true, "ty": "Json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Read screen intelligence accessibility status.",
|
||||
"function": "status",
|
||||
"inputs": [],
|
||||
"method": "openhuman.screen_intelligence_status",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{
|
||||
"comment": "Accessibility status payload.",
|
||||
"name": "status",
|
||||
"required": true,
|
||||
"ty": "Json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Stop screen intelligence session.",
|
||||
"function": "stop_session",
|
||||
"inputs": [
|
||||
{
|
||||
"comment": "Optional stop reason.",
|
||||
"name": "reason",
|
||||
"required": false,
|
||||
"ty": { "Option": "String" }
|
||||
}
|
||||
],
|
||||
"method": "openhuman.screen_intelligence_stop_session",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{ "comment": "Session status payload.", "name": "session", "required": true, "ty": "Json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Flush stored vision summaries.",
|
||||
"function": "vision_flush",
|
||||
"inputs": [],
|
||||
"method": "openhuman.screen_intelligence_vision_flush",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{ "comment": "Vision flush payload.", "name": "result", "required": true, "ty": "Json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Read recent vision summaries.",
|
||||
"function": "vision_recent",
|
||||
"inputs": [
|
||||
{
|
||||
"comment": "Maximum number of summaries.",
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"ty": { "Option": "U64" }
|
||||
}
|
||||
],
|
||||
"method": "openhuman.screen_intelligence_vision_recent",
|
||||
"namespace": "screen_intelligence",
|
||||
"outputs": [
|
||||
{ "comment": "Vision recent payload.", "name": "result", "required": true, "ty": "Json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Manage desktop service lifecycle.",
|
||||
"function": "install",
|
||||
|
||||
@@ -403,7 +403,7 @@ if should_run_suite "settings"; then
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System / AI / voice / screen / Tauri
|
||||
# System / AI / voice / Tauri
|
||||
# linux-cef-deb-runtime.spec.ts is Linux-only (tests /usr/bin path resolution
|
||||
# for .deb package installs) — skipped on macOS/Windows.
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -412,7 +412,6 @@ if should_run_suite "system"; then
|
||||
echo "## Running suite: system"
|
||||
run "test/e2e/specs/local-model-runtime.spec.ts" "local-model" "system"
|
||||
run "test/e2e/specs/voice-mode.spec.ts" "voice-mode" "system"
|
||||
run "test/e2e/specs/screen-intelligence.spec.ts" "screen-intelligence" "system"
|
||||
run "test/e2e/specs/audio-toolkit-flow.spec.ts" "audio-toolkit" "system"
|
||||
run "test/e2e/specs/tauri-commands.spec.ts" "tauri-commands" "system"
|
||||
# service-connectivity-flow tests the old sidecar service model removed in
|
||||
|
||||
@@ -22,14 +22,13 @@
|
||||
(JSON-RPC server, Socket.IO) under the macOS Hardened Runtime. -->
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<!-- Required for the autocomplete focus query and screen-intelligence
|
||||
foreground-context query to send Apple Events to "System Events"
|
||||
under the Hardened Runtime. Without this entitlement, signed DMG
|
||||
builds have AE calls blocked outright before the macOS consent
|
||||
dialog renders, producing the broken-looking error popup tracked
|
||||
in #985. The companion `NSAppleEventsUsageDescription` in
|
||||
`Info.plist` supplies the user-facing text shown in that
|
||||
consent dialog. -->
|
||||
<!-- Required for the autocomplete focus query to send Apple Events to
|
||||
"System Events" under the Hardened Runtime. Without this entitlement,
|
||||
signed DMG builds have AE calls blocked outright before the macOS
|
||||
consent dialog renders, producing the broken-looking error popup
|
||||
tracked in #985. The companion `NSAppleEventsUsageDescription` in
|
||||
`Info.plist` supplies the user-facing text shown in that consent
|
||||
dialog. -->
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
<!-- Required so embedded Chromium can enumerate Bluetooth audio devices
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[permission]]
|
||||
identifier = "allow-core-process"
|
||||
description = "Privileged desktop permissions for core process control, service management, dictation, account webview, window/UI, screen share, notifications, Gmail CDP, log access, Meet, and CEF/profile maintenance"
|
||||
description = "Privileged desktop permissions for core process control, service management, dictation, account webview, window/UI, notifications, Gmail CDP, log access, Meet, and CEF/profile maintenance"
|
||||
|
||||
[permission.commands]
|
||||
|
||||
@@ -120,13 +120,6 @@ allow = [
|
||||
"notch_window_show",
|
||||
"notch_window_hide",
|
||||
|
||||
# =========================
|
||||
# SCREEN SHARE
|
||||
# =========================
|
||||
"screen_share_begin_session",
|
||||
"screen_share_thumbnail",
|
||||
"screen_share_finalize_session",
|
||||
|
||||
# =========================
|
||||
# NOTIFICATIONS
|
||||
# =========================
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
[[permission]]
|
||||
identifier = "allow-webview-recipe"
|
||||
description = "Allow injected per-provider recipe code (running inside the third-party site's origin) to invoke the recipe ingest command back to Rust. Also includes the session-gated screen-share commands (#713 / #812) so the in-page getDisplayMedia shim can open a short-lived enumeration session after a real user gesture. The session gate prevents drive-by window-title / thumbnail exfiltration by third-party scripts running in the same origin."
|
||||
description = "Allow injected per-provider recipe code (running inside the third-party site's origin) to invoke the recipe ingest command back to Rust."
|
||||
|
||||
[permission.commands]
|
||||
allow = [
|
||||
"webview_recipe_event",
|
||||
"screen_share_begin_session",
|
||||
"screen_share_thumbnail",
|
||||
"screen_share_finalize_session",
|
||||
]
|
||||
deny = []
|
||||
|
||||
@@ -531,13 +531,6 @@ async fn run_session_cycle<R: Runtime>(
|
||||
// Without these, Meet sits on the consent dialog forever and cam/mic
|
||||
// never enumerate (verified during #1022 smoke).
|
||||
//
|
||||
// displayCapture is intentionally NOT in this set. Pre-granting it
|
||||
// via `Browser.grantPermissions` bypasses the transient-activation
|
||||
// requirement Chromium enforces on `getDisplayMedia`, which would
|
||||
// let the page initiate a desktop capture without any user gesture.
|
||||
// Without the pre-grant the page's screen-share button triggers
|
||||
// Chrome's native screen-picker on click — same UX, but the gesture
|
||||
// gate stays in place.
|
||||
if origin_host_is(&origin, "meet.google.com") {
|
||||
perms.extend_from_slice(&["audioCapture", "videoCapture", "clipboardReadWrite"]);
|
||||
}
|
||||
@@ -554,11 +547,6 @@ async fn run_session_cycle<R: Runtime>(
|
||||
// tracking issue — granting these perms now means once the paint
|
||||
// bug clears, the huddle is functional immediately rather than
|
||||
// requiring a follow-up perms wire-up.
|
||||
//
|
||||
// displayCapture deliberately omitted for the same reason as Meet:
|
||||
// pre-granting bypasses Chromium's gesture gate on
|
||||
// `getDisplayMedia`; screen-share inside a huddle still works via
|
||||
// the native screen-picker on user click.
|
||||
if origin_host_is(&origin, "app.slack.com") {
|
||||
perms.extend_from_slice(&["audioCapture", "videoCapture", "clipboardReadWrite"]);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
//! Desktop companion — Clicky-style interaction loop (shell-side).
|
||||
//!
|
||||
//! Migrated out of the Rust core (`src/openhuman/desktop_companion`) into the
|
||||
//! Tauri shell. Ties hotkey activation, **native** microphone capture, screen
|
||||
//! context, LLM reasoning, speech synthesis, and visual pointing into one
|
||||
//! product experience:
|
||||
//! Tauri shell. Ties hotkey activation, **native** microphone capture, LLM
|
||||
//! reasoning, and speech synthesis into one product experience:
|
||||
//!
|
||||
//! - `audio` — native `cpal` mic capture (mono i16 PCM ~16 kHz)
|
||||
//! - `session` — session lifecycle + state machine (idle→listening→…→idle)
|
||||
//! - `pipeline` — one interaction turn (STT → LLM → TTS → pointing)
|
||||
//! - `pointing` — `[POINT:x,y:label:screenN]` tag parsing + monitor mapping
|
||||
//! - `pipeline` — one interaction turn (STT → LLM → TTS)
|
||||
//! - `events` — `companion://state_changed` Tauri event delivery
|
||||
//!
|
||||
//! STT/TTS run in the embedded core over JSON-RPC; the LLM turn runs shell-side
|
||||
@@ -18,19 +16,17 @@
|
||||
pub mod audio;
|
||||
pub mod events;
|
||||
pub mod pipeline;
|
||||
pub mod pointing;
|
||||
pub mod session;
|
||||
pub mod types;
|
||||
|
||||
use log::{debug, info, warn};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::AppHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::AppRuntime;
|
||||
use audio::MicCapture;
|
||||
use pointing::ScreenGeometry;
|
||||
use types::*;
|
||||
|
||||
const LOG_PREFIX: &str = "[companion]";
|
||||
@@ -167,12 +163,12 @@ pub fn handle_hotkey_pressed(app: AppHandle<AppRuntime>) {
|
||||
///
|
||||
/// Only push mode consumes releases. Tap mode intentionally waits for the next
|
||||
/// press, preserving toggle behavior.
|
||||
pub fn handle_hotkey_released(app: AppHandle<AppRuntime>) {
|
||||
pub fn handle_hotkey_released(_app: AppHandle<AppRuntime>) {
|
||||
if current_config()
|
||||
.activation_mode
|
||||
.eq_ignore_ascii_case("push")
|
||||
{
|
||||
finish_capture_and_run(app);
|
||||
finish_capture_and_run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +178,7 @@ pub fn handle_hotkey_released(app: AppHandle<AppRuntime>) {
|
||||
/// the interaction turn.
|
||||
pub fn handle_activation(app: AppHandle<AppRuntime>) {
|
||||
if ACTIVE_CAPTURE.lock().is_some() {
|
||||
finish_capture_and_run(app);
|
||||
finish_capture_and_run();
|
||||
} else {
|
||||
start_capture(&app);
|
||||
}
|
||||
@@ -227,7 +223,7 @@ fn start_capture(app: &AppHandle<AppRuntime>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_capture_and_run(app: AppHandle<AppRuntime>) {
|
||||
fn finish_capture_and_run() {
|
||||
let _lifecycle = SESSION_LIFECYCLE.lock();
|
||||
let Some(capture) = ACTIVE_CAPTURE.lock().take() else {
|
||||
debug!("{LOG_PREFIX} capture stop ignored — not listening");
|
||||
@@ -235,10 +231,9 @@ fn finish_capture_and_run(app: AppHandle<AppRuntime>) {
|
||||
};
|
||||
let (samples, rate) = capture.stop();
|
||||
info!("{LOG_PREFIX} captured {} samples @ {rate}Hz", samples.len());
|
||||
let screens = monitors_geometry(&app);
|
||||
let (turn_id, cancel) = register_active_turn();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let result = pipeline::run_audio_turn(&samples, rate, &screens, cancel.clone()).await;
|
||||
let result = pipeline::run_audio_turn(&samples, rate, cancel.clone()).await;
|
||||
if let Err(e) = result {
|
||||
if cancel.is_cancelled() {
|
||||
debug!("{LOG_PREFIX} interrupted companion turn stopped: {e}");
|
||||
@@ -285,34 +280,6 @@ fn clear_active_turn(id: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate connected monitors as [`ScreenGeometry`] for POINT-tag coordinate
|
||||
/// mapping. Best-effort — returns an empty vec if no window/monitor info is
|
||||
/// available (pointing then falls back to raw coordinates).
|
||||
fn monitors_geometry(app: &AppHandle<AppRuntime>) -> Vec<ScreenGeometry> {
|
||||
let Some(window) = app.get_webview_window("main") else {
|
||||
debug!("{LOG_PREFIX} no main window — cannot enumerate monitors");
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(monitors) = window.available_monitors() else {
|
||||
return Vec::new();
|
||||
};
|
||||
monitors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, m)| {
|
||||
let pos = m.position();
|
||||
let size = m.size();
|
||||
ScreenGeometry {
|
||||
index,
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
width: size.width as f64,
|
||||
height: size.height as f64,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod turn_cancellation_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Companion interaction pipeline: STT → screen context → LLM → TTS → pointing.
|
||||
//! Companion interaction pipeline: STT → LLM → TTS.
|
||||
//!
|
||||
//! Orchestrates a single interaction turn for the desktop companion. Migrated
|
||||
//! from the former core `desktop_companion::pipeline`, re-plumbed for the shell:
|
||||
@@ -26,7 +26,6 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::pointing::{self, PointTarget, PointingParseResult, ScreenGeometry};
|
||||
use super::session;
|
||||
use super::types::*;
|
||||
|
||||
@@ -43,10 +42,8 @@ const CONTEXT_WINDOW: usize = 20;
|
||||
pub struct TurnResult {
|
||||
/// The user's transcribed speech (or typed input).
|
||||
pub transcript: String,
|
||||
/// The LLM's response text (POINT tags stripped).
|
||||
/// The LLM's response text.
|
||||
pub response_text: String,
|
||||
/// Parsed pointing targets from the LLM response.
|
||||
pub targets: Vec<PointTarget>,
|
||||
/// Whether TTS audio was synthesized.
|
||||
pub tts_synthesized: bool,
|
||||
/// Whether the turn was cancelled before completion.
|
||||
@@ -58,12 +55,8 @@ pub struct TurnResult {
|
||||
/// **Precondition**: the session must already be in `Listening` state. The
|
||||
/// caller is responsible for the `Idle → Listening` transition before invoking.
|
||||
///
|
||||
/// Transitions: Listening → Thinking → Speaking/Pointing → Idle.
|
||||
pub async fn run_text_turn(
|
||||
text: &str,
|
||||
screens: &[ScreenGeometry],
|
||||
cancel: CancellationToken,
|
||||
) -> Result<TurnResult, String> {
|
||||
/// Transitions: Listening → Thinking → Speaking → Idle.
|
||||
pub async fn run_text_turn(text: &str, cancel: CancellationToken) -> Result<TurnResult, String> {
|
||||
let trimmed = text.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("empty input".into());
|
||||
@@ -82,19 +75,8 @@ pub async fn run_text_turn(
|
||||
let history = session::conversation_history();
|
||||
let history_window = tail_history(&history, CONTEXT_WINDOW);
|
||||
|
||||
// Foreground app/window context (best-effort and user-configurable).
|
||||
let screen_context = if super::current_config().include_app_context {
|
||||
gather_screen_context().await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if cancel.is_cancelled() {
|
||||
return Ok(cancelled_result(trimmed));
|
||||
}
|
||||
|
||||
// LLM call.
|
||||
let raw_reply = match llm_companion(trimmed, &history_window, screen_context.as_deref()).await {
|
||||
let response_text = match llm_companion(trimmed, &history_window).await {
|
||||
Ok(reply) => reply,
|
||||
Err(err) => {
|
||||
if cancel.is_cancelled() {
|
||||
@@ -110,17 +92,7 @@ pub async fn run_text_turn(
|
||||
return Ok(cancelled_result(trimmed));
|
||||
}
|
||||
|
||||
// Parse POINT tags.
|
||||
let PointingParseResult {
|
||||
targets,
|
||||
clean_text,
|
||||
} = pointing::parse_and_map(&raw_reply, screens);
|
||||
|
||||
debug!(
|
||||
"{LOG_PREFIX} LLM reply chars={} targets={}",
|
||||
clean_text.len(),
|
||||
targets.len()
|
||||
);
|
||||
debug!("{LOG_PREFIX} LLM reply chars={}", response_text.len());
|
||||
|
||||
// Record conversation turns.
|
||||
let now_ms = chrono::Utc::now().timestamp_millis();
|
||||
@@ -129,18 +101,18 @@ pub async fn run_text_turn(
|
||||
content: trimmed.to_string(),
|
||||
timestamp_ms: now_ms,
|
||||
});
|
||||
if !clean_text.is_empty() {
|
||||
if !response_text.is_empty() {
|
||||
let _ = session::push_conversation_turn(ConversationTurn {
|
||||
role: "assistant".into(),
|
||||
content: clean_text.clone(),
|
||||
content: response_text.clone(),
|
||||
timestamp_ms: now_ms,
|
||||
});
|
||||
}
|
||||
|
||||
// TTS (skip if response is empty).
|
||||
let tts_ok = if !clean_text.is_empty() && !cancel.is_cancelled() {
|
||||
let tts_ok = if !response_text.is_empty() && !cancel.is_cancelled() {
|
||||
session::transition_state(CompanionState::Speaking, None)?;
|
||||
match tts(&clean_text).await {
|
||||
match tts(&response_text).await {
|
||||
Ok(()) => {
|
||||
debug!("{LOG_PREFIX} TTS synthesized");
|
||||
true
|
||||
@@ -158,18 +130,12 @@ pub async fn run_text_turn(
|
||||
return Ok(cancelled_result(trimmed));
|
||||
}
|
||||
|
||||
// Pointing phase.
|
||||
if !targets.is_empty() {
|
||||
let _ = session::transition_state(CompanionState::Pointing, None);
|
||||
}
|
||||
|
||||
// Back to idle.
|
||||
session::finish_turn();
|
||||
|
||||
let result = TurnResult {
|
||||
transcript: trimmed.to_string(),
|
||||
response_text: clean_text,
|
||||
targets,
|
||||
response_text,
|
||||
tts_synthesized: tts_ok,
|
||||
cancelled: false,
|
||||
};
|
||||
@@ -178,15 +144,14 @@ pub async fn run_text_turn(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Run a full audio-input companion turn: STT → screen context → LLM → TTS → pointing.
|
||||
/// Run a full audio-input companion turn: STT → LLM → TTS.
|
||||
///
|
||||
/// **Precondition**: the session must already be in `Listening` state.
|
||||
///
|
||||
/// Transitions: Listening → Thinking → Speaking/Pointing → Idle.
|
||||
/// Transitions: Listening → Thinking → Speaking → Idle.
|
||||
pub async fn run_audio_turn(
|
||||
audio_samples: &[i16],
|
||||
sample_rate: u32,
|
||||
screens: &[ScreenGeometry],
|
||||
cancel: CancellationToken,
|
||||
) -> Result<TurnResult, String> {
|
||||
if audio_samples.is_empty() {
|
||||
@@ -219,7 +184,6 @@ pub async fn run_audio_turn(
|
||||
return Ok(TurnResult {
|
||||
transcript: String::new(),
|
||||
response_text: String::new(),
|
||||
targets: Vec::new(),
|
||||
tts_synthesized: false,
|
||||
cancelled: false,
|
||||
});
|
||||
@@ -238,7 +202,7 @@ pub async fn run_audio_turn(
|
||||
debug!("{LOG_PREFIX} STT transcript chars={}", transcript.len());
|
||||
|
||||
// Hand off to the text pipeline for the rest.
|
||||
run_text_turn(&transcript, screens, cancel).await
|
||||
run_text_turn(&transcript, cancel).await
|
||||
}
|
||||
|
||||
// ─── Core-RPC / backend adapters ────────────────────────────────────
|
||||
@@ -319,41 +283,25 @@ async fn fetch_backend_creds() -> Result<(String, String), String> {
|
||||
/// System prompt for the desktop companion LLM.
|
||||
const COMPANION_SYSTEM_PROMPT: &str = "\
|
||||
You are OpenHuman, a helpful desktop AI companion. The user is talking to you \
|
||||
via voice or text while using their computer. You can see their screen \
|
||||
(a screenshot and foreground app info may be provided).\n\
|
||||
via voice or text while using their computer.\n\
|
||||
\n\
|
||||
Guidelines:\n\
|
||||
- Be concise and conversational. 1-3 sentences unless the question demands more.\n\
|
||||
- When you want to point the user to a UI element on screen, embed a \
|
||||
`[POINT:x,y:label:screenN]` tag in your response where x,y are pixel \
|
||||
coordinates relative to the screen, label describes the element, and N is \
|
||||
the zero-based screen index.\n\
|
||||
- Do not use markdown formatting (no asterisks, backticks, or bullet markers) — \
|
||||
your response will be spoken aloud via TTS.\n\
|
||||
- If screen context is provided, reference what you see when relevant.\n\
|
||||
- If you don't know or can't help, say so briefly.\n\
|
||||
";
|
||||
|
||||
/// Build a chat-completions request with screen context and conversation
|
||||
/// history, then return the assistant's reply. Runs shell-side against the
|
||||
/// Build a chat-completions request with conversation history, then return the
|
||||
/// assistant's reply. Runs shell-side against the
|
||||
/// backend using creds fetched from core.
|
||||
async fn llm_companion(
|
||||
prompt: &str,
|
||||
history: &[&ConversationTurn],
|
||||
screen_context: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
async fn llm_companion(prompt: &str, history: &[&ConversationTurn]) -> Result<String, String> {
|
||||
let (token, backend_url) = fetch_backend_creds().await?;
|
||||
let endpoint = companion_chat_endpoint(&backend_url);
|
||||
|
||||
let mut messages: Vec<Value> = Vec::with_capacity(history.len() + 2);
|
||||
|
||||
// System prompt with optional screen context.
|
||||
let system = if let Some(ctx) = screen_context {
|
||||
format!("{COMPANION_SYSTEM_PROMPT}\nCurrent screen context:\n{ctx}")
|
||||
} else {
|
||||
COMPANION_SYSTEM_PROMPT.to_string()
|
||||
};
|
||||
messages.push(json!({ "role": "system", "content": system }));
|
||||
messages.push(json!({ "role": "system", "content": COMPANION_SYSTEM_PROMPT }));
|
||||
|
||||
// Rolling conversation history.
|
||||
for turn in history {
|
||||
@@ -405,33 +353,6 @@ fn companion_chat_endpoint(backend_url: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Gather foreground app/window context as a text summary for the LLM.
|
||||
///
|
||||
/// The accessibility backbone remains in the embedded core after desktop
|
||||
/// controls moved out, so the shell can reuse its public, read-only foreground
|
||||
/// query without restoring any automation commands.
|
||||
async fn gather_screen_context() -> Option<String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
openhuman_core::openhuman::accessibility::foreground_context().map(|ctx| {
|
||||
format_foreground_context(ctx.app_name.as_deref(), ctx.window_title.as_deref())
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn format_foreground_context(app_name: Option<&str>, window_title: Option<&str>) -> String {
|
||||
format!(
|
||||
"App: {} | Window: {}",
|
||||
app_name.unwrap_or("unknown"),
|
||||
window_title.unwrap_or("unknown"),
|
||||
)
|
||||
}
|
||||
|
||||
fn extract_chat_completion_text(raw: &Value) -> Option<String> {
|
||||
raw.get("choices")
|
||||
.and_then(|c| c.as_array())
|
||||
@@ -455,7 +376,6 @@ fn cancelled_result(transcript: &str) -> TurnResult {
|
||||
TurnResult {
|
||||
transcript: transcript.to_string(),
|
||||
response_text: String::new(),
|
||||
targets: Vec::new(),
|
||||
tts_synthesized: false,
|
||||
cancelled: true,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! Tests for the companion interaction pipeline.
|
||||
//!
|
||||
//! These tests exercise the pipeline's orchestration logic — state
|
||||
//! transitions, cancellation, conversation history, and POINT-tag
|
||||
//! integration. Real STT/LLM/TTS calls are not made; the pipeline's
|
||||
//! transitions, cancellation, and conversation history. Real STT/LLM/TTS
|
||||
//! calls are not made; the pipeline's
|
||||
//! network calls fail fast in a test environment (no embedded core).
|
||||
|
||||
use super::*;
|
||||
use crate::companion::pointing::ScreenGeometry;
|
||||
use crate::companion::session;
|
||||
|
||||
/// Serialize tests that touch the process-global session state. Shared with
|
||||
@@ -23,16 +22,6 @@ fn lock_and_reset() -> std::sync::MutexGuard<'static, ()> {
|
||||
guard
|
||||
}
|
||||
|
||||
fn single_screen() -> Vec<ScreenGeometry> {
|
||||
vec![ScreenGeometry {
|
||||
index: 0,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 1920.0,
|
||||
height: 1080.0,
|
||||
}]
|
||||
}
|
||||
|
||||
// ── Helper tests ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
@@ -73,7 +62,6 @@ fn cancelled_result_has_correct_fields() {
|
||||
let r = cancelled_result("hello");
|
||||
assert_eq!(r.transcript, "hello");
|
||||
assert!(r.response_text.is_empty());
|
||||
assert!(r.targets.is_empty());
|
||||
assert!(!r.tts_synthesized);
|
||||
assert!(r.cancelled);
|
||||
}
|
||||
@@ -134,7 +122,7 @@ fn companion_chat_endpoint_joins_only_the_backend_path() {
|
||||
async fn text_turn_rejects_empty_input() {
|
||||
let _guard = lock_and_reset();
|
||||
let cancel = CancellationToken::new();
|
||||
let result = run_text_turn("", &single_screen(), cancel).await;
|
||||
let result = run_text_turn("", cancel).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("empty"));
|
||||
session::reset_for_test();
|
||||
@@ -144,7 +132,7 @@ async fn text_turn_rejects_empty_input() {
|
||||
async fn text_turn_rejects_whitespace_only() {
|
||||
let _guard = lock_and_reset();
|
||||
let cancel = CancellationToken::new();
|
||||
let result = run_text_turn(" \n ", &single_screen(), cancel).await;
|
||||
let result = run_text_turn(" \n ", cancel).await;
|
||||
assert!(result.is_err());
|
||||
session::reset_for_test();
|
||||
}
|
||||
@@ -156,7 +144,7 @@ async fn text_turn_cancellation_returns_cancelled() {
|
||||
cancel.cancel();
|
||||
// Transition to Listening first so Thinking is a valid transition.
|
||||
session::transition_state(CompanionState::Listening, None).unwrap();
|
||||
let result = run_text_turn("hello", &single_screen(), cancel).await;
|
||||
let result = run_text_turn("hello", cancel).await;
|
||||
let turn = result.unwrap();
|
||||
assert!(turn.cancelled);
|
||||
assert!(turn.response_text.is_empty());
|
||||
@@ -169,7 +157,7 @@ async fn text_turn_cancellation_returns_cancelled() {
|
||||
async fn audio_turn_rejects_empty_samples() {
|
||||
let _guard = lock_and_reset();
|
||||
let cancel = CancellationToken::new();
|
||||
let result = run_audio_turn(&[], 16_000, &single_screen(), cancel).await;
|
||||
let result = run_audio_turn(&[], 16_000, cancel).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("no audio"));
|
||||
let status = session::session_status();
|
||||
@@ -178,33 +166,12 @@ async fn audio_turn_rejects_empty_samples() {
|
||||
session::reset_for_test();
|
||||
}
|
||||
|
||||
// ── Screen context ───────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn gather_screen_context_returns_option() {
|
||||
let ctx = gather_screen_context().await;
|
||||
// Just verify it doesn't panic — value depends on platform/capability.
|
||||
let _ = ctx;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreground_context_summary_preserves_app_and_window() {
|
||||
assert_eq!(
|
||||
format_foreground_context(Some("Safari"), Some("OpenHuman")),
|
||||
"App: Safari | Window: OpenHuman"
|
||||
);
|
||||
assert_eq!(
|
||||
format_foreground_context(None, None),
|
||||
"App: unknown | Window: unknown"
|
||||
);
|
||||
}
|
||||
|
||||
// ── System prompt ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn companion_system_prompt_mentions_point_tags() {
|
||||
assert!(COMPANION_SYSTEM_PROMPT.contains("[POINT:"));
|
||||
assert!(COMPANION_SYSTEM_PROMPT.contains("screenN"));
|
||||
fn companion_system_prompt_does_not_request_screen_context_or_point_tags() {
|
||||
assert!(!COMPANION_SYSTEM_PROMPT.contains("screen context"));
|
||||
assert!(!COMPANION_SYSTEM_PROMPT.contains("[POINT:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
//! POINT tag parser and multi-monitor coordinate mapping.
|
||||
//!
|
||||
//! The companion LLM embeds `[POINT:x,y:label:screenN]` tags in its
|
||||
//! response text (Clicky convention). This module extracts those tags,
|
||||
//! maps screen-relative coordinates to absolute desktop coordinates
|
||||
//! using monitor geometry, and strips the tags from the display text.
|
||||
//!
|
||||
//! Migrated verbatim from the former core `desktop_companion::pointing`
|
||||
//! (the parsing logic has no core dependencies — it is pure regex + math).
|
||||
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const LOG_PREFIX: &str = "[companion_pointing]";
|
||||
|
||||
/// A parsed point target from the LLM response.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PointTarget {
|
||||
/// Screen-relative X coordinate (as emitted by the LLM).
|
||||
pub x: f64,
|
||||
/// Screen-relative Y coordinate.
|
||||
pub y: f64,
|
||||
/// Human-readable label for the target element.
|
||||
pub label: String,
|
||||
/// Zero-based screen index.
|
||||
pub screen_index: usize,
|
||||
/// Absolute desktop X after multi-monitor mapping.
|
||||
pub absolute_x: f64,
|
||||
/// Absolute desktop Y after multi-monitor mapping.
|
||||
pub absolute_y: f64,
|
||||
}
|
||||
|
||||
/// Monitor geometry used for coordinate mapping.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ScreenGeometry {
|
||||
/// Zero-based index.
|
||||
pub index: usize,
|
||||
/// Left edge in absolute desktop coordinates.
|
||||
pub x: f64,
|
||||
/// Top edge in absolute desktop coordinates.
|
||||
pub y: f64,
|
||||
/// Width in points.
|
||||
pub width: f64,
|
||||
/// Height in points.
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
/// Result of parsing POINT tags from an LLM response.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PointingParseResult {
|
||||
/// Extracted point targets with mapped coordinates.
|
||||
pub targets: Vec<PointTarget>,
|
||||
/// The response text with POINT tags stripped out.
|
||||
pub clean_text: String,
|
||||
}
|
||||
|
||||
/// Lazily compiled POINT-tag regex.
|
||||
fn point_tag_regex() -> &'static regex::Regex {
|
||||
static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
|
||||
RE.get_or_init(|| {
|
||||
regex::Regex::new(r"\[POINT:(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?):([^:\]]+):screen(\d+)\]")
|
||||
.expect("companion POINT tag regex is static and valid")
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse `[POINT:x,y:label:screenN]` tags from LLM response text and map
|
||||
/// coordinates to absolute desktop positions using the given screen geometry.
|
||||
pub fn parse_and_map(text: &str, screens: &[ScreenGeometry]) -> PointingParseResult {
|
||||
let re = point_tag_regex();
|
||||
|
||||
let mut targets = Vec::new();
|
||||
let clean_text = re
|
||||
.replace_all(text, |caps: ®ex::Captures| {
|
||||
let x: f64 = caps[1].parse().unwrap_or(0.0);
|
||||
let y: f64 = caps[2].parse().unwrap_or(0.0);
|
||||
let label = caps[3].trim().to_string();
|
||||
let screen_index: usize = caps[4].parse().unwrap_or(0);
|
||||
|
||||
let (abs_x, abs_y) = map_to_absolute(x, y, screen_index, screens);
|
||||
|
||||
debug!(
|
||||
"{LOG_PREFIX} parsed target: ({x},{y}) label=\"{label}\" screen{screen_index} -> abs({abs_x},{abs_y})"
|
||||
);
|
||||
|
||||
targets.push(PointTarget {
|
||||
x,
|
||||
y,
|
||||
label,
|
||||
screen_index,
|
||||
absolute_x: abs_x,
|
||||
absolute_y: abs_y,
|
||||
});
|
||||
|
||||
// Replace the tag with empty string in the display text.
|
||||
String::new()
|
||||
})
|
||||
.to_string();
|
||||
|
||||
PointingParseResult {
|
||||
targets,
|
||||
clean_text: clean_text.trim().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Map screen-relative coordinates to absolute desktop coordinates.
|
||||
///
|
||||
/// If the screen index is out of range, falls back to screen 0 (primary).
|
||||
/// Coordinates are clamped to screen bounds.
|
||||
fn map_to_absolute(x: f64, y: f64, screen_index: usize, screens: &[ScreenGeometry]) -> (f64, f64) {
|
||||
if screens.is_empty() {
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
let screen = screens
|
||||
.iter()
|
||||
.find(|s| s.index == screen_index)
|
||||
.or_else(|| screens.first())
|
||||
.unwrap();
|
||||
|
||||
let clamped_x = x.clamp(0.0, screen.width);
|
||||
let clamped_y = y.clamp(0.0, screen.height);
|
||||
|
||||
(screen.x + clamped_x, screen.y + clamped_y)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "pointing_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,170 +0,0 @@
|
||||
//! Tests for POINT tag parsing and coordinate mapping.
|
||||
|
||||
use super::*;
|
||||
|
||||
fn single_screen() -> Vec<ScreenGeometry> {
|
||||
vec![ScreenGeometry {
|
||||
index: 0,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 1920.0,
|
||||
height: 1080.0,
|
||||
}]
|
||||
}
|
||||
|
||||
fn dual_screens() -> Vec<ScreenGeometry> {
|
||||
vec![
|
||||
ScreenGeometry {
|
||||
index: 0,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 1920.0,
|
||||
height: 1080.0,
|
||||
},
|
||||
ScreenGeometry {
|
||||
index: 1,
|
||||
x: 1920.0,
|
||||
y: 0.0,
|
||||
width: 2560.0,
|
||||
height: 1440.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// ── Basic parsing ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn parse_single_point_tag() {
|
||||
let text = "Click the button [POINT:100,200:Save Button:screen0] to save.";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.targets.len(), 1);
|
||||
assert_eq!(result.targets[0].x, 100.0);
|
||||
assert_eq!(result.targets[0].y, 200.0);
|
||||
assert_eq!(result.targets[0].label, "Save Button");
|
||||
assert_eq!(result.targets[0].screen_index, 0);
|
||||
assert_eq!(result.clean_text, "Click the button to save.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multiple_point_tags() {
|
||||
let text = "First [POINT:10,20:A:screen0] then [POINT:30,40:B:screen1] done.";
|
||||
let result = parse_and_map(text, &dual_screens());
|
||||
|
||||
assert_eq!(result.targets.len(), 2);
|
||||
assert_eq!(result.targets[0].label, "A");
|
||||
assert_eq!(result.targets[1].label, "B");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_point_tags() {
|
||||
let text = "No pointing needed here.";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert!(result.targets.is_empty());
|
||||
assert_eq!(result.clean_text, "No pointing needed here.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_decimal_coordinates() {
|
||||
let text = "[POINT:100.5,200.75:Pin:screen0]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.targets[0].x, 100.5);
|
||||
assert_eq!(result.targets[0].y, 200.75);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_negative_coordinates_clamped() {
|
||||
let text = "[POINT:-50,-100:Off-screen:screen0]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.targets[0].absolute_x, 0.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 0.0);
|
||||
}
|
||||
|
||||
// ── Multi-monitor mapping ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn map_to_primary_screen() {
|
||||
let text = "[POINT:500,300:Target:screen0]";
|
||||
let result = parse_and_map(text, &dual_screens());
|
||||
|
||||
assert_eq!(result.targets[0].absolute_x, 500.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 300.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_to_secondary_screen() {
|
||||
let text = "[POINT:500,300:Target:screen1]";
|
||||
let result = parse_and_map(text, &dual_screens());
|
||||
|
||||
// screen1 starts at x=1920
|
||||
assert_eq!(result.targets[0].absolute_x, 2420.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 300.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn screen_index_out_of_range_falls_back_to_primary() {
|
||||
let text = "[POINT:100,200:Target:screen5]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
// Falls back to screen 0
|
||||
assert_eq!(result.targets[0].absolute_x, 100.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 200.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coordinates_clamped_to_screen_bounds() {
|
||||
let text = "[POINT:5000,3000:Far:screen0]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.targets[0].absolute_x, 1920.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 1080.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_screens_returns_raw_coordinates() {
|
||||
let text = "[POINT:100,200:Target:screen0]";
|
||||
let result = parse_and_map(text, &[]);
|
||||
|
||||
assert_eq!(result.targets[0].absolute_x, 100.0);
|
||||
assert_eq!(result.targets[0].absolute_y, 200.0);
|
||||
}
|
||||
|
||||
// ── Malformed tags ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn malformed_tag_not_parsed() {
|
||||
let text = "[POINT:abc,def:Bad:screen0] and [POINT:100:Missing:screen0]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
// Neither matches the regex
|
||||
assert!(result.targets.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_tag_not_parsed() {
|
||||
let text = "[POINT:100,200:No Screen] and POINT:100,200:bare:screen0]";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert!(result.targets.is_empty());
|
||||
}
|
||||
|
||||
// ── Clean text ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn clean_text_strips_all_tags() {
|
||||
let text = "Start [POINT:0,0:A:screen0] middle [POINT:0,0:B:screen0] end";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.clean_text, "Start middle end");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_text_trims_whitespace() {
|
||||
let text = " [POINT:0,0:A:screen0] ";
|
||||
let result = parse_and_map(text, &single_screen());
|
||||
|
||||
assert_eq!(result.clean_text, "");
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! A companion session represents a single period of desktop companion
|
||||
//! activity. It owns the state machine (idle → listening → thinking →
|
||||
//! speaking → pointing → idle), TTL enforcement, and conversation history.
|
||||
//! speaking → idle), TTL enforcement, and conversation history.
|
||||
//!
|
||||
//! Only one session may be active at a time. Sessions are created with
|
||||
//! explicit user consent and can be stopped manually or via TTL expiry.
|
||||
@@ -345,13 +345,9 @@ pub fn conversation_history() -> Vec<ConversationTurn> {
|
||||
/// - Listening → Thinking (transcript received)
|
||||
/// - Listening → Idle (cancelled / released)
|
||||
/// - Thinking → Speaking (response ready)
|
||||
/// - Thinking → Pointing (response has point targets, no TTS)
|
||||
/// - Thinking → Idle (cancelled)
|
||||
/// - Speaking → Pointing (TTS done, point targets present)
|
||||
/// - Speaking → Idle (TTS done, no pointing)
|
||||
/// - Speaking → Idle (TTS done)
|
||||
/// - Speaking → Listening (interrupted — new turn)
|
||||
/// - Pointing → Idle (animation done)
|
||||
/// - Pointing → Listening (interrupted — new turn)
|
||||
/// - Error → Idle (reset)
|
||||
/// - Any → Error (error from any state)
|
||||
fn is_valid_transition(from: CompanionState, to: CompanionState) -> bool {
|
||||
@@ -366,13 +362,9 @@ fn is_valid_transition(from: CompanionState, to: CompanionState) -> bool {
|
||||
| (CompanionState::Listening, CompanionState::Thinking)
|
||||
| (CompanionState::Listening, CompanionState::Idle)
|
||||
| (CompanionState::Thinking, CompanionState::Speaking)
|
||||
| (CompanionState::Thinking, CompanionState::Pointing)
|
||||
| (CompanionState::Thinking, CompanionState::Idle)
|
||||
| (CompanionState::Speaking, CompanionState::Pointing)
|
||||
| (CompanionState::Speaking, CompanionState::Idle)
|
||||
| (CompanionState::Speaking, CompanionState::Listening)
|
||||
| (CompanionState::Pointing, CompanionState::Idle)
|
||||
| (CompanionState::Pointing, CompanionState::Listening)
|
||||
| (CompanionState::Error, CompanionState::Idle)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -182,30 +182,6 @@ fn transition_thinking_to_speaking() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transition_speaking_to_pointing() {
|
||||
with_clean_session(|| {
|
||||
let _s = start_default_session();
|
||||
transition_state(CompanionState::Listening, None).unwrap();
|
||||
transition_state(CompanionState::Thinking, None).unwrap();
|
||||
transition_state(CompanionState::Speaking, None).unwrap();
|
||||
let prev = transition_state(CompanionState::Pointing, None).unwrap();
|
||||
assert_eq!(prev, CompanionState::Speaking);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transition_pointing_to_idle() {
|
||||
with_clean_session(|| {
|
||||
let _s = start_default_session();
|
||||
transition_state(CompanionState::Listening, None).unwrap();
|
||||
transition_state(CompanionState::Thinking, None).unwrap();
|
||||
transition_state(CompanionState::Pointing, None).unwrap();
|
||||
let prev = transition_state(CompanionState::Idle, None).unwrap();
|
||||
assert_eq!(prev, CompanionState::Pointing);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transition_full_happy_path() {
|
||||
with_clean_session(|| {
|
||||
@@ -213,7 +189,6 @@ fn transition_full_happy_path() {
|
||||
transition_state(CompanionState::Listening, None).unwrap();
|
||||
transition_state(CompanionState::Thinking, None).unwrap();
|
||||
transition_state(CompanionState::Speaking, None).unwrap();
|
||||
transition_state(CompanionState::Pointing, None).unwrap();
|
||||
transition_state(CompanionState::Idle, None).unwrap();
|
||||
assert_eq!(session_status().state, CompanionState::Idle);
|
||||
});
|
||||
|
||||
@@ -17,12 +17,10 @@ pub enum CompanionState {
|
||||
Idle,
|
||||
/// Microphone is live — capturing user speech.
|
||||
Listening,
|
||||
/// Transcript + screen context sent to LLM; awaiting response.
|
||||
/// Transcript sent to LLM; awaiting response.
|
||||
Thinking,
|
||||
/// TTS is playing the response audio.
|
||||
Speaking,
|
||||
/// Visual pointer is animating toward a UI target.
|
||||
Pointing,
|
||||
/// An unrecoverable error occurred in the current turn.
|
||||
Error,
|
||||
}
|
||||
@@ -34,7 +32,6 @@ impl std::fmt::Display for CompanionState {
|
||||
Self::Listening => write!(f, "listening"),
|
||||
Self::Thinking => write!(f, "thinking"),
|
||||
Self::Speaking => write!(f, "speaking"),
|
||||
Self::Pointing => write!(f, "pointing"),
|
||||
Self::Error => write!(f, "error"),
|
||||
}
|
||||
}
|
||||
@@ -63,12 +60,6 @@ pub struct CompanionConfig {
|
||||
/// Session TTL in seconds. `0` means no automatic expiry.
|
||||
#[serde(default = "default_ttl_secs")]
|
||||
pub ttl_secs: u64,
|
||||
/// Whether to capture a screenshot on each activation.
|
||||
#[serde(default = "default_true")]
|
||||
pub capture_screen: bool,
|
||||
/// Whether to include the foreground app context.
|
||||
#[serde(default = "default_true")]
|
||||
pub include_app_context: bool,
|
||||
}
|
||||
|
||||
impl Default for CompanionConfig {
|
||||
@@ -77,8 +68,6 @@ impl Default for CompanionConfig {
|
||||
hotkey: default_hotkey(),
|
||||
activation_mode: default_activation_mode(),
|
||||
ttl_secs: default_ttl_secs(),
|
||||
capture_screen: true,
|
||||
include_app_context: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,14 +81,11 @@ fn default_activation_mode() -> String {
|
||||
fn default_ttl_secs() -> u64 {
|
||||
3600
|
||||
}
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Parameters for starting a companion session.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StartCompanionSessionParams {
|
||||
/// Explicit user consent to screen monitoring and audio capture.
|
||||
/// Explicit user consent to audio capture.
|
||||
pub consent: bool,
|
||||
/// Optional TTL override in seconds.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -94,7 +94,6 @@ mod ptt_hotkeys;
|
||||
mod ptt_overlay;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod reset_reboot_schedule;
|
||||
mod screen_capture;
|
||||
mod slack_scanner;
|
||||
mod stderr_panic_hook;
|
||||
mod telegram_scanner;
|
||||
@@ -3129,7 +3128,6 @@ pub fn run() {
|
||||
let builder = builder.manage(discord_scanner::ScannerRegistry::new());
|
||||
let builder = builder.manage(telegram_scanner::ScannerRegistry::new());
|
||||
let builder = builder.manage(wechat_scanner::ScannerRegistry::new());
|
||||
let builder = builder.manage(screen_capture::ScreenShareState::new());
|
||||
let builder = builder.manage(meet_call::MeetCallState::new());
|
||||
let builder = builder.manage(meet_audio::MeetAudioState::new());
|
||||
let builder = builder.manage(meet_video::frame_bus::MeetVideoFrameBusState::new());
|
||||
@@ -3874,9 +3872,6 @@ pub fn run() {
|
||||
webview_accounts::webview_set_focused_account,
|
||||
notification_settings::notification_settings_get,
|
||||
notification_settings::notification_settings_set,
|
||||
screen_capture::screen_share_begin_session,
|
||||
screen_capture::screen_share_thumbnail,
|
||||
screen_capture::screen_share_finalize_session,
|
||||
native_notifications::notification_permission_state,
|
||||
native_notifications::notification_permission_request,
|
||||
activate_main_window,
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
// `__openhumanCaptionsBridgeInfo()` for the Tauri shell to drive
|
||||
// over CDP `Runtime.evaluate`.
|
||||
//
|
||||
// Why scraping (and not getDisplayMedia, or Web Speech, or Meet's
|
||||
// undocumented APIs)?
|
||||
// - getDisplayMedia would prompt the user for screen-share permission.
|
||||
// Why scraping (and not Web Speech or Meet's undocumented APIs)?
|
||||
// - Web Speech doesn't reach the remote participants' audio — only
|
||||
// local mic.
|
||||
// - Meet has no public caption API.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -96,676 +96,60 @@
|
||||
window.__openhumanRecipe = api;
|
||||
send('log', { level: 'info', msg: '[recipe-runtime] ready provider=' + ctx.provider + ' accountId=' + ctx.accountId });
|
||||
|
||||
// --- #713 getDisplayMedia shim ---
|
||||
//
|
||||
// Background: embedded webviews run under CEF Alloy, which does not link
|
||||
// Chromium's DesktopMediaPicker. Without an interceptor, `getDisplayMedia`
|
||||
// gets auto-granted by our permission handler and Chromium silently picks
|
||||
// the primary display (issue #713 AC2: "OS screen/window picker appears").
|
||||
//
|
||||
// The picker UI is injected DIRECTLY into the child webview's own DOM
|
||||
// rather than rendered as a React modal in the main OpenHuman window.
|
||||
// Two reasons:
|
||||
// (a) Works uniformly for every embedded provider — Meet, Slack
|
||||
// Huddles, Discord, Zoom — without per-provider host-side glue.
|
||||
// (b) Dodges the CEF native-view stacking problem: a React modal in
|
||||
// the main window is always occluded by the child webview's
|
||||
// NSView, forcing a hide/bounds dance that flickers the embedded
|
||||
// site. An overlay inside the page is stacked in the page's own
|
||||
// compositing context, so it sits above Meet/Slack UI naturally.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Shim calls Tauri `screen_share_list_sources` to enumerate real
|
||||
// screens (`screen:<CGDirectDisplayID>:0`) and windows
|
||||
// (`window:<CGWindowID>:0`) natively.
|
||||
// 2. Shim builds a fixed-position picker overlay inside the page's
|
||||
// document and awaits the user's choice.
|
||||
// 3. On Share, shim calls `getUserMedia` with a hand-crafted
|
||||
// `chromeMediaSource: 'desktop' + chromeMediaSourceId` constraint.
|
||||
// Stage 0 PoC proved Chromium honours the ID directly because our
|
||||
// CEF permission callback grants `DESKTOP_VIDEO_CAPTURE` bits.
|
||||
// 4. On Cancel, shim throws `NotAllowedError` — same shape the real
|
||||
// Chromium picker emits so page error handling is unchanged.
|
||||
(function installGetDisplayMediaShim() {
|
||||
if (!navigator.mediaDevices || typeof navigator.mediaDevices.getDisplayMedia !== 'function') {
|
||||
// Never had getDisplayMedia to begin with (non-WebRTC webview); skip.
|
||||
return;
|
||||
}
|
||||
if (navigator.mediaDevices.__ohGdmShimInstalled) return;
|
||||
|
||||
// `navigator.mediaDevices.getDisplayMedia` is a WebIDL-defined prototype
|
||||
// method on `MediaDevices.prototype`. Chromium marks it
|
||||
// `writable: true, configurable: true` but *only* on the prototype —
|
||||
// plain `navigator.mediaDevices.getDisplayMedia = ...` on the instance
|
||||
// creates an own-property shadow that Chromium's IDL bindings bypass
|
||||
// when the page actually invokes the method. We override on the
|
||||
// prototype with `defineProperty` so the shim is what runs for every
|
||||
// MediaDevices instance in this page (including any iframes that
|
||||
// inherit from the same prototype).
|
||||
const proto = Object.getPrototypeOf(navigator.mediaDevices);
|
||||
const descriptor = Object.getOwnPropertyDescriptor(proto, 'getDisplayMedia');
|
||||
const origGetDisplayMedia = (descriptor && descriptor.value
|
||||
? descriptor.value
|
||||
: navigator.mediaDevices.getDisplayMedia
|
||||
).bind(navigator.mediaDevices);
|
||||
|
||||
// Fire-and-forget session cleanup. Swallows errors because finalize
|
||||
// is a no-op on the host side for unknown/expired tokens and we don't
|
||||
// want a late IPC failure to leak into the getDisplayMedia rejection.
|
||||
function finalizeSessionQuiet(token, pickedId) {
|
||||
if (!token) return Promise.resolve();
|
||||
return rawInvoke('screen_share_finalize_session', {
|
||||
args: { token: token, pickedId: pickedId || null },
|
||||
}).catch(function () {});
|
||||
}
|
||||
|
||||
// In-flight guard (graycyrus refactor #6). The host-side state already
|
||||
// evicts a stale session when begin_session fires twice, but without a
|
||||
// shim-side guard a second call would still append a second picker DOM
|
||||
// while the first is open — the user would see two stacked overlays.
|
||||
// Reject a concurrent call the same way the MediaStreams spec does
|
||||
// when an existing capture request is in progress.
|
||||
let pickerInFlight = false;
|
||||
|
||||
const shim = async function (constraints) {
|
||||
constraints = constraints || {};
|
||||
if (pickerInFlight) {
|
||||
send('log', { level: 'warn', msg: '[gdm-shim] picker already open, rejecting concurrent call' });
|
||||
throw new DOMException(
|
||||
'A screen-share picker is already open',
|
||||
'InvalidStateError'
|
||||
);
|
||||
}
|
||||
pickerInFlight = true;
|
||||
try {
|
||||
return await runShim(constraints);
|
||||
} finally {
|
||||
pickerInFlight = false;
|
||||
}
|
||||
};
|
||||
|
||||
const runShim = async function (constraints) {
|
||||
constraints = constraints || {};
|
||||
// User-activation gate (#812). `navigator.userActivation.isActive`
|
||||
// is transient — true only during the direct call stack of a real
|
||||
// gesture handler (click, key, touch). Third-party JS calling
|
||||
// getDisplayMedia from a timer or async continuation gets filtered
|
||||
// here, so our downstream commands (begin_session etc.) never open
|
||||
// a session without a gesture. Fall through to the original
|
||||
// implementation rather than throw so pages with legitimate
|
||||
// non-gesture flows (rare but possible) aren't hard-blocked.
|
||||
const hasActivation = !!(
|
||||
typeof navigator !== 'undefined' &&
|
||||
navigator.userActivation &&
|
||||
navigator.userActivation.isActive
|
||||
);
|
||||
send('log', {
|
||||
level: 'info',
|
||||
msg:
|
||||
'[gdm-shim] getDisplayMedia intercepted audio=' +
|
||||
!!constraints.audio +
|
||||
' activation=' +
|
||||
hasActivation,
|
||||
});
|
||||
if (!hasActivation) {
|
||||
send('log', {
|
||||
level: 'warn',
|
||||
msg: '[gdm-shim] no user activation, falling through to native getDisplayMedia',
|
||||
});
|
||||
return origGetDisplayMedia(constraints);
|
||||
}
|
||||
|
||||
let session;
|
||||
try {
|
||||
session = await rawInvoke('screen_share_begin_session', {
|
||||
args: {
|
||||
accountId: ctx.accountId,
|
||||
origin: (typeof location !== 'undefined' && location.origin) || 'unknown',
|
||||
hasUserActivation: hasActivation,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'error',
|
||||
msg: '[gdm-shim] begin_session IPC failed: ' + (e && e.message ? e.message : String(e)),
|
||||
});
|
||||
return origGetDisplayMedia(constraints);
|
||||
}
|
||||
if (!session || typeof session.token !== 'string' || !Array.isArray(session.sources)) {
|
||||
send('log', {
|
||||
level: 'warn',
|
||||
msg: '[gdm-shim] begin_session returned malformed payload, falling back',
|
||||
});
|
||||
return origGetDisplayMedia(constraints);
|
||||
}
|
||||
const sessionToken = session.token;
|
||||
const sources = session.sources;
|
||||
if (sources.length === 0) {
|
||||
send('log', { level: 'warn', msg: '[gdm-shim] no sources enumerated, falling back' });
|
||||
await finalizeSessionQuiet(sessionToken, null);
|
||||
return origGetDisplayMedia(constraints);
|
||||
}
|
||||
|
||||
const pick = await showInPagePicker(sources, sessionToken);
|
||||
if (!pick) {
|
||||
send('log', { level: 'info', msg: '[gdm-shim] user cancelled picker' });
|
||||
await finalizeSessionQuiet(sessionToken, null);
|
||||
// Meet (and other video-conf sites) treat `NotAllowedError` on
|
||||
// getDisplayMedia as "the browser blocked us" and pop a
|
||||
// "needs permission" modal. Real Chrome ALSO throws
|
||||
// NotAllowedError on picker cancel, but Meet silently swallows
|
||||
// it there — presumably via a separate Permissions API check
|
||||
// that reports 'granted'. Since we can't easily signal that
|
||||
// state in CEF, throw `AbortError` instead: it's the MDN-blessed
|
||||
// "user interrupted a UI operation" error and most sites (Meet
|
||||
// included) dismiss it silently.
|
||||
throw new DOMException('User cancelled screen share picker', 'AbortError');
|
||||
}
|
||||
// Finalize the session BEFORE getUserMedia: the Chromium capture
|
||||
// path doesn't need the token, and leaving the session open past
|
||||
// this point would just hold the `active` slot for the account
|
||||
// until the 30s TTL fires.
|
||||
await finalizeSessionQuiet(sessionToken, pick.id);
|
||||
send('log', {
|
||||
level: 'info',
|
||||
msg: '[gdm-shim] picked id=' + pick.id + ' kind=' + pick.kind,
|
||||
});
|
||||
const videoMandatory = {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: pick.id,
|
||||
maxFrameRate: 30,
|
||||
// CEF Alloy's Permissions API does not reflect the media-access callback.
|
||||
// Preserve the mic/camera response for providers that check it before
|
||||
// calling getUserMedia; display-capture is deliberately not overridden.
|
||||
try {
|
||||
if (
|
||||
navigator.permissions &&
|
||||
typeof navigator.permissions.query === 'function' &&
|
||||
!navigator.permissions.__ohPermissionsShimInstalled
|
||||
) {
|
||||
const permProto = Object.getPrototypeOf(navigator.permissions);
|
||||
const permDescriptor = Object.getOwnPropertyDescriptor(permProto, 'query');
|
||||
const origQuery = (permDescriptor && permDescriptor.value
|
||||
? permDescriptor.value
|
||||
: navigator.permissions.query
|
||||
).bind(navigator.permissions);
|
||||
const spoofed = {
|
||||
camera: 'granted',
|
||||
microphone: 'granted',
|
||||
};
|
||||
// System-audio capture via `chromeMediaSource: 'desktop'` needs a
|
||||
// loopback driver on macOS (no stock API). If the page requested
|
||||
// audio we try with audio first and fall back to video-only on
|
||||
// rejection so Meet/Slack/etc don't see a generic "Can't share"
|
||||
// error on every attempt. Chromium cleanly handles a missing audio
|
||||
// track in the SDP.
|
||||
const videoOnly = { video: { mandatory: videoMandatory }, audio: false };
|
||||
|
||||
let stream;
|
||||
if (constraints.audio) {
|
||||
const audioMandatory = {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: pick.id,
|
||||
};
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { mandatory: videoMandatory },
|
||||
audio: { mandatory: audioMandatory },
|
||||
});
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'warn',
|
||||
msg:
|
||||
'[gdm-shim] audio+video getUserMedia rejected (' +
|
||||
(e && e.name ? e.name : '?') +
|
||||
'), retrying video-only',
|
||||
});
|
||||
stream = await navigator.mediaDevices.getUserMedia(videoOnly);
|
||||
const spoofedQuery = async function (descriptor) {
|
||||
const n = descriptor && descriptor.name;
|
||||
if (n && spoofed[n]) {
|
||||
return {
|
||||
state: spoofed[n],
|
||||
status: spoofed[n],
|
||||
name: n,
|
||||
onchange: null,
|
||||
addEventListener: function () {},
|
||||
removeEventListener: function () {},
|
||||
dispatchEvent: function () { return true; },
|
||||
};
|
||||
}
|
||||
} else {
|
||||
stream = await navigator.mediaDevices.getUserMedia(videoOnly);
|
||||
}
|
||||
|
||||
// Stream returned by the legacy `chromeMediaSource: 'desktop'`
|
||||
// getUserMedia path is a real capture stream but its tracks lack
|
||||
// the display-media metadata the page expects from real
|
||||
// getDisplayMedia. Google Meet (and others) inspect
|
||||
// `track.getSettings().displaySurface` before they will route the
|
||||
// track over WebRTC — if the field is missing they throw "Can't
|
||||
// share your screen — Something went wrong".
|
||||
//
|
||||
// Patch each video track to expose the right displaySurface and
|
||||
// a `contentHint` of `detail` (standard WebRTC screen-capture
|
||||
// content hint). The underlying capture pipeline is unchanged;
|
||||
// we're only fixing the introspectable metadata the page relies
|
||||
// on to identify a display-media track.
|
||||
const displaySurface = pick.kind === 'screen' ? 'monitor' : 'window';
|
||||
stream.getVideoTracks().forEach(function (track) {
|
||||
try { track.contentHint = 'detail'; } catch (_) { /* ignore */ }
|
||||
try {
|
||||
const origGetSettings = track.getSettings.bind(track);
|
||||
Object.defineProperty(track, 'getSettings', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function () {
|
||||
const base = origGetSettings() || {};
|
||||
return Object.assign({}, base, {
|
||||
displaySurface: displaySurface,
|
||||
logicalSurface: true,
|
||||
cursor: 'motion',
|
||||
});
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'warn',
|
||||
msg: '[gdm-shim] patch getSettings failed: ' + (e && e.message ? e.message : e),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
// In-page picker. Renders straight into the host page's <body> so the
|
||||
// overlay stacks above the site's own compositor (Meet/Slack/Discord
|
||||
// UI) without any native-view gymnastics. All nodes are namespaced
|
||||
// under `__ohsp_*` class/ID prefixes and attached to a closed shadow
|
||||
// root where possible to avoid colliding with the host page's CSS.
|
||||
function showInPagePicker(sources, sessionToken) {
|
||||
return new Promise(function (resolveOuter, rejectOuter) {
|
||||
function host() { return (document.body || document.documentElement); }
|
||||
if (!host()) {
|
||||
// DOM hasn't parsed yet — wait for it and retry. Previously we
|
||||
// resolved null here, which the shim turned into an AbortError
|
||||
// even though no picker was ever shown (coderabbit #809).
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
function () {
|
||||
showInPagePicker(sources, sessionToken).then(resolveOuter, rejectOuter);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const root = document.createElement('div');
|
||||
root.setAttribute('data-openhuman-screen-share-picker', '');
|
||||
root.style.cssText = [
|
||||
'all: initial',
|
||||
'position: fixed',
|
||||
'inset: 0',
|
||||
'z-index: 2147483647',
|
||||
'display: flex',
|
||||
'align-items: center',
|
||||
'justify-content: center',
|
||||
'background: rgba(0,0,0,0.55)',
|
||||
'backdrop-filter: blur(6px)',
|
||||
'-webkit-backdrop-filter: blur(6px)',
|
||||
'font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", sans-serif',
|
||||
].join(';');
|
||||
|
||||
const shadow = root.attachShadow ? root.attachShadow({ mode: 'closed' }) : root;
|
||||
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.textContent = [
|
||||
'* { box-sizing: border-box; margin: 0; padding: 0; font-family: inherit; }',
|
||||
'.card { background: #fff; color: #1C1917; border-radius: 16px; width: min(640px, 92vw);',
|
||||
' max-height: 86vh; box-shadow: 0 24px 64px rgba(0,0,0,0.35); overflow: hidden;',
|
||||
' display: flex; flex-direction: column; }',
|
||||
'.head { padding: 20px 24px; border-bottom: 1px solid #E7E5E4; display: flex;',
|
||||
' align-items: flex-start; justify-content: space-between; gap: 16px; }',
|
||||
'.title { font-size: 17px; font-weight: 600; color: #1C1917; }',
|
||||
'.origin { margin-top: 4px; font-size: 13px; color: #78716C; }',
|
||||
'.closebtn { width: 32px; height: 32px; border: none; background: transparent;',
|
||||
' color: #78716C; cursor: pointer; border-radius: 8px; font-size: 18px;',
|
||||
' display: flex; align-items: center; justify-content: center; }',
|
||||
'.closebtn:hover { background: #F5F5F4; color: #1C1917; }',
|
||||
'.tabs { display: flex; gap: 4px; padding: 0 24px; border-bottom: 1px solid #E7E5E4; }',
|
||||
'.tab { appearance: none; -webkit-appearance: none; background: transparent; border: 0;',
|
||||
' padding: 12px 16px; font-size: 14px; font-weight: 500; color: #78716C;',
|
||||
' cursor: pointer; border-bottom: 2px solid transparent; }',
|
||||
'.tab.active { color: #4A83DD; border-bottom-color: #4A83DD; }',
|
||||
'.body { padding: 20px 24px; overflow-y: auto; }',
|
||||
'.grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; }',
|
||||
'.srcbtn { background: #FAFAF9; border: 2px solid #E7E5E4; border-radius: 10px;',
|
||||
' padding: 0; cursor: pointer; text-align: left; overflow: hidden;',
|
||||
' transition: border-color .15s, box-shadow .15s; }',
|
||||
'.srcbtn:hover { border-color: #D4D4D1; }',
|
||||
'.srcbtn.selected { border-color: #4A83DD;',
|
||||
' box-shadow: 0 0 0 3px rgba(74,131,221,0.18); }',
|
||||
'.srcthumb { aspect-ratio: 16/10; background: #F5F5F4; display: flex;',
|
||||
' align-items: center; justify-content: center; color: #A8A29E;',
|
||||
' font-size: 32px; }',
|
||||
'.srcname { padding: 8px 10px; font-size: 13px; color: #1C1917; font-weight: 500;',
|
||||
' white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
||||
'.srcapp { padding: 0 10px 8px; font-size: 11px; color: #78716C;',
|
||||
' white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
||||
'.empty { padding: 32px 0; text-align: center; color: #78716C; font-size: 13px; }',
|
||||
'.foot { padding: 12px 16px; border-top: 1px solid #E7E5E4; display: flex;',
|
||||
' justify-content: flex-end; gap: 8px; }',
|
||||
'.btn { appearance: none; -webkit-appearance: none; border: 0; border-radius: 10px;',
|
||||
' padding: 9px 16px; font-size: 14px; font-weight: 500; cursor: pointer; }',
|
||||
'.btn-secondary { background: transparent; color: #1C1917; }',
|
||||
'.btn-secondary:hover { background: #F5F5F4; }',
|
||||
'.btn-primary { background: #4A83DD; color: #fff; }',
|
||||
'.btn-primary:hover { background: #3D6DC4; }',
|
||||
'.btn-primary:disabled { background: #D4D4D1; cursor: not-allowed; }',
|
||||
].join('\n');
|
||||
shadow.appendChild(styleTag);
|
||||
|
||||
function hostnameOf(url) {
|
||||
try { return new URL(url).hostname || url; } catch (e) { return url; }
|
||||
}
|
||||
|
||||
const origin = (typeof location !== 'undefined' && location.origin) || 'this site';
|
||||
let activeTab = sources.some(function (s) { return s.kind === 'screen'; })
|
||||
? 'screen'
|
||||
: 'window';
|
||||
let selectedId = null;
|
||||
|
||||
// DOM is constructed imperatively (no innerHTML) because hosts
|
||||
// like Google Meet ship strict Trusted Types CSP that rejects
|
||||
// string-based HTML assignment with a TypeError. `createElement`
|
||||
// and `appendChild` are policy-free and work everywhere.
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
|
||||
function el(tag, attrs, text) {
|
||||
const node = document.createElement(tag);
|
||||
if (attrs) {
|
||||
Object.keys(attrs).forEach(function (k) {
|
||||
if (k === 'className') node.className = attrs[k];
|
||||
else node.setAttribute(k, attrs[k]);
|
||||
});
|
||||
}
|
||||
if (text != null) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
const head = el('div', { className: 'head' });
|
||||
const headLeft = el('div');
|
||||
headLeft.appendChild(el('div', { className: 'title' }, 'Choose what to share'));
|
||||
const originEl = el(
|
||||
'div',
|
||||
{ className: 'origin' },
|
||||
hostnameOf(origin) + ' wants to share your screen.'
|
||||
);
|
||||
headLeft.appendChild(originEl);
|
||||
head.appendChild(headLeft);
|
||||
const closeBtn = el(
|
||||
'button',
|
||||
{ className: 'closebtn', 'data-action': 'cancel', 'aria-label': 'Cancel' },
|
||||
'✕'
|
||||
);
|
||||
head.appendChild(closeBtn);
|
||||
card.appendChild(head);
|
||||
|
||||
const tabs = el('div', { className: 'tabs' });
|
||||
const screenTab = el('button', { className: 'tab', 'data-tab': 'screen' }, 'Entire Screen');
|
||||
const windowTab = el('button', { className: 'tab', 'data-tab': 'window' }, 'Window');
|
||||
tabs.appendChild(screenTab);
|
||||
tabs.appendChild(windowTab);
|
||||
card.appendChild(tabs);
|
||||
|
||||
const bodyEl = el('div', { className: 'body' });
|
||||
const gridEl = el('div', { className: 'grid' });
|
||||
bodyEl.appendChild(gridEl);
|
||||
card.appendChild(bodyEl);
|
||||
|
||||
const foot = el('div', { className: 'foot' });
|
||||
const cancelBtn = el(
|
||||
'button',
|
||||
{ className: 'btn btn-secondary', 'data-action': 'cancel' },
|
||||
'Cancel'
|
||||
);
|
||||
const shareBtn = el('button', { className: 'btn btn-primary' }, 'Share');
|
||||
shareBtn.disabled = true;
|
||||
foot.appendChild(cancelBtn);
|
||||
foot.appendChild(shareBtn);
|
||||
card.appendChild(foot);
|
||||
|
||||
shadow.appendChild(card);
|
||||
|
||||
const tabButtons = [screenTab, windowTab];
|
||||
|
||||
function setTab(next) {
|
||||
activeTab = next;
|
||||
tabButtons.forEach(function (btn) {
|
||||
btn.classList.toggle('active', btn.getAttribute('data-tab') === activeTab);
|
||||
});
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
while (gridEl.firstChild) gridEl.removeChild(gridEl.firstChild);
|
||||
const filtered = sources.filter(function (s) { return s.kind === activeTab; });
|
||||
if (filtered.length === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'empty';
|
||||
empty.textContent =
|
||||
'No ' + (activeTab === 'screen' ? 'screens' : 'windows') + ' available.';
|
||||
gridEl.appendChild(empty);
|
||||
shareBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
filtered.forEach(function (src) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'srcbtn' + (selectedId === src.id ? ' selected' : '');
|
||||
btn.setAttribute('data-source-id', src.id);
|
||||
const thumb = document.createElement('div');
|
||||
thumb.className = 'srcthumb';
|
||||
if (src.thumbnailPngBase64) {
|
||||
const img = document.createElement('img');
|
||||
img.src = 'data:image/png;base64,' + src.thumbnailPngBase64;
|
||||
img.alt = '';
|
||||
img.style.cssText =
|
||||
'width: 100%; height: 100%; object-fit: contain; display: block;';
|
||||
thumb.appendChild(img);
|
||||
} else {
|
||||
// Placeholder glyph until the lazy-loaded thumbnail arrives.
|
||||
thumb.textContent = activeTab === 'screen' ? '□' : '▣';
|
||||
// Dedup in-flight thumbnail IPCs: render() re-runs on every
|
||||
// selection change and tab switch, and without this cache
|
||||
// each pass would re-issue screen_share_thumbnail for every
|
||||
// source that hadn't yet returned (coderabbit #809).
|
||||
function paintThumb(b64) {
|
||||
if (!b64 || typeof b64 !== 'string') return;
|
||||
const liveBtn = gridEl.querySelector(
|
||||
'[data-source-id="' + src.id.replace(/"/g, '\\"') + '"]'
|
||||
);
|
||||
if (!liveBtn) return;
|
||||
const liveThumb = liveBtn.querySelector('.srcthumb');
|
||||
if (!liveThumb) return;
|
||||
while (liveThumb.firstChild) liveThumb.removeChild(liveThumb.firstChild);
|
||||
const img = document.createElement('img');
|
||||
img.src = 'data:image/png;base64,' + b64;
|
||||
img.alt = '';
|
||||
img.style.cssText =
|
||||
'width: 100%; height: 100%; object-fit: contain; display: block;';
|
||||
liveThumb.appendChild(img);
|
||||
}
|
||||
if (src.__thumbnailPromise) {
|
||||
src.__thumbnailPromise.then(paintThumb, function () {});
|
||||
} else {
|
||||
src.__thumbnailPromise = rawInvoke('screen_share_thumbnail', {
|
||||
args: { token: sessionToken, id: src.id },
|
||||
}).then(
|
||||
function (b64) {
|
||||
if (b64 && typeof b64 === 'string') {
|
||||
// Stash on the source so future re-renders keep
|
||||
// the thumbnail without re-requesting it.
|
||||
src.thumbnailPngBase64 = b64;
|
||||
}
|
||||
paintThumb(b64);
|
||||
return b64;
|
||||
},
|
||||
function () {
|
||||
/* thumbnail failures degrade gracefully to the glyph */
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
const name = document.createElement('div');
|
||||
name.className = 'srcname';
|
||||
name.textContent = src.name;
|
||||
btn.appendChild(thumb);
|
||||
btn.appendChild(name);
|
||||
if (src.appName) {
|
||||
const app = document.createElement('div');
|
||||
app.className = 'srcapp';
|
||||
app.textContent = src.appName;
|
||||
btn.appendChild(app);
|
||||
}
|
||||
btn.addEventListener('click', function () {
|
||||
selectedId = src.id;
|
||||
render();
|
||||
});
|
||||
btn.addEventListener('dblclick', function () {
|
||||
selectedId = src.id;
|
||||
finish(sources.find(function (s) { return s.id === selectedId; }) || null);
|
||||
});
|
||||
gridEl.appendChild(btn);
|
||||
});
|
||||
if (!selectedId || !filtered.some(function (s) { return s.id === selectedId; })) {
|
||||
selectedId = filtered[0].id;
|
||||
gridEl.firstChild && gridEl.firstChild.classList.add('selected');
|
||||
}
|
||||
shareBtn.disabled = !selectedId;
|
||||
}
|
||||
|
||||
tabButtons.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () { setTab(btn.getAttribute('data-tab')); });
|
||||
});
|
||||
|
||||
let settled = false;
|
||||
function finish(pick) {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
window.removeEventListener('keydown', onKey, true);
|
||||
try { root.remove(); } catch (e) { /* ignore */ }
|
||||
resolveOuter(pick);
|
||||
}
|
||||
|
||||
card.querySelectorAll('[data-action="cancel"]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () { finish(null); });
|
||||
});
|
||||
shareBtn.addEventListener('click', function () {
|
||||
const pick = sources.find(function (s) { return s.id === selectedId; }) || null;
|
||||
finish(pick);
|
||||
});
|
||||
// Clicks on the backdrop (outside the card) cancel. Clicks inside
|
||||
// the card bubble up to root too, but we stop them there.
|
||||
root.addEventListener('click', function (e) {
|
||||
if (e.target === root || e.composedPath()[0] === root) finish(null);
|
||||
});
|
||||
card.addEventListener('click', function (e) { e.stopPropagation(); });
|
||||
|
||||
function onKey(e) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
finish(null);
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKey, true);
|
||||
|
||||
setTab(activeTab);
|
||||
host().appendChild(root);
|
||||
});
|
||||
}
|
||||
|
||||
let installed = false;
|
||||
try {
|
||||
Object.defineProperty(proto, 'getDisplayMedia', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: shim,
|
||||
});
|
||||
installed = true;
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'error',
|
||||
msg: '[gdm-shim] defineProperty(proto) failed: ' + (e && e.message ? e.message : String(e)),
|
||||
});
|
||||
}
|
||||
if (!installed) {
|
||||
return origQuery(descriptor);
|
||||
};
|
||||
try {
|
||||
Object.defineProperty(navigator.mediaDevices, 'getDisplayMedia', {
|
||||
Object.defineProperty(permProto, 'query', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: shim,
|
||||
value: spoofedQuery,
|
||||
});
|
||||
installed = true;
|
||||
} catch (e2) {
|
||||
send('log', {
|
||||
level: 'error',
|
||||
msg: '[gdm-shim] defineProperty(instance) failed: ' + (e2 && e2.message ? e2.message : String(e2)),
|
||||
} catch (e) {
|
||||
Object.defineProperty(navigator.permissions, 'query', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: spoofedQuery,
|
||||
});
|
||||
}
|
||||
navigator.permissions.__ohPermissionsShimInstalled = true;
|
||||
send('log', { level: 'info', msg: '[recipe-runtime] media permissions.query shim installed' });
|
||||
}
|
||||
navigator.mediaDevices.__ohGdmShimInstalled = installed;
|
||||
|
||||
// Some pages (Meet) also consult `navigator.permissions.query` and
|
||||
// branch on the reported state for `display-capture` /
|
||||
// `camera` / `microphone`. CEF Alloy's Permissions API does not
|
||||
// reflect what our OnRequestMediaAccessPermission callback will
|
||||
// grant dynamically, so it defaults to 'prompt' or even 'denied'
|
||||
// for `display-capture`. A page that sees 'denied' will assume
|
||||
// sharing is structurally blocked and refuse to call
|
||||
// getDisplayMedia — or show the "needs permission" modal on cancel.
|
||||
// We shadow the query for these names so the page sees 'granted'
|
||||
// and relies on our shim for the actual user decision.
|
||||
try {
|
||||
if (
|
||||
navigator.permissions &&
|
||||
typeof navigator.permissions.query === 'function' &&
|
||||
!navigator.permissions.__ohPermissionsShimInstalled
|
||||
) {
|
||||
const permProto = Object.getPrototypeOf(navigator.permissions);
|
||||
const permDescriptor = Object.getOwnPropertyDescriptor(permProto, 'query');
|
||||
const origQuery = (permDescriptor && permDescriptor.value
|
||||
? permDescriptor.value
|
||||
: navigator.permissions.query
|
||||
).bind(navigator.permissions);
|
||||
// CEF Alloy's Permissions API doesn't reflect what our
|
||||
// OnRequestMediaAccessPermission callback will grant dynamically,
|
||||
// so it defaults to 'prompt' or 'denied' for the media permissions
|
||||
// we do handle. Pages that consult the Permissions API up front
|
||||
// (Meet for display-capture; some flows for camera/microphone)
|
||||
// refuse to try the actual getUserMedia call if they see 'denied'
|
||||
// here. Spoof all three to 'granted'; the real grant still goes
|
||||
// through our CEF permission handler where it's scoped per-call.
|
||||
const spoofed = {
|
||||
'display-capture': 'granted',
|
||||
camera: 'granted',
|
||||
microphone: 'granted',
|
||||
};
|
||||
const spoofedQuery = async function (descriptor) {
|
||||
const n = descriptor && descriptor.name;
|
||||
if (n && spoofed[n]) {
|
||||
return {
|
||||
state: spoofed[n],
|
||||
status: spoofed[n],
|
||||
name: n,
|
||||
onchange: null,
|
||||
addEventListener: function () {},
|
||||
removeEventListener: function () {},
|
||||
dispatchEvent: function () { return true; },
|
||||
};
|
||||
}
|
||||
return origQuery(descriptor);
|
||||
};
|
||||
try {
|
||||
Object.defineProperty(permProto, 'query', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: spoofedQuery,
|
||||
});
|
||||
} catch (e) {
|
||||
Object.defineProperty(navigator.permissions, 'query', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: spoofedQuery,
|
||||
});
|
||||
}
|
||||
navigator.permissions.__ohPermissionsShimInstalled = true;
|
||||
send('log', { level: 'info', msg: '[gdm-shim] permissions.query shim installed' });
|
||||
}
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'warn',
|
||||
msg: '[gdm-shim] permissions.query shim failed: ' + (e && e.message ? e.message : e),
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
send('log', {
|
||||
level: 'info',
|
||||
msg:
|
||||
'[gdm-shim] install=' + installed +
|
||||
' on ' + ((typeof location !== 'undefined' && location.origin) || '?'),
|
||||
level: 'warn',
|
||||
msg: '[recipe-runtime] media permissions.query shim failed: ' + (e && e.message ? e.message : e),
|
||||
});
|
||||
})();
|
||||
}
|
||||
})();
|
||||
|
||||
Vendored
+1
-1
Submodule app/src-tauri/vendor/tauri-cef updated: f09d7e746a...455b47debb
@@ -1,266 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
type ScreenIntelligenceState,
|
||||
useScreenIntelligenceState,
|
||||
} from '../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
|
||||
const formatBytes = (bytes: number | null | undefined): string => {
|
||||
if (bytes == null) return '-';
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
||||
};
|
||||
|
||||
interface ScreenIntelligenceDebugPanelProps {
|
||||
state?: Pick<
|
||||
ScreenIntelligenceState,
|
||||
| 'status'
|
||||
| 'captureTestResult'
|
||||
| 'isCaptureTestRunning'
|
||||
| 'recentVisionSummaries'
|
||||
| 'lastError'
|
||||
| 'refreshStatus'
|
||||
| 'refreshVision'
|
||||
| 'runCaptureTest'
|
||||
>;
|
||||
}
|
||||
|
||||
const ScreenIntelligenceDebugPanelContent = ({
|
||||
state: providedState,
|
||||
}: Required<Pick<ScreenIntelligenceDebugPanelProps, 'state'>>) => {
|
||||
const { t } = useT();
|
||||
const {
|
||||
status,
|
||||
captureTestResult,
|
||||
isCaptureTestRunning,
|
||||
recentVisionSummaries,
|
||||
lastError,
|
||||
refreshStatus,
|
||||
refreshVision,
|
||||
runCaptureTest,
|
||||
} = providedState;
|
||||
|
||||
const handleCaptureTest = useCallback(() => {
|
||||
void runCaptureTest();
|
||||
}, [runCaptureTest]);
|
||||
|
||||
const handleRefreshStatus = useCallback(() => {
|
||||
void refreshStatus();
|
||||
void refreshVision(5);
|
||||
}, [refreshStatus, refreshVision]);
|
||||
|
||||
const permissions = status?.permissions;
|
||||
const session = status?.session;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-stone-100">
|
||||
{t('intelligence.screenDebug.title')}
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleRefreshStatus}
|
||||
className="rounded-lg border border-stone-700 bg-stone-800/60 px-3 py-1 text-xs text-content-faint dark:text-neutral-600 transition-colors hover:bg-stone-700/60">
|
||||
{t('common.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Permissions */}
|
||||
<div className="rounded-xl border border-stone-700 bg-stone-900/50 p-3">
|
||||
<h4 className="mb-2 text-xs font-medium uppercase tracking-wide text-content-faint">
|
||||
{t('intelligence.screenDebug.permissions')}
|
||||
</h4>
|
||||
{status?.platform_supported === false ? (
|
||||
<p className="text-xs text-content-muted">
|
||||
{t('intelligence.screenDebug.platformNotSupported')}
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 gap-2 text-xs">
|
||||
<PermissionDot
|
||||
label={t('intelligence.screenDebug.permScreen')}
|
||||
value={permissions?.screen_recording}
|
||||
/>
|
||||
<PermissionDot
|
||||
label={t('intelligence.screenDebug.permAccessibility')}
|
||||
value={permissions?.accessibility}
|
||||
/>
|
||||
<PermissionDot
|
||||
label={t('intelligence.screenDebug.permInput')}
|
||||
value={permissions?.input_monitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Session Status */}
|
||||
<div className="rounded-xl border border-stone-700 bg-stone-900/50 p-3">
|
||||
<h4 className="mb-2 text-xs font-medium uppercase tracking-wide text-content-faint">
|
||||
{t('intelligence.screenDebug.session')}
|
||||
</h4>
|
||||
<div className="space-y-1 text-xs text-content-faint dark:text-neutral-600">
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.active')}</span>
|
||||
<span className={session?.active ? 'text-green-400' : 'text-content-muted'}>
|
||||
{session?.active ? t('common.yes') : t('common.no')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.frames')}</span>
|
||||
<span>{session?.frames_in_memory ?? 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.visionState')}</span>
|
||||
<span>{session?.vision_state ?? t('intelligence.screenDebug.idle')}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.visionQueue')}</span>
|
||||
<span>{session?.vision_queue_depth ?? 0}</span>
|
||||
</div>
|
||||
{session?.last_context && (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.lastApp')}</span>
|
||||
<span className="max-w-[180px] truncate">{session.last_context}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Capture Test */}
|
||||
<div className="rounded-xl border border-stone-700 bg-stone-900/50 p-3">
|
||||
<h4 className="mb-2 text-xs font-medium uppercase tracking-wide text-content-faint">
|
||||
{t('intelligence.screenDebug.captureTest')}
|
||||
</h4>
|
||||
<button
|
||||
onClick={handleCaptureTest}
|
||||
disabled={isCaptureTestRunning}
|
||||
className="mb-3 w-full rounded-lg border border-primary-600/40 bg-primary-600/20 px-3 py-2 text-sm font-medium text-primary-300 transition-colors hover:bg-primary-600/30 disabled:opacity-50">
|
||||
{isCaptureTestRunning
|
||||
? t('intelligence.screenDebug.capturing')
|
||||
: t('intelligence.screenDebug.testCapture')}
|
||||
</button>
|
||||
|
||||
{captureTestResult && (
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-1 text-xs text-content-faint dark:text-neutral-600">
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.status')}</span>
|
||||
<span className={captureTestResult.ok ? 'text-green-400' : 'text-red-400'}>
|
||||
{captureTestResult.ok
|
||||
? t('intelligence.screenDebug.captureSuccess')
|
||||
: t('intelligence.screenDebug.captureFailed')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.mode')}</span>
|
||||
<span>{captureTestResult.capture_mode}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.time')}</span>
|
||||
<span>{captureTestResult.timing_ms}ms</span>
|
||||
</div>
|
||||
{captureTestResult.bytes_estimate != null && (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.size')}</span>
|
||||
<span>{formatBytes(captureTestResult.bytes_estimate)}</span>
|
||||
</div>
|
||||
)}
|
||||
{captureTestResult.context && (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.app')}</span>
|
||||
<span className="max-w-[180px] truncate">
|
||||
{captureTestResult.context.app_name ?? t('intelligence.screenDebug.unknown')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{captureTestResult.context?.bounds_width != null && (
|
||||
<div className="flex justify-between">
|
||||
<span>{t('intelligence.screenDebug.bounds')}</span>
|
||||
<span>
|
||||
{captureTestResult.context.bounds_width}x
|
||||
{captureTestResult.context.bounds_height} at (
|
||||
{captureTestResult.context.bounds_x},{captureTestResult.context.bounds_y})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{captureTestResult.error && (
|
||||
<div className="rounded-lg border border-red-800/50 bg-red-900/20 p-2 text-xs text-red-300">
|
||||
{captureTestResult.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{captureTestResult.image_ref && (
|
||||
<div className="overflow-hidden rounded-lg border border-stone-700">
|
||||
<img
|
||||
src={captureTestResult.image_ref}
|
||||
alt={t('intelligence.screenDebug.captureAlt')}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Recent Vision Summaries */}
|
||||
{recentVisionSummaries.length > 0 && (
|
||||
<div className="rounded-xl border border-stone-700 bg-stone-900/50 p-3">
|
||||
<h4 className="mb-2 text-xs font-medium uppercase tracking-wide text-content-faint">
|
||||
{t('intelligence.screenDebug.recentVisionSummaries')}
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{recentVisionSummaries.map(summary => (
|
||||
<div
|
||||
key={summary.id}
|
||||
className="rounded-lg border border-stone-700/50 bg-stone-800/30 p-2 text-xs">
|
||||
<div className="flex justify-between text-content-faint">
|
||||
<span>{summary.app_name ?? t('intelligence.screenDebug.unknown')}</span>
|
||||
<span>
|
||||
{new Date(summary.captured_at_ms).toLocaleTimeString()} ·{' '}
|
||||
{(summary.confidence * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-stone-200">{summary.actionable_notes}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Display */}
|
||||
{lastError && (
|
||||
<div className="rounded-lg border border-red-800/50 bg-red-900/20 p-2 text-xs text-red-300">
|
||||
{lastError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OwnedScreenIntelligenceDebugPanel = () => {
|
||||
const state = useScreenIntelligenceState({ loadVision: true, visionLimit: 5, pollMs: 2000 });
|
||||
return <ScreenIntelligenceDebugPanelContent state={state} />;
|
||||
};
|
||||
|
||||
const ScreenIntelligenceDebugPanel = ({ state }: ScreenIntelligenceDebugPanelProps) => {
|
||||
if (state) {
|
||||
return <ScreenIntelligenceDebugPanelContent state={state} />;
|
||||
}
|
||||
return <OwnedScreenIntelligenceDebugPanel />;
|
||||
};
|
||||
|
||||
const PermissionDot = ({ label, value }: { label: string; value?: string }) => {
|
||||
const color =
|
||||
value === 'granted' ? 'bg-green-500' : value === 'denied' ? 'bg-red-500' : 'bg-stone-600';
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className={`h-2 w-2 rounded-full ${color}`} />
|
||||
<span className="text-content-faint dark:text-neutral-600">{label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenIntelligenceDebugPanel;
|
||||
@@ -26,7 +26,6 @@ import type { ToastNotification } from '../../types/intelligence';
|
||||
import SettingsPanel from '../settings/layout/SettingsPanel';
|
||||
import CreateSkillModal from '../skills/CreateSkillModal';
|
||||
import UnifiedSkillCard from '../skills/SkillCard';
|
||||
import { BUILT_IN_SKILL_ICONS } from '../skills/skillIcons';
|
||||
import UninstallSkillConfirmDialog from '../skills/UninstallSkillConfirmDialog';
|
||||
import Button from '../ui/Button';
|
||||
import { ToastContainer } from './Toast';
|
||||
@@ -181,7 +180,7 @@ export default function WorkflowsTab({ asSettingsPanel = false }: WorkflowsTabPr
|
||||
return (
|
||||
<UnifiedSkillCard
|
||||
key={wf.id}
|
||||
icon={BUILT_IN_SKILL_ICONS.screenIntelligence}
|
||||
icon={null}
|
||||
title={wf.name}
|
||||
description={wf.description}
|
||||
statusLabel={scopeLabel}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
type ScreenIntelligenceState,
|
||||
useScreenIntelligenceState,
|
||||
} from '../../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import ScreenIntelligenceDebugPanel from '../ScreenIntelligenceDebugPanel';
|
||||
|
||||
vi.mock('../../../features/screen-intelligence/useScreenIntelligenceState', () => ({
|
||||
useScreenIntelligenceState: vi.fn(),
|
||||
}));
|
||||
|
||||
const baseState: ScreenIntelligenceState = {
|
||||
status: {
|
||||
platform_supported: true,
|
||||
permissions: {
|
||||
screen_recording: 'granted',
|
||||
accessibility: 'granted',
|
||||
input_monitoring: 'granted',
|
||||
},
|
||||
features: { screen_monitoring: true },
|
||||
session: {
|
||||
active: false,
|
||||
started_at_ms: null,
|
||||
expires_at_ms: null,
|
||||
remaining_ms: null,
|
||||
ttl_secs: 300,
|
||||
panic_hotkey: 'Cmd+Shift+.',
|
||||
stop_reason: null,
|
||||
frames_in_memory: 0,
|
||||
last_capture_at_ms: null,
|
||||
last_context: null,
|
||||
vision_enabled: true,
|
||||
vision_state: 'idle',
|
||||
vision_queue_depth: 0,
|
||||
last_vision_at_ms: null,
|
||||
last_vision_summary: null,
|
||||
},
|
||||
config: {
|
||||
enabled: true,
|
||||
capture_policy: 'hybrid',
|
||||
policy_mode: 'all_except_blacklist',
|
||||
baseline_fps: 1,
|
||||
vision_enabled: true,
|
||||
session_ttl_secs: 300,
|
||||
panic_stop_hotkey: 'Cmd+Shift+.',
|
||||
autocomplete_enabled: true,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: [],
|
||||
denylist: [],
|
||||
},
|
||||
denylist: [],
|
||||
is_context_blocked: false,
|
||||
},
|
||||
lastRestartSummary: null,
|
||||
recentVisionSummaries: [],
|
||||
captureTestResult: null,
|
||||
isCaptureTestRunning: false,
|
||||
isLoading: false,
|
||||
isRequestingPermissions: false,
|
||||
isRestartingCore: false,
|
||||
isStartingSession: false,
|
||||
isStoppingSession: false,
|
||||
isLoadingVision: false,
|
||||
isFlushingVision: false,
|
||||
lastError: null,
|
||||
refreshStatus: vi.fn().mockResolvedValue(null),
|
||||
requestPermission: vi.fn().mockResolvedValue(null),
|
||||
refreshPermissionsWithRestart: vi.fn().mockResolvedValue(null),
|
||||
startSession: vi.fn().mockResolvedValue(null),
|
||||
stopSession: vi.fn().mockResolvedValue(null),
|
||||
refreshVision: vi.fn().mockResolvedValue([]),
|
||||
flushVision: vi.fn().mockResolvedValue(undefined),
|
||||
runCaptureTest: vi.fn().mockResolvedValue(undefined),
|
||||
clearError: vi.fn(),
|
||||
};
|
||||
|
||||
describe('ScreenIntelligenceDebugPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(useScreenIntelligenceState).mockReturnValue(baseState);
|
||||
});
|
||||
|
||||
it('renders successful capture diagnostics and preview image', async () => {
|
||||
const state: ScreenIntelligenceState = {
|
||||
...baseState,
|
||||
captureTestResult: {
|
||||
ok: true,
|
||||
capture_mode: 'windowed',
|
||||
context: {
|
||||
app_name: 'Safari',
|
||||
window_title: 'GitHub',
|
||||
bounds_x: 10,
|
||||
bounds_y: 20,
|
||||
bounds_width: 1440,
|
||||
bounds_height: 900,
|
||||
},
|
||||
image_ref: 'data:image/png;base64,ZmFrZQ==',
|
||||
bytes_estimate: 2048,
|
||||
error: null,
|
||||
timing_ms: 155,
|
||||
},
|
||||
};
|
||||
|
||||
render(<ScreenIntelligenceDebugPanel state={state} />);
|
||||
|
||||
expect(screen.getByText('Success')).toBeInTheDocument();
|
||||
expect(screen.getByText('windowed')).toBeInTheDocument();
|
||||
expect(screen.getByText('155ms')).toBeInTheDocument();
|
||||
expect(screen.getByText('2.0 KB')).toBeInTheDocument();
|
||||
expect(screen.getByText('Safari')).toBeInTheDocument();
|
||||
expect(screen.getByAltText('Capture test result')).toHaveAttribute(
|
||||
'src',
|
||||
'data:image/png;base64,ZmFrZQ=='
|
||||
);
|
||||
});
|
||||
|
||||
it('shows "Platform not supported" and hides permission dots when platform_supported is false', () => {
|
||||
const state: ScreenIntelligenceState = {
|
||||
...baseState,
|
||||
status: { ...baseState.status!, platform_supported: false },
|
||||
};
|
||||
|
||||
render(<ScreenIntelligenceDebugPanel state={state} />);
|
||||
|
||||
expect(screen.getByText('Platform not supported')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Screen')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Accessibility')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Input')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders capture failures without breaking the diagnostics panel', async () => {
|
||||
const state: ScreenIntelligenceState = {
|
||||
...baseState,
|
||||
captureTestResult: {
|
||||
ok: false,
|
||||
capture_mode: 'fullscreen',
|
||||
context: null,
|
||||
image_ref: null,
|
||||
bytes_estimate: null,
|
||||
error: 'screen recording permission is not granted',
|
||||
timing_ms: 42,
|
||||
},
|
||||
};
|
||||
|
||||
render(<ScreenIntelligenceDebugPanel state={state} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Refresh' }));
|
||||
|
||||
expect(screen.getByText('Failed')).toBeInTheDocument();
|
||||
expect(screen.getByText('fullscreen')).toBeInTheDocument();
|
||||
expect(screen.getByText('42ms')).toBeInTheDocument();
|
||||
expect(screen.getByText('screen recording permission is not granted')).toBeInTheDocument();
|
||||
expect(screen.queryByAltText('Capture test result')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Permissions')).toBeInTheDocument();
|
||||
expect(baseState.refreshStatus).toHaveBeenCalledTimes(1);
|
||||
expect(baseState.refreshVision).toHaveBeenCalledWith(5);
|
||||
});
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe('oauthAuthReadiness', () => {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
@@ -125,7 +125,7 @@ describe('oauthAuthReadiness', () => {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
|
||||
@@ -20,7 +20,6 @@ type SettingsRoute =
|
||||
| 'agent-access'
|
||||
| 'account'
|
||||
| 'cron-jobs'
|
||||
| 'screen-intelligence'
|
||||
| 'privacy'
|
||||
| 'billing'
|
||||
| 'team'
|
||||
@@ -32,7 +31,6 @@ type SettingsRoute =
|
||||
| 'tools'
|
||||
| 'recovery-phrase'
|
||||
| 'wallet-balances'
|
||||
| 'screen-awareness-debug'
|
||||
| 'local-model-debug'
|
||||
| 'notifications'
|
||||
| 'notification-routing'
|
||||
|
||||
@@ -116,7 +116,6 @@ export const SETTINGS_NAV_ICONS: Record<string, ReactNode> = {
|
||||
'M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z'
|
||||
)
|
||||
),
|
||||
'screen-awareness-debug': icon(stroke('M3 5h18v12H3zM8 21h8m-4-4v4')),
|
||||
// Automation & Integrations
|
||||
tasks: icon(
|
||||
stroke(
|
||||
|
||||
@@ -101,7 +101,7 @@ const CompanionPanel = () => {
|
||||
const sessionActive = status?.active ?? false;
|
||||
|
||||
return (
|
||||
<SettingsPanel description={t('pages.settings.features.desktopCompanionDesc')}>
|
||||
<SettingsPanel description={t('settings.assistant.desktopCompanionDesc')}>
|
||||
{/* Session status + controls */}
|
||||
<SettingsSection>
|
||||
<SettingsRow
|
||||
@@ -182,22 +182,6 @@ const CompanionPanel = () => {
|
||||
label={t('settings.companion.sessionTtl')}
|
||||
control={<span className="text-xs text-content-muted">{config.ttl_secs}s</span>}
|
||||
/>
|
||||
<SettingsRow
|
||||
label={t('settings.companion.screenCapture')}
|
||||
control={
|
||||
<span className="text-xs text-content-muted">
|
||||
{config.capture_screen ? t('common.enabled') : t('common.disabled')}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<SettingsRow
|
||||
label={t('settings.companion.appContext')}
|
||||
control={
|
||||
<span className="text-xs text-content-muted">
|
||||
{config.include_app_context ? t('common.enabled') : t('common.disabled')}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// [settings] Developer & Diagnostics panel — debug-only entries only.
|
||||
// User-facing routes (agents, autonomy, agent-access, sandbox-settings,
|
||||
// activity-level, tools, companion, screen-intelligence, voice, embeddings,
|
||||
// activity-level, tools, companion, voice, embeddings,
|
||||
// heartbeat, ledger-usage, cost-dashboard, task-sources, composio-routing,
|
||||
// webhooks-triggers, migration, security) have been moved to their canonical
|
||||
// section pages. Only genuine diagnostics remain here.
|
||||
@@ -46,7 +46,7 @@ interface DevGroup {
|
||||
// Removed from all groups (moved to canonical section pages):
|
||||
// agents, autonomy, agent-access, sandbox-settings, activity-level
|
||||
// → Settings → Agents
|
||||
// tools, companion, screen-intelligence
|
||||
// tools, companion
|
||||
// → Settings → Features
|
||||
// voice, embeddings, heartbeat, ledger-usage, cost-dashboard
|
||||
// → Settings → AI & Models
|
||||
@@ -121,22 +121,6 @@ const modelsDebugGroup: DevGroup = {
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'screen-awareness-debug',
|
||||
titleKey: 'settings.developerMenu.screenAwareness.title',
|
||||
descriptionKey: 'settings.developerMenu.screenAwareness.desc',
|
||||
route: 'screen-awareness-debug',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 5h18v12H3zM8 21h8m-4-4v4"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
import { type ComponentProps, useRef, useState } from 'react';
|
||||
|
||||
import ScreenIntelligenceDebugPanel from '../../../components/intelligence/ScreenIntelligenceDebugPanel';
|
||||
import { useScreenIntelligenceState } from '../../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { isTauri, openhumanUpdateScreenIntelligenceSettings } from '../../../utils/tauriCommands';
|
||||
import Button from '../../ui/Button';
|
||||
import {
|
||||
SettingsCheckbox,
|
||||
SettingsEmptyState,
|
||||
SettingsNumberField,
|
||||
SettingsRow,
|
||||
SettingsSection,
|
||||
SettingsStatusLine,
|
||||
SettingsTextArea,
|
||||
} from '../controls';
|
||||
import SettingsPanel from '../layout/SettingsPanel';
|
||||
|
||||
const DebugSection = ({
|
||||
state,
|
||||
t,
|
||||
}: {
|
||||
state: ComponentProps<typeof ScreenIntelligenceDebugPanel>['state'];
|
||||
t: (key: string, fallback?: string) => string;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<SettingsSection>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(prev => !prev)}
|
||||
className="flex w-full items-center justify-between text-sm font-semibold text-content">
|
||||
<span>{t('screenAwareness.debug.debugAndDiagnostics')}</span>
|
||||
<span className="text-xs text-content-muted">
|
||||
{isOpen ? t('screenAwareness.debug.collapse') : t('screenAwareness.debug.expand')}
|
||||
</span>
|
||||
</button>
|
||||
{isOpen && <ScreenIntelligenceDebugPanel state={state} />}
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
};
|
||||
|
||||
const ScreenAwarenessDebugPanel = () => {
|
||||
const { t } = useT();
|
||||
const {
|
||||
status,
|
||||
lastError,
|
||||
isLoadingVision,
|
||||
recentVisionSummaries,
|
||||
refreshStatus,
|
||||
refreshVision,
|
||||
runCaptureTest,
|
||||
captureTestResult,
|
||||
isCaptureTestRunning,
|
||||
} = useScreenIntelligenceState({ loadVision: true, visionLimit: 10, pollMs: 2000 });
|
||||
|
||||
const [baselineFps, setBaselineFps] = useState<string>('1');
|
||||
const [useVisionModel, setUseVisionModel] = useState<boolean>(true);
|
||||
const [keepScreenshots, setKeepScreenshots] = useState<boolean>(false);
|
||||
const [allowlistText, setAllowlistText] = useState('');
|
||||
const [denylistText, setDenylistText] = useState('');
|
||||
const [isSavingConfig, setIsSavingConfig] = useState(false);
|
||||
const [configError, setConfigError] = useState<string | null>(null);
|
||||
|
||||
// Initialize form state from server config once on first render where config
|
||||
// is available. After initialization, form state is user-controlled until save.
|
||||
// This runs during render (not in useEffect) so it is synchronous and avoids
|
||||
// the set-state-in-effect lint rule.
|
||||
const initializedRef = useRef(false);
|
||||
if (!initializedRef.current && status?.config) {
|
||||
initializedRef.current = true;
|
||||
// One-time assignment — React batches these with the current render.
|
||||
setBaselineFps(String(status.config.baseline_fps ?? 1));
|
||||
setUseVisionModel(status.config.use_vision_model ?? true);
|
||||
setKeepScreenshots(status.config.keep_screenshots ?? false);
|
||||
setAllowlistText((status.config.allowlist ?? []).join('\n'));
|
||||
setDenylistText((status.config.denylist ?? []).join('\n'));
|
||||
}
|
||||
|
||||
const saveConfig = async () => {
|
||||
if (!isTauri()) return;
|
||||
setConfigError(null);
|
||||
setIsSavingConfig(true);
|
||||
try {
|
||||
const fps = Number(baselineFps);
|
||||
await openhumanUpdateScreenIntelligenceSettings({
|
||||
enabled: status?.config.enabled ?? false,
|
||||
policy_mode:
|
||||
status?.config.policy_mode === 'whitelist_only'
|
||||
? 'whitelist_only'
|
||||
: 'all_except_blacklist',
|
||||
baseline_fps: Number.isFinite(fps) && fps > 0 ? fps : 1,
|
||||
use_vision_model: useVisionModel,
|
||||
keep_screenshots: keepScreenshots,
|
||||
allowlist: allowlistText
|
||||
.split('\n')
|
||||
.map(v => v.trim())
|
||||
.filter(Boolean),
|
||||
denylist: denylistText
|
||||
.split('\n')
|
||||
.map(v => v.trim())
|
||||
.filter(Boolean),
|
||||
});
|
||||
await refreshStatus();
|
||||
} catch (error) {
|
||||
setConfigError(
|
||||
error instanceof Error ? error.message : t('screenAwareness.debug.failedToSave')
|
||||
);
|
||||
} finally {
|
||||
setIsSavingConfig(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsPanel description={t('settings.developerMenu.screenAwareness.desc')}>
|
||||
<>
|
||||
{/* Advanced policy settings */}
|
||||
<SettingsSection title={t('screenAwareness.debug.policyTitle')}>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<SettingsRow
|
||||
htmlFor="screen-baseline-fps"
|
||||
label={t('screenAwareness.debug.baselineFps')}
|
||||
control={
|
||||
<SettingsNumberField
|
||||
id="screen-baseline-fps"
|
||||
min={0.2}
|
||||
max={30}
|
||||
step={0.1}
|
||||
value={baselineFps}
|
||||
onChange={setBaselineFps}
|
||||
onCommit={() => {}}
|
||||
aria-label={t('screenAwareness.debug.baselineFps')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingsRow
|
||||
htmlFor="screen-use-vision-model"
|
||||
label={t('screenAwareness.debug.useVisionModel')}
|
||||
description={t('screenAwareness.debug.useVisionModelDesc')}
|
||||
control={
|
||||
<SettingsCheckbox
|
||||
id="screen-use-vision-model"
|
||||
checked={useVisionModel}
|
||||
onCheckedChange={setUseVisionModel}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingsRow
|
||||
htmlFor="screen-keep-screenshots"
|
||||
label={t('screenAwareness.debug.keepScreenshots')}
|
||||
description={t('screenAwareness.debug.keepScreenshotsDesc')}
|
||||
control={
|
||||
<SettingsCheckbox
|
||||
id="screen-keep-screenshots"
|
||||
checked={keepScreenshots}
|
||||
onCheckedChange={setKeepScreenshots}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs text-content-muted">
|
||||
{t('screenAwareness.debug.allowlist')}
|
||||
</div>
|
||||
<SettingsTextArea
|
||||
value={allowlistText}
|
||||
onChange={event => setAllowlistText(event.target.value)}
|
||||
rows={3}
|
||||
aria-label={t('screenAwareness.debug.allowlist')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs text-content-muted">
|
||||
{t('screenAwareness.debug.denylist')}
|
||||
</div>
|
||||
<SettingsTextArea
|
||||
value={denylistText}
|
||||
onChange={event => setDenylistText(event.target.value)}
|
||||
rows={3}
|
||||
aria-label={t('screenAwareness.debug.denylist')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => void saveConfig()}
|
||||
disabled={isSavingConfig}>
|
||||
{isSavingConfig ? t('common.loading') : t('screenAwareness.debug.saveSettings')}
|
||||
</Button>
|
||||
<SettingsStatusLine saving={false} error={configError} savingLabel="" />
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Session stats */}
|
||||
<SettingsSection title={t('screenAwareness.debug.sessionStats')}>
|
||||
<div className="px-4 py-3 text-sm text-content-muted space-y-1">
|
||||
<div>
|
||||
{t('screenAwareness.debug.framesEphemeral')}: {status?.session.frames_in_memory ?? 0}
|
||||
</div>
|
||||
<div>
|
||||
{t('screenAwareness.debug.panicStop')}:{' '}
|
||||
{status?.session.panic_hotkey ?? t('screenAwareness.debug.defaultPanicHotkey')}
|
||||
</div>
|
||||
<div>
|
||||
{t('screenAwareness.debug.vision')}:{' '}
|
||||
{status?.session.vision_state ?? t('screenAwareness.debug.idle')}
|
||||
</div>
|
||||
<div>
|
||||
{t('screenAwareness.debug.visionQueue')}: {status?.session.vision_queue_depth ?? 0}
|
||||
</div>
|
||||
<div>
|
||||
{t('screenAwareness.debug.lastVision')}:{' '}
|
||||
{status?.session.last_vision_at_ms
|
||||
? new Date(status.session.last_vision_at_ms).toLocaleTimeString()
|
||||
: t('screenAwareness.debug.notAvailable')}
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Vision summaries */}
|
||||
<SettingsSection title={t('screenAwareness.debug.visionSummaries')}>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="xs"
|
||||
onClick={() => void refreshVision(10)}
|
||||
disabled={isLoadingVision}>
|
||||
{isLoadingVision ? t('screenAwareness.debug.refreshing') : t('common.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{recentVisionSummaries.length === 0 ? (
|
||||
<SettingsEmptyState label={t('screenAwareness.debug.noSummaries')} />
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{recentVisionSummaries.map(summary => (
|
||||
<div
|
||||
key={summary.id}
|
||||
className="rounded-xl border border-line bg-surface p-3 text-xs">
|
||||
<div className="text-content-muted">
|
||||
{new Date(summary.captured_at_ms).toLocaleTimeString()} ·{' '}
|
||||
{summary.app_name ?? t('screenAwareness.debug.unknownApp')}
|
||||
{summary.window_title ? ` · ${summary.window_title}` : ''}
|
||||
</div>
|
||||
<div className="mt-1 text-content">{summary.actionable_notes}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Debug & Diagnostics (collapsible) */}
|
||||
<DebugSection
|
||||
t={t}
|
||||
state={{
|
||||
status,
|
||||
recentVisionSummaries,
|
||||
lastError,
|
||||
captureTestResult,
|
||||
isCaptureTestRunning,
|
||||
refreshStatus,
|
||||
refreshVision,
|
||||
runCaptureTest,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Platform unsupported notice */}
|
||||
{status !== null && !status.platform_supported && (
|
||||
<div className="rounded-xl border border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10 p-3 text-sm text-amber-700 dark:text-amber-300">
|
||||
{t('screenAwareness.debug.macosOnly')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error notice */}
|
||||
{lastError && <SettingsStatusLine saving={false} error={lastError} savingLabel="" />}
|
||||
</>
|
||||
</SettingsPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenAwarenessDebugPanel;
|
||||
@@ -1,281 +0,0 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useScreenIntelligenceState } from '../../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { isTauri, openhumanUpdateScreenIntelligenceSettings } from '../../../utils/tauriCommands';
|
||||
import Button from '../../ui/Button';
|
||||
import {
|
||||
SettingsRow,
|
||||
SettingsSection,
|
||||
SettingsSelect,
|
||||
SettingsStatusLine,
|
||||
SettingsSwitch,
|
||||
} from '../controls';
|
||||
import SettingsPanel from '../layout/SettingsPanel';
|
||||
import PermissionsSection from './screen-intelligence/PermissionsSection';
|
||||
|
||||
const formatRemaining = (remainingMs: number | null): string => {
|
||||
if (remainingMs === null || remainingMs <= 0) {
|
||||
return '00:00';
|
||||
}
|
||||
|
||||
const totalSeconds = Math.floor(remainingMs / 1000);
|
||||
const mins = Math.floor(totalSeconds / 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const secs = (totalSeconds % 60).toString().padStart(2, '0');
|
||||
return `${mins}:${secs}`;
|
||||
};
|
||||
|
||||
const ScreenIntelligencePanel = () => {
|
||||
const { t } = useT();
|
||||
const {
|
||||
status,
|
||||
lastRestartSummary,
|
||||
isLoading,
|
||||
isRequestingPermissions,
|
||||
isRestartingCore,
|
||||
isStartingSession,
|
||||
isStoppingSession,
|
||||
isFlushingVision,
|
||||
lastError,
|
||||
refreshStatus,
|
||||
startSession,
|
||||
stopSession,
|
||||
flushVision,
|
||||
requestPermission,
|
||||
refreshPermissionsWithRestart,
|
||||
} = useScreenIntelligenceState({ loadVision: false, pollMs: 2000 });
|
||||
const [featureOverrides, setFeatureOverrides] = useState<{ screen_monitoring?: boolean }>({});
|
||||
const [enabled, setEnabled] = useState<boolean>(false);
|
||||
const [policyMode, setPolicyMode] = useState<'all_except_blacklist' | 'whitelist_only'>(
|
||||
'all_except_blacklist'
|
||||
);
|
||||
const [isSavingConfig, setIsSavingConfig] = useState(false);
|
||||
const [configError, setConfigError] = useState<string | null>(null);
|
||||
|
||||
const lastSyncedConfigSigRef = useRef<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!status?.config) {
|
||||
return;
|
||||
}
|
||||
const sig = JSON.stringify(status.config);
|
||||
if (lastSyncedConfigSigRef.current === sig) {
|
||||
return;
|
||||
}
|
||||
lastSyncedConfigSigRef.current = sig;
|
||||
setEnabled(status.config.enabled ?? false);
|
||||
setPolicyMode(
|
||||
status.config.policy_mode === 'whitelist_only' ? 'whitelist_only' : 'all_except_blacklist'
|
||||
);
|
||||
}, [status?.config]);
|
||||
|
||||
const screenMonitoring =
|
||||
featureOverrides.screen_monitoring ?? status?.features.screen_monitoring ?? true;
|
||||
|
||||
const remaining = useMemo(
|
||||
() => formatRemaining(status?.session.remaining_ms ?? null),
|
||||
[status?.session.remaining_ms]
|
||||
);
|
||||
|
||||
const anyPermissionDenied =
|
||||
status?.permissions.screen_recording === 'denied' ||
|
||||
status?.permissions.accessibility === 'denied' ||
|
||||
status?.permissions.input_monitoring === 'denied';
|
||||
|
||||
const startDisabled =
|
||||
isStartingSession ||
|
||||
isLoading ||
|
||||
!status ||
|
||||
!status.platform_supported ||
|
||||
status.session.active ||
|
||||
status.permissions.accessibility !== 'granted';
|
||||
const stopDisabled = isStoppingSession || !status?.session.active;
|
||||
|
||||
const saveConfig = async () => {
|
||||
if (!isTauri()) return;
|
||||
setConfigError(null);
|
||||
setIsSavingConfig(true);
|
||||
try {
|
||||
await openhumanUpdateScreenIntelligenceSettings({
|
||||
enabled,
|
||||
policy_mode: policyMode,
|
||||
baseline_fps: status?.config.baseline_fps ?? 1,
|
||||
use_vision_model: status?.config.use_vision_model ?? true,
|
||||
keep_screenshots: status?.config.keep_screenshots ?? false,
|
||||
allowlist: status?.config.allowlist ?? [],
|
||||
denylist: status?.config.denylist ?? [],
|
||||
});
|
||||
await refreshStatus();
|
||||
} catch (error) {
|
||||
setConfigError(error instanceof Error ? error.message : 'Failed to save screen intelligence');
|
||||
} finally {
|
||||
setIsSavingConfig(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsPanel description={t('pages.settings.features.screenAwarenessDesc')}>
|
||||
<>
|
||||
{(status?.platform_supported ?? true) && (
|
||||
<PermissionsSection
|
||||
screenRecording={status?.permissions.screen_recording ?? 'unknown'}
|
||||
accessibility={status?.permissions.accessibility ?? 'unknown'}
|
||||
inputMonitoring={status?.permissions.input_monitoring ?? 'unknown'}
|
||||
anyPermissionDenied={anyPermissionDenied ?? false}
|
||||
lastRestartSummary={lastRestartSummary}
|
||||
permissionCheckProcessPath={status?.permission_check_process_path}
|
||||
isRequestingPermissions={isRequestingPermissions}
|
||||
isRestartingCore={isRestartingCore}
|
||||
isLoading={isLoading}
|
||||
requestPermission={requestPermission}
|
||||
refreshPermissionsWithRestart={refreshPermissionsWithRestart}
|
||||
refreshStatus={refreshStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Screen awareness config */}
|
||||
<SettingsSection title={t('settings.features.screenAwareness')}>
|
||||
{/* Enabled toggle */}
|
||||
<SettingsRow
|
||||
htmlFor="screen-intelligence-enabled"
|
||||
label={t('common.enabled')}
|
||||
control={
|
||||
<SettingsSwitch
|
||||
id="screen-intelligence-enabled"
|
||||
checked={enabled}
|
||||
onCheckedChange={setEnabled}
|
||||
aria-label={t('common.enabled')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Policy mode */}
|
||||
<SettingsRow
|
||||
htmlFor="select-policy-mode"
|
||||
label={t('settings.screenAwareness.mode')}
|
||||
control={
|
||||
<SettingsSelect
|
||||
id="select-policy-mode"
|
||||
value={policyMode}
|
||||
onChange={event =>
|
||||
setPolicyMode(
|
||||
event.target.value === 'whitelist_only'
|
||||
? 'whitelist_only'
|
||||
: 'all_except_blacklist'
|
||||
)
|
||||
}
|
||||
inputSize="sm">
|
||||
<option value="all_except_blacklist">
|
||||
{t('settings.screenAwareness.allExceptBlacklist')}
|
||||
</option>
|
||||
<option value="whitelist_only">
|
||||
{t('settings.screenAwareness.whitelistOnly')}
|
||||
</option>
|
||||
</SettingsSelect>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Screen monitoring toggle */}
|
||||
<SettingsRow
|
||||
htmlFor="screen-intelligence-monitoring"
|
||||
label={t('settings.screenAwareness.screenMonitoring')}
|
||||
control={
|
||||
<SettingsSwitch
|
||||
id="screen-intelligence-monitoring"
|
||||
checked={screenMonitoring}
|
||||
onCheckedChange={next =>
|
||||
setFeatureOverrides(current => ({ ...current, screen_monitoring: next }))
|
||||
}
|
||||
aria-label={t('settings.screenAwareness.screenMonitoring')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Save */}
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => void saveConfig()}
|
||||
disabled={isSavingConfig}>
|
||||
{isSavingConfig ? 'Saving…' : t('settings.screenAwareness.saveSettings')}
|
||||
</Button>
|
||||
<SettingsStatusLine
|
||||
saving={false}
|
||||
savedNote={null}
|
||||
error={configError}
|
||||
savingLabel={t('settings.agentAccess.saving')}
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Session controls */}
|
||||
<SettingsSection title={t('settings.screenAwareness.session')}>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<div className="text-sm text-content-secondary space-y-1">
|
||||
<div>
|
||||
{t('settings.screenAwareness.status')}:{' '}
|
||||
{status?.session.active
|
||||
? t('settings.screenAwareness.active')
|
||||
: t('settings.screenAwareness.stopped')}
|
||||
</div>
|
||||
<div>
|
||||
{t('settings.screenAwareness.remaining')}: {remaining}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
void startSession({
|
||||
consent: true,
|
||||
ttl_secs: status?.config.session_ttl_secs ?? 300,
|
||||
screen_monitoring: screenMonitoring,
|
||||
})
|
||||
}
|
||||
disabled={startDisabled}>
|
||||
{isStartingSession ? 'Starting…' : t('settings.screenAwareness.startSession')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
tone="danger"
|
||||
size="sm"
|
||||
onClick={() => void stopSession('manual_stop')}
|
||||
disabled={stopDisabled}>
|
||||
{isStoppingSession ? 'Stopping…' : t('settings.screenAwareness.stopSession')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => void flushVision()}
|
||||
disabled={isFlushingVision || !status?.session.active}>
|
||||
{isFlushingVision ? 'Analyzing…' : t('settings.screenAwareness.analyzeNow')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{status !== null && !status.platform_supported && (
|
||||
<div className="rounded-xl border border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10 p-3 text-sm text-amber-700 dark:text-amber-300">
|
||||
{t('settings.screenAwareness.macosOnly')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lastError && (
|
||||
<div className="rounded-xl border border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10 p-3 text-sm text-red-600 dark:text-red-300">
|
||||
{lastError}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</SettingsPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenIntelligencePanel;
|
||||
@@ -31,13 +31,7 @@ const mockStatus = {
|
||||
last_error: null,
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
hotkey: 'ctrl+space',
|
||||
activation_mode: 'push',
|
||||
ttl_secs: 3600,
|
||||
capture_screen: true,
|
||||
include_app_context: true,
|
||||
};
|
||||
const mockConfig = { hotkey: 'ctrl+space', activation_mode: 'push', ttl_secs: 3600 };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -244,17 +238,12 @@ describe('CompanionPanel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders "Disabled" when capture_screen and include_app_context are false', async () => {
|
||||
invokeMock.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'companion_status') return mockStatus;
|
||||
if (cmd === 'companion_config_get') {
|
||||
return { ...mockConfig, capture_screen: false, include_app_context: false };
|
||||
}
|
||||
throw new Error(`unmocked command: ${cmd}`);
|
||||
});
|
||||
it('does not render screen capture or app context configuration', async () => {
|
||||
renderWithProviders(<CompanionPanel />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('Disabled').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getByText('ctrl+space')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('Screen Capture')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('App Context')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,9 +127,7 @@ describe('DeveloperOptionsPanel — CoreModeBadge', () => {
|
||||
// Knowledge & Memory group was retired. Assert a destination that IS
|
||||
// present: 事件日志 (Event Log).
|
||||
expect(screen.getByText('事件日志')).toBeInTheDocument();
|
||||
// Two screen-awareness rows now exist (the moved settings row + the debug
|
||||
// panel), which collapse to the same zh-CN label — assert at least one.
|
||||
expect(screen.getAllByText('屏幕感知').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('屏幕感知')).not.toBeInTheDocument();
|
||||
// Composio triggers moved to the Connections Composio page — assert a
|
||||
// destination that IS still present: MCP 服务器 (MCP Server).
|
||||
expect(screen.getByText('MCP 服务器')).toBeInTheDocument();
|
||||
|
||||
@@ -97,7 +97,7 @@ const setCoreSession = ({
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
/**
|
||||
* ScreenAwarenessDebugPanel coverage tests.
|
||||
*
|
||||
* Target uncovered lines (from diff-cover report):
|
||||
* 34,38,41,143,181,193,203,205,245,247,251,255-256,261-262,297
|
||||
*
|
||||
* These cover:
|
||||
* - DebugSection toggle (line 34,38,41): expand/collapse
|
||||
* - Config form inputs: baselineFps, use-vision-model checkbox, keep-screenshots (143,181,193)
|
||||
* - saveConfig handler (line 203,205)
|
||||
* - Vision summaries: list rendering, refresh, empty state (245,247,251,255-256,261-262)
|
||||
* - lastError display (297)
|
||||
*/
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Module mocks
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
vi.mock('../../../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (key: string) => key }) }));
|
||||
|
||||
vi.mock('../../hooks/useSettingsNavigation', () => ({
|
||||
useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/intelligence/ScreenIntelligenceDebugPanel', () => ({
|
||||
default: () => <div data-testid="screen-debug-inner">debug-content</div>,
|
||||
}));
|
||||
|
||||
const { mockIsTauri, mockUpdateScreenIntelligenceSettings, mockUseScreenIntelligenceState } =
|
||||
vi.hoisted(() => ({
|
||||
mockIsTauri: vi.fn(() => true),
|
||||
mockUpdateScreenIntelligenceSettings: vi.fn(),
|
||||
mockUseScreenIntelligenceState: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../utils/tauriCommands', () => ({
|
||||
isTauri: mockIsTauri,
|
||||
openhumanUpdateScreenIntelligenceSettings: mockUpdateScreenIntelligenceSettings,
|
||||
}));
|
||||
|
||||
vi.mock('../../../../features/screen-intelligence/useScreenIntelligenceState', () => ({
|
||||
useScreenIntelligenceState: mockUseScreenIntelligenceState,
|
||||
}));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Fixture builders
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
const makeStatus = (overrides: Record<string, unknown> = {}) => ({
|
||||
platform_supported: true,
|
||||
config: {
|
||||
enabled: true,
|
||||
baseline_fps: 1,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: ['TextEdit', 'Xcode'],
|
||||
denylist: ['Safari'],
|
||||
policy_mode: 'all_except_blacklist',
|
||||
...((overrides.config as Record<string, unknown>) ?? {}),
|
||||
},
|
||||
session: {
|
||||
frames_in_memory: 5,
|
||||
panic_hotkey: 'Ctrl+Shift+P',
|
||||
vision_state: 'running',
|
||||
vision_queue_depth: 2,
|
||||
last_vision_at_ms: Date.now() - 3000,
|
||||
},
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const makeVisionSummary = (overrides: Record<string, unknown> = {}) => ({
|
||||
id: 'vs-001',
|
||||
captured_at_ms: Date.now() - 10000,
|
||||
app_name: 'TextEdit',
|
||||
window_title: 'Untitled',
|
||||
actionable_notes: 'User is editing a document.',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const makeDefaultState = (overrides: Record<string, unknown> = {}) => ({
|
||||
status: makeStatus(),
|
||||
lastError: null,
|
||||
isLoadingVision: false,
|
||||
recentVisionSummaries: [],
|
||||
refreshStatus: vi.fn().mockResolvedValue(null),
|
||||
refreshVision: vi.fn().mockResolvedValue([]),
|
||||
runCaptureTest: vi.fn().mockResolvedValue(undefined),
|
||||
captureTestResult: null,
|
||||
isCaptureTestRunning: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
async function renderPanel(stateOverrides: Record<string, unknown> = {}) {
|
||||
mockUseScreenIntelligenceState.mockReturnValue(makeDefaultState(stateOverrides));
|
||||
const { default: ScreenAwarenessDebugPanel } = await import('../ScreenAwarenessDebugPanel');
|
||||
return render(<ScreenAwarenessDebugPanel />);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
describe('ScreenAwarenessDebugPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockIsTauri.mockReturnValue(true);
|
||||
mockUpdateScreenIntelligenceSettings.mockResolvedValue({});
|
||||
});
|
||||
|
||||
// ── DebugSection expand/collapse (lines 34, 38, 41) ───────────────────────
|
||||
|
||||
it('collapses debug section by default and shows expand label (line 34, 38)', async () => {
|
||||
await renderPanel();
|
||||
expect(screen.getByText('screenAwareness.debug.expand')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('screen-debug-inner')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('expands debug section when toggle is clicked (line 41)', async () => {
|
||||
await renderPanel();
|
||||
|
||||
fireEvent.click(screen.getByText('screenAwareness.debug.debugAndDiagnostics'));
|
||||
|
||||
expect(screen.getByText('screenAwareness.debug.collapse')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('screen-debug-inner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('collapses debug section after second click', async () => {
|
||||
await renderPanel();
|
||||
|
||||
const toggleBtn = screen.getByText('screenAwareness.debug.debugAndDiagnostics');
|
||||
fireEvent.click(toggleBtn);
|
||||
fireEvent.click(toggleBtn);
|
||||
|
||||
expect(screen.getByText('screenAwareness.debug.expand')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('screen-debug-inner')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ── Policy settings form: FPS input (line 143) ───────────────────────────
|
||||
|
||||
it('renders baseline FPS input with config value (line 143)', async () => {
|
||||
await renderPanel({
|
||||
status: makeStatus({
|
||||
config: {
|
||||
baseline_fps: 2,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: [],
|
||||
denylist: [],
|
||||
policy_mode: 'all_except_blacklist',
|
||||
enabled: true,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const fpsInput = screen.getByRole('spinbutton') as HTMLInputElement;
|
||||
expect(fpsInput.value).toBe('2');
|
||||
});
|
||||
|
||||
it('updates baseline FPS on change', async () => {
|
||||
await renderPanel();
|
||||
|
||||
const fpsInput = screen.getByRole('spinbutton');
|
||||
fireEvent.change(fpsInput, { target: { value: '5' } });
|
||||
|
||||
expect((fpsInput as HTMLInputElement).value).toBe('5');
|
||||
});
|
||||
|
||||
// ── Use vision model checkbox (line 181) ──────────────────────────────────
|
||||
|
||||
it('renders use-vision-model checkbox checked (line 181)', async () => {
|
||||
await renderPanel();
|
||||
|
||||
// SettingsCheckbox renders a native <input type="checkbox" id="screen-use-vision-model">.
|
||||
// The panel also has a keep-screenshots checkbox, so query by id.
|
||||
const checkbox = document.getElementById('screen-use-vision-model') as HTMLInputElement;
|
||||
expect(checkbox).toBeInTheDocument();
|
||||
// config.use_vision_model=true → checked=true
|
||||
expect(checkbox.checked).toBe(true);
|
||||
expect(screen.getByText('screenAwareness.debug.useVisionModel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ── saveConfig handler (lines 203, 205) ───────────────────────────────────
|
||||
|
||||
it('calls updateScreenIntelligenceSettings when save is clicked (line 203)', async () => {
|
||||
const refreshStatus = vi.fn().mockResolvedValue(null);
|
||||
mockUseScreenIntelligenceState.mockReturnValue(makeDefaultState({ refreshStatus }));
|
||||
|
||||
const { default: ScreenAwarenessDebugPanel } = await import('../ScreenAwarenessDebugPanel');
|
||||
render(<ScreenAwarenessDebugPanel />);
|
||||
|
||||
fireEvent.click(screen.getByText('screenAwareness.debug.saveSettings'));
|
||||
|
||||
await waitFor(() => expect(mockUpdateScreenIntelligenceSettings).toHaveBeenCalled());
|
||||
expect(mockUpdateScreenIntelligenceSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ enabled: true, use_vision_model: true, keep_screenshots: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('shows error when saveConfig throws (line 205)', async () => {
|
||||
mockUpdateScreenIntelligenceSettings.mockRejectedValue(new Error('permission denied'));
|
||||
|
||||
await renderPanel();
|
||||
fireEvent.click(screen.getByText('screenAwareness.debug.saveSettings'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('permission denied')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('skips saveConfig when not in tauri env (line 203)', async () => {
|
||||
mockIsTauri.mockReturnValue(false);
|
||||
await renderPanel();
|
||||
|
||||
fireEvent.click(screen.getByText('screenAwareness.debug.saveSettings'));
|
||||
|
||||
// Should not call update
|
||||
expect(mockUpdateScreenIntelligenceSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ── Vision summaries: empty state (line 251) ──────────────────────────────
|
||||
|
||||
it('shows empty state when no vision summaries (line 251)', async () => {
|
||||
await renderPanel({ recentVisionSummaries: [] });
|
||||
|
||||
expect(screen.getByText('screenAwareness.debug.noSummaries')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ── Vision summaries: list rendering (lines 245, 247, 255-256, 261-262) ───
|
||||
|
||||
it('renders vision summary rows with app name and notes (lines 255-256, 261-262)', async () => {
|
||||
const summary = makeVisionSummary();
|
||||
await renderPanel({ recentVisionSummaries: [summary] });
|
||||
|
||||
// app_name and window_title are text nodes inside a div that also contains the
|
||||
// timestamp and bullet separators — use body.textContent to avoid split-node issues.
|
||||
expect(document.body.textContent).toContain('TextEdit');
|
||||
expect(document.body.textContent).toContain('User is editing a document.');
|
||||
expect(document.body.textContent).toContain('Untitled');
|
||||
});
|
||||
|
||||
it('renders unknownApp when app_name is null (line 261)', async () => {
|
||||
const summary = makeVisionSummary({ app_name: null, window_title: null });
|
||||
await renderPanel({ recentVisionSummaries: [summary] });
|
||||
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.unknownApp');
|
||||
});
|
||||
|
||||
it('calls refreshVision when Refresh button is clicked (line 247)', async () => {
|
||||
const refreshVision = vi.fn().mockResolvedValue([]);
|
||||
await renderPanel({ refreshVision });
|
||||
|
||||
fireEvent.click(screen.getByText('common.refresh'));
|
||||
|
||||
await waitFor(() => expect(refreshVision).toHaveBeenCalledWith(10));
|
||||
});
|
||||
|
||||
it('shows refreshing label while vision is loading (line 245)', async () => {
|
||||
await renderPanel({ isLoadingVision: true });
|
||||
|
||||
expect(screen.getByText('screenAwareness.debug.refreshing')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ── Platform unsupported notice and lastError (line 297) ─────────────────
|
||||
|
||||
it('shows macosOnly notice when platform not supported (line 290-292)', async () => {
|
||||
const status = makeStatus();
|
||||
status.platform_supported = false;
|
||||
await renderPanel({ status });
|
||||
|
||||
expect(screen.getByText('screenAwareness.debug.macosOnly')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows lastError status line when lastError is non-null (line 297)', async () => {
|
||||
await renderPanel({ lastError: 'screen recording permission denied' });
|
||||
|
||||
expect(screen.getByText('screen recording permission denied')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ── Session stats section (lines 215-235) ────────────────────────────────
|
||||
|
||||
it('renders session stats with live values', async () => {
|
||||
await renderPanel();
|
||||
|
||||
// These keys appear as text nodes inside divs that also hold ': <value>' —
|
||||
// use body.textContent to avoid split-text-node matching failures.
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.framesEphemeral');
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.panicStop');
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.vision');
|
||||
});
|
||||
|
||||
it('shows defaultPanicHotkey when panic_hotkey is null', async () => {
|
||||
const status = makeStatus();
|
||||
// At runtime the JSON from core may return null even though the TypeScript type says string.
|
||||
// Force-cast to test the nullish-coalescing fallback branch.
|
||||
(status.session as unknown as Record<string, unknown>).panic_hotkey = null;
|
||||
await renderPanel({ status });
|
||||
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.defaultPanicHotkey');
|
||||
});
|
||||
|
||||
it('shows notAvailable for last_vision_at_ms when null', async () => {
|
||||
const status = makeStatus();
|
||||
// last_vision_at_ms is number | null in the real type, but inferred as number in the fixture.
|
||||
// Force-cast to assign null and test the notAvailable branch.
|
||||
(status.session as unknown as Record<string, unknown>).last_vision_at_ms = null;
|
||||
await renderPanel({ status });
|
||||
|
||||
expect(document.body.textContent).toContain('screenAwareness.debug.notAvailable');
|
||||
});
|
||||
});
|
||||
@@ -1,339 +0,0 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
type ScreenIntelligenceState,
|
||||
useScreenIntelligenceState,
|
||||
} from '../../../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import {
|
||||
type ConfigSnapshot,
|
||||
isTauri,
|
||||
openhumanUpdateScreenIntelligenceSettings,
|
||||
} from '../../../../utils/tauriCommands';
|
||||
import ScreenIntelligencePanel from '../ScreenIntelligencePanel';
|
||||
|
||||
vi.mock('../../../../features/screen-intelligence/useScreenIntelligenceState', () => ({
|
||||
useScreenIntelligenceState: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../utils/tauriCommands', async importOriginal => {
|
||||
const actual = await importOriginal<typeof import('../../../../utils/tauriCommands')>();
|
||||
return {
|
||||
...actual,
|
||||
isTauri: vi.fn(() => true),
|
||||
openhumanUpdateScreenIntelligenceSettings: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const baseState: ScreenIntelligenceState = {
|
||||
status: {
|
||||
platform_supported: true,
|
||||
core_process: { pid: 4242, started_at_ms: 1712700000000 },
|
||||
permissions: {
|
||||
screen_recording: 'granted',
|
||||
accessibility: 'granted',
|
||||
input_monitoring: 'unknown',
|
||||
},
|
||||
features: { screen_monitoring: true },
|
||||
session: {
|
||||
active: false,
|
||||
started_at_ms: null,
|
||||
expires_at_ms: null,
|
||||
remaining_ms: null,
|
||||
ttl_secs: 300,
|
||||
panic_hotkey: 'Cmd+Shift+.',
|
||||
stop_reason: null,
|
||||
frames_in_memory: 0,
|
||||
last_capture_at_ms: null,
|
||||
last_context: null,
|
||||
vision_enabled: true,
|
||||
vision_state: 'idle',
|
||||
vision_queue_depth: 0,
|
||||
last_vision_at_ms: null,
|
||||
last_vision_summary: null,
|
||||
},
|
||||
config: {
|
||||
enabled: false,
|
||||
capture_policy: 'hybrid',
|
||||
policy_mode: 'all_except_blacklist',
|
||||
baseline_fps: 1,
|
||||
vision_enabled: true,
|
||||
session_ttl_secs: 300,
|
||||
panic_stop_hotkey: 'Cmd+Shift+.',
|
||||
autocomplete_enabled: true,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: ['Code'],
|
||||
denylist: ['1Password'],
|
||||
},
|
||||
denylist: ['1Password'],
|
||||
is_context_blocked: false,
|
||||
permission_check_process_path: '/tmp/openhuman-core',
|
||||
},
|
||||
lastRestartSummary: null,
|
||||
recentVisionSummaries: [],
|
||||
captureTestResult: null,
|
||||
isCaptureTestRunning: false,
|
||||
isLoading: false,
|
||||
isRequestingPermissions: false,
|
||||
isRestartingCore: false,
|
||||
isStartingSession: false,
|
||||
isStoppingSession: false,
|
||||
isLoadingVision: false,
|
||||
isFlushingVision: false,
|
||||
lastError: null,
|
||||
refreshStatus: vi.fn().mockResolvedValue(null),
|
||||
requestPermission: vi.fn().mockResolvedValue(null),
|
||||
refreshPermissionsWithRestart: vi.fn().mockResolvedValue(null),
|
||||
startSession: vi.fn().mockResolvedValue(null),
|
||||
stopSession: vi.fn().mockResolvedValue(null),
|
||||
refreshVision: vi.fn().mockResolvedValue([]),
|
||||
flushVision: vi.fn().mockResolvedValue(undefined),
|
||||
runCaptureTest: vi.fn().mockResolvedValue(undefined),
|
||||
clearError: vi.fn(),
|
||||
};
|
||||
|
||||
function renderPanel(state: ScreenIntelligenceState = baseState) {
|
||||
vi.mocked(useScreenIntelligenceState).mockReturnValue(state);
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/settings/screen-intelligence']}>
|
||||
<ScreenIntelligencePanel />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>(res => {
|
||||
resolve = res;
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
describe('ScreenIntelligencePanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(isTauri).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('saves screen awareness settings and refreshes core-backed status', async () => {
|
||||
const deferred = createDeferred<{ result: ConfigSnapshot; logs: [] }>();
|
||||
vi.mocked(openhumanUpdateScreenIntelligenceSettings).mockReturnValueOnce(deferred.promise);
|
||||
|
||||
renderPanel();
|
||||
|
||||
// The SettingsHeader renders "Screen Awareness" as the panel heading; wait for it.
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('Screen Awareness').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
const enabledSwitch = screen.getByRole('switch', { name: 'Enabled' });
|
||||
expect(enabledSwitch).toHaveAttribute('aria-checked', 'false');
|
||||
|
||||
fireEvent.click(enabledSwitch);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save Settings' }));
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Saving…' })).toBeInTheDocument();
|
||||
expect(openhumanUpdateScreenIntelligenceSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ enabled: true, policy_mode: 'all_except_blacklist' })
|
||||
);
|
||||
|
||||
deferred.resolve({
|
||||
result: { config: {}, workspace_dir: '/tmp/workspace', config_path: '/tmp/config.toml' },
|
||||
logs: [],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Save Settings' })).toBeInTheDocument();
|
||||
});
|
||||
expect(baseState.refreshStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('hides permissions section and shows unsupported-platform messaging', async () => {
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: {
|
||||
...baseState.status!,
|
||||
platform_supported: false,
|
||||
permissions: {
|
||||
screen_recording: 'denied',
|
||||
accessibility: 'denied',
|
||||
input_monitoring: 'unknown',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Screen Awareness desktop capture and permission controls are currently supported on macOS only.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('Permissions')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/After granting in System Settings, click/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'Restart & Refresh Permissions' })
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the last successful restart summary', async () => {
|
||||
renderPanel({
|
||||
...baseState,
|
||||
lastRestartSummary: 'Core restarted: PID 4000 at 9:00:00 AM -> PID 4242 at 9:01:00 AM.',
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/Core restarted: PID 4000/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ─── Session controls ─────────────────────────────────────────────────────
|
||||
|
||||
it('Start Session button calls startSession with consent=true', async () => {
|
||||
const startSession = vi.fn().mockResolvedValue(null);
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: {
|
||||
...baseState.status!,
|
||||
// accessibility granted so Start is not disabled
|
||||
permissions: { ...baseState.status!.permissions, accessibility: 'granted' },
|
||||
session: { ...baseState.status!.session, active: false },
|
||||
},
|
||||
startSession,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getAllByText(/start session/i).length).toBeGreaterThan(0));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /start session/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(startSession).toHaveBeenCalledWith(expect.objectContaining({ consent: true }))
|
||||
);
|
||||
});
|
||||
|
||||
it('Stop Session button calls stopSession', async () => {
|
||||
const stopSession = vi.fn().mockResolvedValue(null);
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, session: { ...baseState.status!.session, active: true } },
|
||||
stopSession,
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('button', { name: /stop session/i })).not.toBeDisabled()
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /stop session/i }));
|
||||
|
||||
await waitFor(() => expect(stopSession).toHaveBeenCalledWith('manual_stop'));
|
||||
});
|
||||
|
||||
it('Analyze Now button calls flushVision when session is active', async () => {
|
||||
const flushVision = vi.fn().mockResolvedValue(undefined);
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, session: { ...baseState.status!.session, active: true } },
|
||||
flushVision,
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('button', { name: /analyze now/i })).not.toBeDisabled()
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /analyze now/i }));
|
||||
|
||||
await waitFor(() => expect(flushVision).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('Analyze Now is disabled when session is not active', async () => {
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, session: { ...baseState.status!.session, active: false } },
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('button', { name: /analyze now/i })).toBeDisabled()
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Policy mode select ───────────────────────────────────────────────────
|
||||
|
||||
it('changing policy mode select updates the local state', async () => {
|
||||
renderPanel();
|
||||
|
||||
await waitFor(() => expect(screen.getAllByText(/screen awareness/i).length).toBeGreaterThan(0));
|
||||
|
||||
const policySelect = screen.getByRole('combobox', { name: /mode/i }) as HTMLSelectElement;
|
||||
|
||||
fireEvent.change(policySelect, { target: { value: 'whitelist_only' } });
|
||||
|
||||
expect(policySelect.value).toBe('whitelist_only');
|
||||
});
|
||||
|
||||
it('saves with whitelist_only policy when mode is changed', async () => {
|
||||
const deferred = createDeferred<{ result: ConfigSnapshot; logs: [] }>();
|
||||
vi.mocked(openhumanUpdateScreenIntelligenceSettings).mockReturnValueOnce(deferred.promise);
|
||||
|
||||
renderPanel();
|
||||
|
||||
await waitFor(() => expect(screen.getAllByText(/screen awareness/i).length).toBeGreaterThan(0));
|
||||
|
||||
const policySelect = screen.getByRole('combobox', { name: /mode/i });
|
||||
fireEvent.change(policySelect, { target: { value: 'whitelist_only' } });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save Settings' }));
|
||||
|
||||
expect(vi.mocked(openhumanUpdateScreenIntelligenceSettings)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ policy_mode: 'whitelist_only' })
|
||||
);
|
||||
|
||||
deferred.resolve({
|
||||
result: { config: {}, workspace_dir: '/tmp/workspace', config_path: '/tmp/config.toml' },
|
||||
logs: [],
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Error display ────────────────────────────────────────────────────────
|
||||
|
||||
it('shows lastError when state has an error', async () => {
|
||||
renderPanel({ ...baseState, lastError: 'Permission denied by OS' });
|
||||
|
||||
expect(await screen.findByText('Permission denied by OS')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ─── Screen monitoring toggle ─────────────────────────────────────────────
|
||||
|
||||
it('toggling screen monitoring checkbox updates the override', async () => {
|
||||
renderPanel();
|
||||
|
||||
await waitFor(() => expect(screen.getAllByText(/screen awareness/i).length).toBeGreaterThan(0));
|
||||
|
||||
const monitoringSwitch = screen.getByRole('switch', { name: 'Screen Monitoring' });
|
||||
|
||||
// Initially reflects status value (true in baseState.features)
|
||||
expect(monitoringSwitch).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
fireEvent.click(monitoringSwitch);
|
||||
expect(monitoringSwitch).toHaveAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
// ─── Session status display ───────────────────────────────────────────────
|
||||
|
||||
it('shows Active session status when session is active', async () => {
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, session: { ...baseState.status!.session, active: true } },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/active/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows Stopped session status when session is inactive', async () => {
|
||||
renderPanel({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, session: { ...baseState.status!.session, active: false } },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/stopped/i)).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
@@ -1,144 +0,0 @@
|
||||
import { useT } from '../../../../lib/i18n/I18nContext';
|
||||
import type { AccessibilityPermissionKind } from '../../../../utils/tauriCommands';
|
||||
import Button from '../../../ui/Button';
|
||||
import {
|
||||
SettingsBadge,
|
||||
type SettingsBadgeVariant,
|
||||
SettingsRow,
|
||||
SettingsSection,
|
||||
} from '../../controls';
|
||||
|
||||
const badgeVariant = (value: string): SettingsBadgeVariant =>
|
||||
value === 'granted' ? 'success' : value === 'denied' ? 'danger' : 'neutral';
|
||||
|
||||
interface PermissionsSectionProps {
|
||||
screenRecording: string;
|
||||
accessibility: string;
|
||||
inputMonitoring: string;
|
||||
anyPermissionDenied: boolean;
|
||||
lastRestartSummary: string | null;
|
||||
permissionCheckProcessPath: string | null | undefined;
|
||||
isRequestingPermissions: boolean;
|
||||
isRestartingCore: boolean;
|
||||
isLoading: boolean;
|
||||
requestPermission: (permission: AccessibilityPermissionKind) => Promise<unknown>;
|
||||
refreshPermissionsWithRestart: () => Promise<unknown>;
|
||||
refreshStatus: () => Promise<unknown>;
|
||||
}
|
||||
|
||||
const PermissionsSection = ({
|
||||
screenRecording,
|
||||
accessibility,
|
||||
inputMonitoring,
|
||||
anyPermissionDenied,
|
||||
lastRestartSummary,
|
||||
permissionCheckProcessPath,
|
||||
isRequestingPermissions,
|
||||
isRestartingCore,
|
||||
isLoading,
|
||||
requestPermission,
|
||||
refreshPermissionsWithRestart,
|
||||
refreshStatus,
|
||||
}: PermissionsSectionProps) => {
|
||||
const { t } = useT();
|
||||
return (
|
||||
<SettingsSection title={t('settings.screenIntel.permissions.title')}>
|
||||
<SettingsRow
|
||||
label={t('settings.screenIntel.permissions.screenRecording')}
|
||||
control={
|
||||
<SettingsBadge variant={badgeVariant(screenRecording)}>{screenRecording}</SettingsBadge>
|
||||
}
|
||||
/>
|
||||
<SettingsRow
|
||||
label={t('settings.screenIntel.permissions.accessibility')}
|
||||
control={
|
||||
<SettingsBadge variant={badgeVariant(accessibility)}>{accessibility}</SettingsBadge>
|
||||
}
|
||||
/>
|
||||
<SettingsRow
|
||||
label={t('settings.screenIntel.permissions.inputMonitoring')}
|
||||
control={
|
||||
<SettingsBadge variant={badgeVariant(inputMonitoring)}>{inputMonitoring}</SettingsBadge>
|
||||
}
|
||||
/>
|
||||
|
||||
{anyPermissionDenied && (
|
||||
<div className="px-4 py-3">
|
||||
<div className="space-y-1 rounded-xl border border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10 p-3 text-sm text-amber-700 dark:text-amber-300">
|
||||
<p>{t('settings.screenIntel.permissions.grantHint')}</p>
|
||||
{permissionCheckProcessPath ? (
|
||||
<p className="text-xs opacity-75">
|
||||
{t('settings.screenIntel.permissions.macosAppliesPrivacy')}{' '}
|
||||
<span className="break-all font-mono text-content-secondary">
|
||||
{permissionCheckProcessPath}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lastRestartSummary ? (
|
||||
<div className="px-4 py-3">
|
||||
<div className="rounded-xl border border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-300">
|
||||
{lastRestartSummary}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-wrap gap-2 px-4 py-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => void requestPermission('screen_recording')}
|
||||
disabled={isRequestingPermissions || isRestartingCore}>
|
||||
{isRequestingPermissions
|
||||
? t('settings.screenIntel.permissions.requesting')
|
||||
: t('settings.screenIntel.permissions.requestScreenRecording')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => void requestPermission('accessibility')}
|
||||
disabled={isRequestingPermissions || isRestartingCore}>
|
||||
{isRequestingPermissions
|
||||
? t('settings.screenIntel.permissions.requesting')
|
||||
: t('settings.screenIntel.permissions.requestAccessibility')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => void requestPermission('input_monitoring')}
|
||||
disabled={isRequestingPermissions || isRestartingCore}>
|
||||
{isRequestingPermissions
|
||||
? t('settings.screenIntel.permissions.requesting')
|
||||
: t('settings.screenIntel.permissions.openInputMonitoring')}
|
||||
</Button>
|
||||
{anyPermissionDenied ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
tone="danger"
|
||||
size="sm"
|
||||
onClick={() => void refreshPermissionsWithRestart()}
|
||||
disabled={isRestartingCore || isLoading}>
|
||||
{isRestartingCore
|
||||
? t('settings.screenIntel.permissions.restartingCore')
|
||||
: t('settings.screenIntel.permissions.restartRefresh')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => void refreshStatus()}
|
||||
disabled={isLoading || isRestartingCore}>
|
||||
{isLoading
|
||||
? t('settings.screenIntel.permissions.refreshing')
|
||||
: t('settings.screenIntel.permissions.refreshStatus')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionsSection;
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Tests for the Screen Awareness → Permissions section.
|
||||
*
|
||||
* PermissionsSection is a pure, props-driven presentational component (no
|
||||
* hooks/RPC of its own beyond i18n). These tests exercise both the
|
||||
* "permission denied" branch (denied alert + macOS process-path hint +
|
||||
* Restart & Refresh button) and the "all granted" branch (plain Refresh
|
||||
* Status button), plus the per-permission request buttons and the
|
||||
* granted/denied/unknown badge variants.
|
||||
*/
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import PermissionsSection from '../PermissionsSection';
|
||||
|
||||
type Props = React.ComponentProps<typeof PermissionsSection>;
|
||||
|
||||
const baseProps = (): Props => ({
|
||||
screenRecording: 'granted',
|
||||
accessibility: 'denied',
|
||||
inputMonitoring: 'unknown',
|
||||
anyPermissionDenied: true,
|
||||
lastRestartSummary: null,
|
||||
permissionCheckProcessPath: null,
|
||||
isRequestingPermissions: false,
|
||||
isRestartingCore: false,
|
||||
isLoading: false,
|
||||
requestPermission: vi.fn().mockResolvedValue(null),
|
||||
refreshPermissionsWithRestart: vi.fn().mockResolvedValue(null),
|
||||
refreshStatus: vi.fn().mockResolvedValue(null),
|
||||
});
|
||||
|
||||
describe('PermissionsSection', () => {
|
||||
it('renders denied alert with process path, restart summary, and request buttons', () => {
|
||||
const props = {
|
||||
...baseProps(),
|
||||
anyPermissionDenied: true,
|
||||
permissionCheckProcessPath: '/tmp/openhuman-core',
|
||||
lastRestartSummary: 'Core restarted: PID 4000 -> PID 4242.',
|
||||
};
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
// Mixed badge variants render (granted/denied/unknown).
|
||||
expect(screen.getByText('granted')).toBeInTheDocument();
|
||||
expect(screen.getByText('denied')).toBeInTheDocument();
|
||||
expect(screen.getByText('unknown')).toBeInTheDocument();
|
||||
|
||||
// Denied-alert block surfaces the macOS process path (line ~69).
|
||||
expect(screen.getByText('/tmp/openhuman-core')).toBeInTheDocument();
|
||||
|
||||
// Last restart summary is shown.
|
||||
expect(screen.getByText('Core restarted: PID 4000 -> PID 4242.')).toBeInTheDocument();
|
||||
|
||||
// The three per-permission request buttons render.
|
||||
expect(screen.getByRole('button', { name: 'Request Screen Recording' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Request Accessibility' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Input Monitoring' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('invokes the request handlers and the restart-with-refresh handler when denied', () => {
|
||||
const props = { ...baseProps(), anyPermissionDenied: true };
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Request Screen Recording' }));
|
||||
expect(props.requestPermission).toHaveBeenCalledWith('screen_recording');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Request Accessibility' }));
|
||||
expect(props.requestPermission).toHaveBeenCalledWith('accessibility');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open Input Monitoring' }));
|
||||
expect(props.requestPermission).toHaveBeenCalledWith('input_monitoring');
|
||||
|
||||
// When permissions are denied, the danger Restart & Refresh button shows.
|
||||
const restartBtn = screen.getByRole('button', { name: 'Restarting core…' });
|
||||
fireEvent.click(restartBtn);
|
||||
expect(props.refreshPermissionsWithRestart).toHaveBeenCalledTimes(1);
|
||||
expect(props.refreshStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows the plain Refresh Status button when nothing is denied', () => {
|
||||
const props = {
|
||||
...baseProps(),
|
||||
screenRecording: 'granted',
|
||||
accessibility: 'granted',
|
||||
inputMonitoring: 'granted',
|
||||
anyPermissionDenied: false,
|
||||
permissionCheckProcessPath: null,
|
||||
};
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
// The denied-alert process path is absent when nothing is denied.
|
||||
expect(screen.queryByText('/tmp/openhuman-core')).not.toBeInTheDocument();
|
||||
|
||||
// The restart-with-refresh button is replaced by the plain refresh button.
|
||||
expect(screen.queryByRole('button', { name: 'Restarting core…' })).not.toBeInTheDocument();
|
||||
|
||||
const refreshBtn = screen.getByRole('button', { name: 'Refreshing…' });
|
||||
fireEvent.click(refreshBtn);
|
||||
expect(props.refreshStatus).toHaveBeenCalledTimes(1);
|
||||
expect(props.refreshPermissionsWithRestart).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reflects in-flight labels while requesting permissions', () => {
|
||||
const props = { ...baseProps(), isRequestingPermissions: true };
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
// All three request buttons collapse to the "Requesting…" label.
|
||||
expect(screen.getAllByRole('button', { name: 'Requesting…' })).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('shows the restarting-core label and disables the restart button while restarting', () => {
|
||||
// Exercises the `isRestartingCore ? …` branch of the denied restart button.
|
||||
const props = { ...baseProps(), anyPermissionDenied: true, isRestartingCore: true };
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
const restartBtn = screen.getByRole('button', { name: 'Restarting core…' });
|
||||
expect(restartBtn).toBeDisabled();
|
||||
// While restarting, the request buttons are also disabled.
|
||||
expect(screen.getByRole('button', { name: 'Request Screen Recording' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows the refreshing label and disables the refresh button while loading', () => {
|
||||
// Exercises the `isLoading ? …` branch of the not-denied refresh button.
|
||||
const props = { ...baseProps(), anyPermissionDenied: false, isLoading: true };
|
||||
render(<PermissionsSection {...props} />);
|
||||
|
||||
const refreshBtn = screen.getByRole('button', { name: 'Refreshing…' });
|
||||
expect(refreshBtn).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { HashRouter, Outlet, Route, Routes } from 'react-router-dom';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { settingsRouteElements } from './settingsRouteElements';
|
||||
|
||||
describe.each([
|
||||
'/settings/features',
|
||||
'/settings/screen-intelligence',
|
||||
'/settings/screen-awareness-debug',
|
||||
])('retired screen settings route %s', route => {
|
||||
it('normalizes to the settings index without rendering removed content', async () => {
|
||||
window.location.hash = `#${route}`;
|
||||
|
||||
render(
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/settings" element={<Outlet />}>
|
||||
{settingsRouteElements()}
|
||||
</Route>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => expect(window.location.hash).toBe('#/settings'));
|
||||
expect(screen.queryByText(/screen intelligence/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/screen awareness debug/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -30,7 +30,6 @@ import ProfileEditorPage from './panels/ProfileEditorPage';
|
||||
import ProfilesPanel from './panels/ProfilesPanel';
|
||||
import RecoveryPhrasePanel from './panels/RecoveryPhrasePanel';
|
||||
import SandboxSettingsPanel from './panels/SandboxSettingsPanel';
|
||||
import ScreenAwarenessDebugPanel from './panels/ScreenAwarenessDebugPanel';
|
||||
import SecurityPanel from './panels/SecurityPanel';
|
||||
import TeamInvitesPanel from './panels/TeamInvitesPanel';
|
||||
import TeamManagementPanel from './panels/TeamManagementPanel';
|
||||
@@ -135,12 +134,6 @@ export function settingsRouteElements(): ReactNode {
|
||||
{/* The Integrations settings section was retired; the composio/OAuth grid
|
||||
lives on the Connections page. */}
|
||||
<Route path="integrations" element={<Navigate to="/connections" replace />} />
|
||||
{/* Screen Awareness / Desktop Agent / Desktop Companion moved to the
|
||||
Connections page (Desktop group). */}
|
||||
<Route
|
||||
path="screen-intelligence"
|
||||
element={<Navigate to="/connections?tab=screen-intelligence" replace />}
|
||||
/>
|
||||
<Route path="tools" element={wrapSettingsPage(<ToolsPanel />)} />
|
||||
<Route path="companion" element={<Navigate to="/connections?tab=companion" replace />} />
|
||||
{/* Meeting settings moved to the Connections page (meetings tab). */}
|
||||
@@ -180,10 +173,6 @@ export function settingsRouteElements(): ReactNode {
|
||||
{/* Dev Workflow panel retired — superseded by Workflows (/flows). */}
|
||||
<Route path="dev-workflow" element={<Navigate to="/flows" replace />} />
|
||||
<Route path="skills-runner" element={wrapSettingsPage(<WorkflowRunnerPanel />)} />
|
||||
<Route
|
||||
path="screen-awareness-debug"
|
||||
element={wrapSettingsPage(<ScreenAwarenessDebugPanel />)}
|
||||
/>
|
||||
{/* Voice Debug page retired. */}
|
||||
<Route path="voice-debug" element={<SettingsRedirect to="/settings/developer-options" />} />
|
||||
{/* Local Model Debug is a chip on the Connections → LLM page. */}
|
||||
@@ -214,10 +203,6 @@ export function settingsRouteElements(): ReactNode {
|
||||
{/* Old hub pages */}
|
||||
<Route path="ai" element={<Navigate to="/connections?tab=llm" replace />} />
|
||||
<Route path="agents-settings" element={<SettingsRedirect to="/settings/agents" />} />
|
||||
<Route
|
||||
path="features"
|
||||
element={<Navigate to="/connections?tab=screen-intelligence" replace />}
|
||||
/>
|
||||
<Route path="crypto" element={<Navigate to="/connections?tab=wallet" replace />} />
|
||||
<Route path="notifications-hub" element={<SettingsRedirect to="/settings/notifications" />} />
|
||||
{/* Composio (API key + routing) moved to Connections → API keys. */}
|
||||
|
||||
@@ -230,8 +230,8 @@ export const SETTINGS_ROUTE_REGISTRY: SettingsRegistryEntry[] = [
|
||||
// --- Connections group ---
|
||||
// The Integrations settings section was retired — the composio/OAuth grid
|
||||
// lives on the Connections page and the task-source/webhook triage surface is
|
||||
// no longer used. Screen Awareness, Desktop Agent and Desktop Companion moved
|
||||
// to the Connections page's Desktop group; their slugs redirect there.
|
||||
// no longer used. Desktop Agent and Desktop Companion moved to the
|
||||
// Connections page's Desktop group; their slugs redirect there.
|
||||
|
||||
// Notifications-hub and crypto hub pages are retired — their slugs redirect
|
||||
// to /settings/notifications and /settings/wallet-balances.
|
||||
@@ -450,15 +450,6 @@ export const SETTINGS_ROUTE_REGISTRY: SettingsRegistryEntry[] = [
|
||||
// =========================================================================
|
||||
// FEATURES section leaf panels
|
||||
// =========================================================================
|
||||
{
|
||||
// Surfaced on the Connections page (Desktop group); route redirects there.
|
||||
// Entry kept for search + deep-link compatibility.
|
||||
id: 'screen-intelligence',
|
||||
titleKey: 'pages.settings.features.screenAwareness',
|
||||
descriptionKey: 'pages.settings.features.screenAwarenessDesc',
|
||||
section: 'features',
|
||||
searchKeywords: ['screen', 'awareness', 'vision', 'capture'],
|
||||
},
|
||||
{
|
||||
id: 'tools',
|
||||
titleKey: 'pages.settings.features.tools',
|
||||
@@ -472,7 +463,7 @@ export const SETTINGS_ROUTE_REGISTRY: SettingsRegistryEntry[] = [
|
||||
// Surfaced on the Connections page (Desktop group); route redirects there.
|
||||
id: 'companion',
|
||||
titleKey: 'pages.settings.features.desktopCompanion',
|
||||
descriptionKey: 'pages.settings.features.desktopCompanionDesc',
|
||||
descriptionKey: 'settings.assistant.desktopCompanionDesc',
|
||||
section: 'features',
|
||||
searchKeywords: ['desktop', 'overlay', 'companion'],
|
||||
},
|
||||
@@ -538,7 +529,7 @@ export const SETTINGS_ROUTE_REGISTRY: SettingsRegistryEntry[] = [
|
||||
// These live ONLY under Settings → Developer & Diagnostics.
|
||||
// Items removed from this list compared to the old DeveloperOptionsPanel:
|
||||
// agents, autonomy, agent-access, sandbox-settings, activity-level,
|
||||
// tools, companion, screen-intelligence, voice, embeddings, heartbeat,
|
||||
// tools, companion, voice, embeddings, heartbeat,
|
||||
// ledger-usage, cost-dashboard, task-sources, composio-routing,
|
||||
// webhooks-triggers, migration, security
|
||||
// (all moved to their canonical section pages).
|
||||
@@ -557,14 +548,6 @@ export const SETTINGS_ROUTE_REGISTRY: SettingsRegistryEntry[] = [
|
||||
// Knowledge & Memory group retired entirely — memory surfaces live on the
|
||||
// Brain page (graph / goals / sources / sync / subconscious).
|
||||
// voice-debug retired from the settings UI.
|
||||
{
|
||||
id: 'screen-awareness-debug',
|
||||
titleKey: 'settings.developerMenu.screenAwareness.title',
|
||||
descriptionKey: 'settings.developerMenu.screenAwareness.desc',
|
||||
section: 'developer',
|
||||
devOnly: true,
|
||||
navGroup: 'modelsInference',
|
||||
},
|
||||
{
|
||||
id: 'event-log',
|
||||
titleKey: 'settings.developerMenu.eventLog.title',
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
/**
|
||||
* Screen Intelligence setup/enable modal.
|
||||
*
|
||||
* Guides the user through permission grants, enables the feature,
|
||||
* and shows a success confirmation — matching the UX of third-party
|
||||
* skill setup flows (Gmail, etc.).
|
||||
*/
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useScreenIntelligenceState } from '../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
import { openhumanUpdateScreenIntelligenceSettings } from '../../utils/tauriCommands';
|
||||
import { settingsNavState } from '../settings/modal/settingsOverlay';
|
||||
import { CheckIcon } from '../ui';
|
||||
import Button from '../ui/Button';
|
||||
import {
|
||||
SetupNotice,
|
||||
SetupSettingRow,
|
||||
SetupSuccess,
|
||||
SkillSetupModalShell,
|
||||
} from './SkillSetupPrimitives';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type Step = 'permissions' | 'enable' | 'success';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
/** Skip straight to manage mode when permissions are already granted. */
|
||||
initialStep?: Step;
|
||||
}
|
||||
|
||||
// ─── Permission badge (reusable) ──────────────────────────────────────────────
|
||||
|
||||
const PermissionRow = ({
|
||||
label,
|
||||
value,
|
||||
onRequest,
|
||||
isRequesting,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onRequest: () => void;
|
||||
isRequesting: boolean;
|
||||
}) => {
|
||||
const { t } = useT();
|
||||
const granted = value === 'granted';
|
||||
const badgeColor = granted
|
||||
? 'bg-sage-50 text-sage-700 border-sage-200'
|
||||
: value === 'denied'
|
||||
? 'bg-coral-50 text-coral-700 border-coral-200'
|
||||
: 'bg-surface-subtle text-content-secondary border-line';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between rounded-xl border border-line bg-surface px-3 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
{granted ? (
|
||||
<CheckIcon className="w-4 h-4 text-sage-500" />
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4 text-content-faint"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" strokeWidth={2} />
|
||||
</svg>
|
||||
)}
|
||||
<span className="text-sm text-content-secondary">{label}</span>
|
||||
</div>
|
||||
{granted ? (
|
||||
<span
|
||||
className={`rounded-md border px-2 py-0.5 text-[10px] uppercase tracking-wide ${badgeColor}`}>
|
||||
{t('skills.setup.screenIntel.granted')}
|
||||
</span>
|
||||
) : (
|
||||
<Button variant="secondary" size="xs" disabled={isRequesting} onClick={onRequest}>
|
||||
{isRequesting
|
||||
? t('skills.setup.screenIntel.opening')
|
||||
: t('skills.setup.screenIntel.grant')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Modal ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function ScreenIntelligenceSetupModal({ onClose, initialStep }: Props) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { t } = useT();
|
||||
const {
|
||||
status,
|
||||
isRequestingPermissions,
|
||||
isRestartingCore,
|
||||
lastRestartSummary,
|
||||
lastError,
|
||||
requestPermission,
|
||||
refreshPermissionsWithRestart,
|
||||
refreshStatus,
|
||||
} = useScreenIntelligenceState({ loadVision: false });
|
||||
|
||||
const [isEnabling, setIsEnabling] = useState(false);
|
||||
const [enableError, setEnableError] = useState<string | null>(null);
|
||||
|
||||
const allGranted = useMemo(() => {
|
||||
if (!status) return false;
|
||||
return (
|
||||
status.permissions.screen_recording === 'granted' &&
|
||||
status.permissions.accessibility === 'granted' &&
|
||||
status.permissions.input_monitoring === 'granted'
|
||||
);
|
||||
}, [status]);
|
||||
|
||||
const anyDenied = useMemo(() => {
|
||||
if (!status) return false;
|
||||
return (
|
||||
status.permissions.screen_recording === 'denied' ||
|
||||
status.permissions.accessibility === 'denied' ||
|
||||
status.permissions.input_monitoring === 'denied'
|
||||
);
|
||||
}, [status]);
|
||||
|
||||
// Derive current step
|
||||
const [step, setStep] = useState<Step>(initialStep ?? 'permissions');
|
||||
|
||||
// Auto-advance: when permissions are all granted, move past the permissions step
|
||||
useEffect(() => {
|
||||
if (step === 'permissions' && allGranted) {
|
||||
setStep('enable');
|
||||
}
|
||||
}, [step, allGranted]);
|
||||
|
||||
const handleEnable = async () => {
|
||||
setIsEnabling(true);
|
||||
setEnableError(null);
|
||||
try {
|
||||
await openhumanUpdateScreenIntelligenceSettings({ enabled: true });
|
||||
await refreshStatus();
|
||||
setStep('success');
|
||||
} catch (error) {
|
||||
setEnableError(
|
||||
error instanceof Error ? error.message : t('skills.setup.screenIntel.enableError')
|
||||
);
|
||||
} finally {
|
||||
setIsEnabling(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoToSettings = () => {
|
||||
onClose();
|
||||
navigate('/settings/screen-intelligence', settingsNavState(location));
|
||||
};
|
||||
|
||||
if (status?.platform_supported === false) {
|
||||
return (
|
||||
<SkillSetupModalShell
|
||||
onClose={onClose}
|
||||
title={t('skills.setup.screenIntel.title')}
|
||||
titleId="si-setup-title"
|
||||
icon={
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.8}
|
||||
d="M3 5h18v12H3zM8 21h8m-4-4v4"
|
||||
/>
|
||||
</svg>
|
||||
}>
|
||||
<div className="space-y-4 py-2">
|
||||
<p className="text-sm text-content-secondary leading-relaxed">
|
||||
{t('skills.setup.screenIntel.macosOnly')}
|
||||
</p>
|
||||
<Button variant="secondary" size="lg" onClick={onClose} className="w-full">
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
</div>
|
||||
</SkillSetupModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SkillSetupModalShell
|
||||
onClose={onClose}
|
||||
title={t('skills.setup.screenIntel.title')}
|
||||
titleId="si-setup-title"
|
||||
subtitle={
|
||||
<>
|
||||
{step === 'permissions' && t('skills.setup.screenIntel.stepPermissions')}
|
||||
{step === 'enable' && t('skills.setup.screenIntel.stepEnable')}
|
||||
{step === 'success' && t('skills.setup.screenIntel.stepSuccess')}
|
||||
</>
|
||||
}
|
||||
icon={
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.8}
|
||||
d="M3 5h18v12H3zM8 21h8m-4-4v4"
|
||||
/>
|
||||
</svg>
|
||||
}>
|
||||
{/* ─── Step 1: Permissions ─── */}
|
||||
{step === 'permissions' && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs text-content-muted leading-relaxed">
|
||||
{t('skills.setup.screenIntel.permissionsDesc')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
<PermissionRow
|
||||
label={t('skills.setup.screenIntel.permScreenRecording')}
|
||||
value={status?.permissions.screen_recording ?? 'unknown'}
|
||||
onRequest={() => void requestPermission('screen_recording')}
|
||||
isRequesting={isRequestingPermissions}
|
||||
/>
|
||||
<PermissionRow
|
||||
label={t('skills.setup.screenIntel.permAccessibility')}
|
||||
value={status?.permissions.accessibility ?? 'unknown'}
|
||||
onRequest={() => void requestPermission('accessibility')}
|
||||
isRequesting={isRequestingPermissions}
|
||||
/>
|
||||
<PermissionRow
|
||||
label={t('skills.setup.screenIntel.permInputMonitoring')}
|
||||
value={status?.permissions.input_monitoring ?? 'unknown'}
|
||||
onRequest={() => void requestPermission('input_monitoring')}
|
||||
isRequesting={isRequestingPermissions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{anyDenied && (
|
||||
<SetupNotice tone="amber" className="leading-relaxed">
|
||||
<p>{t('skills.setup.screenIntel.deniedHint')}</p>
|
||||
{status?.permission_check_process_path && (
|
||||
<p className="mt-1 opacity-75 text-[10px]">
|
||||
{t('skills.setup.screenIntel.permissionPathLabel')}{' '}
|
||||
<span className="font-mono break-all text-content-secondary">
|
||||
{status.permission_check_process_path}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</SetupNotice>
|
||||
)}
|
||||
|
||||
{lastRestartSummary && <SetupNotice tone="sage">{lastRestartSummary}</SetupNotice>}
|
||||
|
||||
{lastError && <SetupNotice tone="coral">{lastError}</SetupNotice>}
|
||||
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
{anyDenied ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void refreshPermissionsWithRestart()}
|
||||
disabled={isRestartingCore}
|
||||
className="flex-1 rounded-xl border border-amber-300 bg-amber-50 px-3 py-2.5 text-sm font-medium text-amber-700 hover:bg-amber-100 disabled:opacity-50 transition-colors">
|
||||
{isRestartingCore
|
||||
? t('skills.setup.screenIntel.restarting')
|
||||
: t('skills.setup.screenIntel.restartRefresh')}
|
||||
</button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={() => void refreshStatus()}
|
||||
disabled={isRestartingCore}
|
||||
className="flex-1">
|
||||
{t('skills.setup.screenIntel.refreshStatus')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── Step 2: Enable ─── */}
|
||||
{step === 'enable' && (
|
||||
<div className="space-y-4">
|
||||
<SetupNotice tone="sage" icon={<CheckIcon className="w-4 h-4 text-sage-500" />}>
|
||||
{t('skills.setup.screenIntel.allGranted')}
|
||||
</SetupNotice>
|
||||
|
||||
<p className="text-xs text-content-muted leading-relaxed">
|
||||
{t('skills.setup.screenIntel.enableDesc')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
<SetupSettingRow
|
||||
label={t('skills.setup.screenIntel.captureMode')}
|
||||
value={t('skills.setup.screenIntel.captureModeValue')}
|
||||
/>
|
||||
<SetupSettingRow
|
||||
label={t('skills.setup.screenIntel.visionModel')}
|
||||
value={t('common.enabled')}
|
||||
/>
|
||||
<SetupSettingRow
|
||||
label={t('skills.setup.screenIntel.panicHotkey')}
|
||||
value={status?.session.panic_hotkey ?? 'Cmd+Shift+.'}
|
||||
mono
|
||||
/>
|
||||
</div>
|
||||
|
||||
{enableError && <SetupNotice tone="coral">{enableError}</SetupNotice>}
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => void handleEnable()}
|
||||
disabled={isEnabling}
|
||||
className="w-full">
|
||||
{isEnabling
|
||||
? t('skills.setup.screenIntel.enabling')
|
||||
: t('skills.setup.screenIntel.enableBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── Step 3: Success ─── */}
|
||||
{step === 'success' && (
|
||||
<SetupSuccess
|
||||
title={t('skills.setup.screenIntel.activeTitle')}
|
||||
description={t('skills.setup.screenIntel.activeDesc')}
|
||||
settingsLabel={t('skills.setup.screenIntel.advancedSettings')}
|
||||
finishLabel={t('common.finish')}
|
||||
onSettings={handleGoToSettings}
|
||||
onFinish={onClose}
|
||||
/>
|
||||
)}
|
||||
</SkillSetupModalShell>
|
||||
);
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
type ScreenIntelligenceState,
|
||||
useScreenIntelligenceState,
|
||||
} from '../../../features/screen-intelligence/useScreenIntelligenceState';
|
||||
import ScreenIntelligenceSetupModal from '../ScreenIntelligenceSetupModal';
|
||||
|
||||
vi.mock('../../../features/screen-intelligence/useScreenIntelligenceState', () => ({
|
||||
useScreenIntelligenceState: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual<Record<string, unknown>>('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: vi.fn(() => vi.fn()),
|
||||
useLocation: () => ({
|
||||
pathname: '/connections',
|
||||
search: '',
|
||||
hash: '',
|
||||
state: null,
|
||||
key: 'test',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../../utils/tauriCommands', async () => {
|
||||
const actual = await vi.importActual<Record<string, unknown>>('../../../utils/tauriCommands');
|
||||
return {
|
||||
...actual,
|
||||
openhumanUpdateScreenIntelligenceSettings: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
});
|
||||
|
||||
const baseState: ScreenIntelligenceState = {
|
||||
status: {
|
||||
platform_supported: true,
|
||||
permissions: {
|
||||
screen_recording: 'unknown',
|
||||
accessibility: 'unknown',
|
||||
input_monitoring: 'unknown',
|
||||
},
|
||||
features: { screen_monitoring: false },
|
||||
session: {
|
||||
active: false,
|
||||
started_at_ms: null,
|
||||
expires_at_ms: null,
|
||||
remaining_ms: null,
|
||||
ttl_secs: 300,
|
||||
panic_hotkey: 'Cmd+Shift+.',
|
||||
stop_reason: null,
|
||||
frames_in_memory: 0,
|
||||
last_capture_at_ms: null,
|
||||
last_context: null,
|
||||
vision_enabled: true,
|
||||
vision_state: 'idle',
|
||||
vision_queue_depth: 0,
|
||||
last_vision_at_ms: null,
|
||||
last_vision_summary: null,
|
||||
},
|
||||
config: {
|
||||
enabled: false,
|
||||
capture_policy: 'hybrid',
|
||||
policy_mode: 'all_except_blacklist',
|
||||
baseline_fps: 1,
|
||||
vision_enabled: true,
|
||||
session_ttl_secs: 300,
|
||||
panic_stop_hotkey: 'Cmd+Shift+.',
|
||||
autocomplete_enabled: true,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: [],
|
||||
denylist: [],
|
||||
},
|
||||
denylist: [],
|
||||
is_context_blocked: false,
|
||||
},
|
||||
lastRestartSummary: null,
|
||||
recentVisionSummaries: [],
|
||||
captureTestResult: null,
|
||||
isCaptureTestRunning: false,
|
||||
isLoading: false,
|
||||
isRequestingPermissions: false,
|
||||
isRestartingCore: false,
|
||||
isStartingSession: false,
|
||||
isStoppingSession: false,
|
||||
isLoadingVision: false,
|
||||
isFlushingVision: false,
|
||||
lastError: null,
|
||||
refreshStatus: vi.fn().mockResolvedValue(null),
|
||||
requestPermission: vi.fn().mockResolvedValue(null),
|
||||
refreshPermissionsWithRestart: vi.fn().mockResolvedValue(null),
|
||||
startSession: vi.fn().mockResolvedValue(null),
|
||||
stopSession: vi.fn().mockResolvedValue(null),
|
||||
refreshVision: vi.fn().mockResolvedValue([]),
|
||||
flushVision: vi.fn().mockResolvedValue(undefined),
|
||||
runCaptureTest: vi.fn().mockResolvedValue(undefined),
|
||||
clearError: vi.fn(),
|
||||
};
|
||||
|
||||
describe('ScreenIntelligenceSetupModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows macOS-only message and a Close button when platform_supported is false', () => {
|
||||
vi.mocked(useScreenIntelligenceState).mockReturnValue({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, platform_supported: false },
|
||||
});
|
||||
|
||||
render(<ScreenIntelligenceSetupModal onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText(/macOS only/i)).toBeInTheDocument();
|
||||
expect(screen.getByText('Close', { selector: 'button' })).toBeInTheDocument();
|
||||
expect(screen.queryByText('Grant permissions')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Screen Recording')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClose when the Close button is clicked on the unsupported-platform screen', () => {
|
||||
const onClose = vi.fn();
|
||||
vi.mocked(useScreenIntelligenceState).mockReturnValue({
|
||||
...baseState,
|
||||
status: { ...baseState.status!, platform_supported: false },
|
||||
});
|
||||
|
||||
render(<ScreenIntelligenceSetupModal onClose={onClose} />);
|
||||
|
||||
screen.getByText('Close', { selector: 'button' }).click();
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders the permissions setup flow when platform_supported is true', () => {
|
||||
vi.mocked(useScreenIntelligenceState).mockReturnValue(baseState);
|
||||
|
||||
render(<ScreenIntelligenceSetupModal onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText('Grant permissions')).toBeInTheDocument();
|
||||
expect(screen.getByText('Screen Recording')).toBeInTheDocument();
|
||||
expect(screen.getByText('Accessibility')).toBeInTheDocument();
|
||||
expect(screen.getByText('Input Monitoring')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/macOS only/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
LuKeyboard,
|
||||
LuMessageSquareMore,
|
||||
LuMic,
|
||||
LuMonitor,
|
||||
LuPlugZap,
|
||||
LuShare2,
|
||||
LuSparkles,
|
||||
@@ -180,7 +179,6 @@ export function skillCategoryHeadingClassName(category: SkillCategory): string {
|
||||
}
|
||||
|
||||
export const BUILT_IN_SKILL_ICONS = {
|
||||
screenIntelligence: <LuMonitor className="h-5 w-5" aria-hidden="true" />,
|
||||
textAutocomplete: <LuKeyboard className="h-5 w-5" aria-hidden="true" />,
|
||||
voiceStt: <LuMic className="h-5 w-5" aria-hidden="true" />,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ import { visemePath, VISEMES, type VisemeShape } from './visemes';
|
||||
* - `celebrating` — success animation after meaningful work.
|
||||
* - `writing` — agent is editing/creating files.
|
||||
* - `reading` — agent is browsing or reading content.
|
||||
* - `recording` — agent is capturing screen or recording.
|
||||
* - `waving` — greeting or hello gesture.
|
||||
* - `dancing` — celebratory/playful animation.
|
||||
* - `drinking_coffee` — agent is processing / long-running task.
|
||||
@@ -50,7 +49,6 @@ export type MascotFace =
|
||||
| 'celebrating'
|
||||
| 'writing'
|
||||
| 'reading'
|
||||
| 'recording'
|
||||
| 'waving'
|
||||
| 'dancing'
|
||||
| 'drinking_coffee'
|
||||
@@ -216,14 +214,6 @@ const FACE_PRESETS: Record<Exclude<MascotFace, 'normal'>, FacePreset> = {
|
||||
showBrows: true,
|
||||
blushOpacity: 0.75,
|
||||
},
|
||||
recording: {
|
||||
eyeScaleY: 1,
|
||||
eyeScaleX: 1,
|
||||
browTilt: 0,
|
||||
browDy: 0,
|
||||
showBrows: false,
|
||||
blushOpacity: 0.9,
|
||||
},
|
||||
waving: {
|
||||
eyeScaleY: 0.5,
|
||||
eyeScaleX: 1.1,
|
||||
@@ -489,8 +479,6 @@ function restMouthPath(face: MascotFace): string {
|
||||
case 'writing':
|
||||
case 'reading':
|
||||
return 'M488,585 Q520,595 552,585 Q520,592 488,585 Z';
|
||||
case 'recording':
|
||||
return 'M495,580 Q520,600 545,580 Q520,615 495,580 Z';
|
||||
case 'drinking_coffee':
|
||||
case 'drinking_boba':
|
||||
return 'M500,578 Q520,598 540,578 Q520,610 500,578 Z';
|
||||
|
||||
@@ -28,7 +28,6 @@ export const RIVE_POSES = [
|
||||
'coffeedrink',
|
||||
'writing',
|
||||
'bobbateadrink',
|
||||
'recording',
|
||||
'hand_wave',
|
||||
'dancing',
|
||||
] as const;
|
||||
@@ -53,7 +52,6 @@ export const FACE_TO_POSE: Record<MascotFace, RivePose> = {
|
||||
celebrating: 'celebration',
|
||||
writing: 'writing',
|
||||
reading: 'bookreading',
|
||||
recording: 'recording',
|
||||
waving: 'hand_wave',
|
||||
dancing: 'dancing',
|
||||
drinking_coffee: 'coffeedrink',
|
||||
@@ -159,8 +157,8 @@ export function toRiveVisemeCode(code: string): RiveVisemeCode {
|
||||
|
||||
/**
|
||||
* Poses the mascot drifts through on its own while otherwise idle, to keep it
|
||||
* feeling alive. Deliberately excludes `idle` (the resting state it returns to
|
||||
* between picks) and `recording` (reads as an active screen-capture cue).
|
||||
* feeling alive. Deliberately excludes `idle`, the resting state it returns to
|
||||
* between picks.
|
||||
*/
|
||||
export const AMBIENT_POSES: readonly RivePose[] = [
|
||||
'thinking',
|
||||
|
||||
@@ -336,6 +336,17 @@ describe('useHumanMascot state machine', () => {
|
||||
expect(result.current.face).toBe('thinking');
|
||||
});
|
||||
|
||||
it('does not expose a capture-specific activity face', () => {
|
||||
const { result } = renderHook(() => useHumanMascot());
|
||||
act(() => {
|
||||
capturedListeners?.onInferenceStart?.(fakeEvent({}));
|
||||
capturedListeners?.onToolCall?.(
|
||||
fakeEvent({ tool_name: 'memory_capture', skill_id: 's', args: {}, round: 1 })
|
||||
);
|
||||
});
|
||||
expect(result.current.face).toBe('thinking');
|
||||
});
|
||||
|
||||
it('maps reading tools to reading face', () => {
|
||||
const { result } = renderHook(() => useHumanMascot());
|
||||
act(() => {
|
||||
|
||||
@@ -254,10 +254,6 @@ function toolToActivityFace(toolName: string): MascotFace | null {
|
||||
return 'reading';
|
||||
}
|
||||
|
||||
if (name.includes('screen') || name.includes('screenshot') || name.includes('capture')) {
|
||||
return 'recording';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -284,7 +280,7 @@ interface UseHumanMascotResult {
|
||||
*
|
||||
* - `inference_start` → `thinking`
|
||||
* - `iteration_start` round > 1 or `tool_call` → activity pose based on tool
|
||||
* name (writing/reading/recording) or `confused` as fallback
|
||||
* name (writing/reading) or `confused` as fallback
|
||||
* - `tool_result success=false` → `concerned` (held briefly)
|
||||
* - `text_delta` → `speaking`, pseudo-lipsync from the trailing letter
|
||||
* - `chat_done` (no TTS) → message-aware ack face (held briefly), then `idle`
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import {
|
||||
type AccessibilityPermissionKind,
|
||||
type AccessibilityStartSessionParams,
|
||||
type AccessibilityStatus,
|
||||
openhumanAccessibilityRequestPermission,
|
||||
openhumanAccessibilityStartSession,
|
||||
openhumanAccessibilityStatus,
|
||||
openhumanAccessibilityStopSession,
|
||||
openhumanAccessibilityVisionFlush,
|
||||
openhumanAccessibilityVisionRecent,
|
||||
openhumanScreenIntelligenceCaptureTest,
|
||||
openhumanServiceRestart,
|
||||
} from '../../utils/tauriCommands';
|
||||
|
||||
const ACCESSIBILITY_ERROR_PREFIX = '[screen-intelligence]';
|
||||
|
||||
const extractError = (error: unknown, fallback: string): string => {
|
||||
if (error instanceof Error && error.message.trim()) {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === 'string' && error.trim()) {
|
||||
return error;
|
||||
}
|
||||
if (error && typeof error === 'object') {
|
||||
const msg = (error as { message?: unknown }).message;
|
||||
if (typeof msg === 'string' && msg.trim()) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const formatCoreIdentity = (status: AccessibilityStatus | null | undefined): string | null => {
|
||||
const process = status?.core_process;
|
||||
if (!process) {
|
||||
return null;
|
||||
}
|
||||
const startedAt = Number.isFinite(process.started_at_ms)
|
||||
? new Date(process.started_at_ms).toLocaleTimeString()
|
||||
: null;
|
||||
return startedAt ? `PID ${process.pid} at ${startedAt}` : `PID ${process.pid}`;
|
||||
};
|
||||
|
||||
interface RefreshPermissionsResult {
|
||||
status: AccessibilityStatus;
|
||||
restartSummary: string;
|
||||
}
|
||||
|
||||
async function fetchScreenIntelligenceStatus(): Promise<AccessibilityStatus> {
|
||||
const response = await openhumanAccessibilityStatus();
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export async function requestScreenIntelligencePermission(
|
||||
permission: AccessibilityPermissionKind
|
||||
): Promise<AccessibilityStatus> {
|
||||
await openhumanAccessibilityRequestPermission(permission);
|
||||
return await fetchScreenIntelligenceStatus();
|
||||
}
|
||||
|
||||
export async function refreshScreenIntelligencePermissionsWithRestart(
|
||||
previousStatus: AccessibilityStatus | null
|
||||
): Promise<RefreshPermissionsResult> {
|
||||
try {
|
||||
const previousProcess = previousStatus?.core_process;
|
||||
console.debug(
|
||||
`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: requesting core self-restart`
|
||||
);
|
||||
await openhumanServiceRestart('screen-intelligence-ui', 'refresh_permissions');
|
||||
console.debug(
|
||||
`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: waiting for sidecar ready`
|
||||
);
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 400));
|
||||
console.debug(
|
||||
`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: fetching updated status`
|
||||
);
|
||||
|
||||
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
||||
try {
|
||||
const status = await fetchScreenIntelligenceStatus();
|
||||
console.debug(
|
||||
`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: done screen_recording=%s accessibility=%s input_monitoring=%s`,
|
||||
status.permissions.screen_recording,
|
||||
status.permissions.accessibility,
|
||||
status.permissions.input_monitoring
|
||||
);
|
||||
const currentProcess = status.core_process;
|
||||
if (
|
||||
previousProcess &&
|
||||
currentProcess &&
|
||||
previousProcess.pid === currentProcess.pid &&
|
||||
previousProcess.started_at_ms === currentProcess.started_at_ms
|
||||
) {
|
||||
throw new Error(
|
||||
`Core restart command completed, but the same core instance is still serving requests (${formatCoreIdentity(status)}).`
|
||||
);
|
||||
}
|
||||
|
||||
const previousLabel = formatCoreIdentity(previousStatus);
|
||||
const currentLabel = formatCoreIdentity(status);
|
||||
const restartSummary =
|
||||
previousLabel && currentLabel
|
||||
? `Core restarted: ${previousLabel} -> ${currentLabel}.`
|
||||
: currentLabel
|
||||
? `Core restarted. Now serving from ${currentLabel}.`
|
||||
: 'Core restarted and permissions refreshed.';
|
||||
|
||||
return { status, restartSummary };
|
||||
} catch (error) {
|
||||
if (attempt === 5) {
|
||||
throw error;
|
||||
}
|
||||
console.debug(
|
||||
`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: status fetch failed (attempt %s), retrying`,
|
||||
attempt
|
||||
);
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 350 * attempt));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch accessibility status after core restart');
|
||||
} catch (error) {
|
||||
const message = extractError(error, 'Failed to restart core and refresh permissions');
|
||||
console.error(`${ACCESSIBILITY_ERROR_PREFIX} refreshPermissionsWithRestart: error`, message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function startScreenIntelligenceSession(
|
||||
params: AccessibilityStartSessionParams
|
||||
): Promise<AccessibilityStatus> {
|
||||
await openhumanAccessibilityStartSession(params);
|
||||
return await fetchScreenIntelligenceStatus();
|
||||
}
|
||||
|
||||
export async function stopScreenIntelligenceSession(reason?: string): Promise<AccessibilityStatus> {
|
||||
await openhumanAccessibilityStopSession(reason ? { reason } : undefined);
|
||||
return await fetchScreenIntelligenceStatus();
|
||||
}
|
||||
|
||||
export async function fetchScreenIntelligenceVisionRecent(limit?: number) {
|
||||
const response = await openhumanAccessibilityVisionRecent(limit);
|
||||
return response.result.summaries;
|
||||
}
|
||||
|
||||
export async function flushScreenIntelligenceVision() {
|
||||
const response = await openhumanAccessibilityVisionFlush();
|
||||
return response.result.summary;
|
||||
}
|
||||
|
||||
export async function runScreenIntelligenceCaptureTest() {
|
||||
const response = await openhumanScreenIntelligenceCaptureTest();
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export { extractError };
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Derives a skill-card-friendly status for Screen Intelligence,
|
||||
* matching the state vocabulary used by third-party skills (Gmail, etc.).
|
||||
*/
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useCoreState } from '../../providers/CoreStateProvider';
|
||||
import {
|
||||
activeStatus,
|
||||
enabledStatus,
|
||||
offlineStatus,
|
||||
setupRequiredStatus,
|
||||
type SkillCardStatusDescriptor,
|
||||
unsupportedStatus,
|
||||
} from '../skills/skillCardStatus';
|
||||
|
||||
interface ScreenIntelligenceSkillStatus extends SkillCardStatusDescriptor {
|
||||
/** True when all three macOS permissions are granted. */
|
||||
allPermissionsGranted: boolean;
|
||||
/** True when the platform doesn't support screen intelligence. */
|
||||
platformUnsupported: boolean;
|
||||
}
|
||||
|
||||
export function useScreenIntelligenceSkillStatus(): ScreenIntelligenceSkillStatus {
|
||||
const { snapshot } = useCoreState();
|
||||
const status = snapshot.runtime.screenIntelligence;
|
||||
|
||||
return useMemo(() => {
|
||||
// No status yet (core not ready or not in Tauri)
|
||||
if (!status) {
|
||||
return { ...offlineStatus(), allPermissionsGranted: false, platformUnsupported: false };
|
||||
}
|
||||
|
||||
if (!status.platform_supported) {
|
||||
return { ...unsupportedStatus(), allPermissionsGranted: false, platformUnsupported: true };
|
||||
}
|
||||
|
||||
const { permissions, session, config } = status;
|
||||
const allGranted =
|
||||
permissions.screen_recording === 'granted' &&
|
||||
permissions.accessibility === 'granted' &&
|
||||
permissions.input_monitoring === 'granted';
|
||||
|
||||
// Permissions missing — needs setup
|
||||
if (!allGranted) {
|
||||
return { ...setupRequiredStatus(), allPermissionsGranted: false, platformUnsupported: false };
|
||||
}
|
||||
|
||||
// Session active — fully connected
|
||||
if (session.active) {
|
||||
return { ...activeStatus(), allPermissionsGranted: true, platformUnsupported: false };
|
||||
}
|
||||
|
||||
// Permissions granted, enabled in config, but session not active
|
||||
if (config.enabled) {
|
||||
return { ...enabledStatus(), allPermissionsGranted: true, platformUnsupported: false };
|
||||
}
|
||||
|
||||
// Permissions granted but not enabled
|
||||
return {
|
||||
...offlineStatus('Disabled'),
|
||||
allPermissionsGranted: true,
|
||||
platformUnsupported: false,
|
||||
};
|
||||
}, [status]);
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getCoreStateSnapshot } from '../../lib/coreState/store';
|
||||
import { useCoreState } from '../../providers/CoreStateProvider';
|
||||
import type {
|
||||
AccessibilityPermissionKind,
|
||||
AccessibilityStartSessionParams,
|
||||
AccessibilityStatus,
|
||||
AccessibilityVisionSummary,
|
||||
CaptureTestResult,
|
||||
} from '../../utils/tauriCommands';
|
||||
import {
|
||||
extractError,
|
||||
fetchScreenIntelligenceVisionRecent,
|
||||
flushScreenIntelligenceVision,
|
||||
refreshScreenIntelligencePermissionsWithRestart,
|
||||
requestScreenIntelligencePermission,
|
||||
runScreenIntelligenceCaptureTest,
|
||||
startScreenIntelligenceSession,
|
||||
stopScreenIntelligenceSession,
|
||||
} from './api';
|
||||
|
||||
export interface ScreenIntelligenceState {
|
||||
status: AccessibilityStatus | null;
|
||||
lastRestartSummary: string | null;
|
||||
recentVisionSummaries: AccessibilityVisionSummary[];
|
||||
captureTestResult: CaptureTestResult | null;
|
||||
isCaptureTestRunning: boolean;
|
||||
isLoading: boolean;
|
||||
isRequestingPermissions: boolean;
|
||||
isRestartingCore: boolean;
|
||||
isStartingSession: boolean;
|
||||
isStoppingSession: boolean;
|
||||
isLoadingVision: boolean;
|
||||
isFlushingVision: boolean;
|
||||
lastError: string | null;
|
||||
refreshStatus: () => Promise<AccessibilityStatus | null>;
|
||||
requestPermission: (
|
||||
permission: AccessibilityPermissionKind
|
||||
) => Promise<AccessibilityStatus | null>;
|
||||
refreshPermissionsWithRestart: () => Promise<AccessibilityStatus | null>;
|
||||
startSession: (params: AccessibilityStartSessionParams) => Promise<AccessibilityStatus | null>;
|
||||
stopSession: (reason?: string) => Promise<AccessibilityStatus | null>;
|
||||
refreshVision: (limit?: number) => Promise<AccessibilityVisionSummary[]>;
|
||||
flushVision: () => Promise<void>;
|
||||
runCaptureTest: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
interface UseScreenIntelligenceStateOptions {
|
||||
pollMs?: number;
|
||||
visionLimit?: number;
|
||||
loadVision?: boolean;
|
||||
}
|
||||
|
||||
export function useScreenIntelligenceState(
|
||||
options: UseScreenIntelligenceStateOptions = {}
|
||||
): ScreenIntelligenceState {
|
||||
const { pollMs = 2000, visionLimit = 10, loadVision = false } = options;
|
||||
const { refresh: refreshCoreState, snapshot } = useCoreState();
|
||||
const status = snapshot.runtime.screenIntelligence;
|
||||
const [lastRestartSummary, setLastRestartSummary] = useState<string | null>(null);
|
||||
const [recentVisionSummaries, setRecentVisionSummaries] = useState<AccessibilityVisionSummary[]>(
|
||||
[]
|
||||
);
|
||||
const [captureTestResult, setCaptureTestResult] = useState<CaptureTestResult | null>(null);
|
||||
const [isCaptureTestRunning, setIsCaptureTestRunning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isRequestingPermissions, setIsRequestingPermissions] = useState(false);
|
||||
const [isRestartingCore, setIsRestartingCore] = useState(false);
|
||||
const [isStartingSession, setIsStartingSession] = useState(false);
|
||||
const [isStoppingSession, setIsStoppingSession] = useState(false);
|
||||
const [isLoadingVision, setIsLoadingVision] = useState(false);
|
||||
const [isFlushingVision, setIsFlushingVision] = useState(false);
|
||||
const [lastError, setLastError] = useState<string | null>(null);
|
||||
|
||||
const latestScreenIntelligenceStatus = useCallback(
|
||||
(): AccessibilityStatus | null => getCoreStateSnapshot().snapshot.runtime.screenIntelligence,
|
||||
[]
|
||||
);
|
||||
|
||||
const refreshStatus = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setLastError(null);
|
||||
try {
|
||||
await refreshCoreState();
|
||||
return latestScreenIntelligenceStatus();
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to fetch accessibility status'));
|
||||
return null;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [latestScreenIntelligenceStatus, refreshCoreState]);
|
||||
|
||||
const refreshVision = useCallback(
|
||||
async (limit = visionLimit) => {
|
||||
setIsLoadingVision(true);
|
||||
try {
|
||||
const summaries = await fetchScreenIntelligenceVisionRecent(limit);
|
||||
setRecentVisionSummaries(summaries);
|
||||
return summaries;
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to fetch accessibility vision summaries'));
|
||||
return [];
|
||||
} finally {
|
||||
setIsLoadingVision(false);
|
||||
}
|
||||
},
|
||||
[visionLimit]
|
||||
);
|
||||
|
||||
const requestPermission = useCallback(
|
||||
async (permission: AccessibilityPermissionKind) => {
|
||||
setIsRequestingPermissions(true);
|
||||
setLastError(null);
|
||||
setLastRestartSummary(null);
|
||||
try {
|
||||
await requestScreenIntelligencePermission(permission);
|
||||
await refreshCoreState();
|
||||
return latestScreenIntelligenceStatus();
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to request accessibility permission'));
|
||||
return null;
|
||||
} finally {
|
||||
setIsRequestingPermissions(false);
|
||||
}
|
||||
},
|
||||
[latestScreenIntelligenceStatus, refreshCoreState]
|
||||
);
|
||||
|
||||
const refreshPermissionsWithRestart = useCallback(async () => {
|
||||
setIsRestartingCore(true);
|
||||
setLastError(null);
|
||||
setLastRestartSummary(null);
|
||||
try {
|
||||
const result = await refreshScreenIntelligencePermissionsWithRestart(status);
|
||||
setLastRestartSummary(result.restartSummary);
|
||||
await refreshCoreState();
|
||||
return latestScreenIntelligenceStatus() ?? result.status;
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to restart core and refresh permissions'));
|
||||
return null;
|
||||
} finally {
|
||||
setIsRestartingCore(false);
|
||||
}
|
||||
}, [latestScreenIntelligenceStatus, refreshCoreState, status]);
|
||||
|
||||
const startSession = useCallback(
|
||||
async (params: AccessibilityStartSessionParams) => {
|
||||
setIsStartingSession(true);
|
||||
setLastError(null);
|
||||
try {
|
||||
await startScreenIntelligenceSession(params);
|
||||
await refreshCoreState();
|
||||
return latestScreenIntelligenceStatus();
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to start accessibility session'));
|
||||
return null;
|
||||
} finally {
|
||||
setIsStartingSession(false);
|
||||
}
|
||||
},
|
||||
[latestScreenIntelligenceStatus, refreshCoreState]
|
||||
);
|
||||
|
||||
const stopSession = useCallback(
|
||||
async (reason?: string) => {
|
||||
setIsStoppingSession(true);
|
||||
setLastError(null);
|
||||
try {
|
||||
await stopScreenIntelligenceSession(reason);
|
||||
await refreshCoreState();
|
||||
return latestScreenIntelligenceStatus();
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to stop accessibility session'));
|
||||
return null;
|
||||
} finally {
|
||||
setIsStoppingSession(false);
|
||||
}
|
||||
},
|
||||
[latestScreenIntelligenceStatus, refreshCoreState]
|
||||
);
|
||||
|
||||
const flushVision = useCallback(async () => {
|
||||
setIsFlushingVision(true);
|
||||
try {
|
||||
const summary = await flushScreenIntelligenceVision();
|
||||
if (summary) {
|
||||
setRecentVisionSummaries(current => [summary, ...current].slice(0, 30));
|
||||
}
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to flush accessibility vision'));
|
||||
} finally {
|
||||
setIsFlushingVision(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const runCaptureTest = useCallback(async () => {
|
||||
setIsCaptureTestRunning(true);
|
||||
setCaptureTestResult(null);
|
||||
setLastError(null);
|
||||
try {
|
||||
const result = await runScreenIntelligenceCaptureTest();
|
||||
setCaptureTestResult(result);
|
||||
} catch (error) {
|
||||
setLastError(extractError(error, 'Failed to run capture test'));
|
||||
} finally {
|
||||
setIsCaptureTestRunning(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadVision) {
|
||||
void refreshVision(visionLimit);
|
||||
}
|
||||
}, [loadVision, refreshVision, visionLimit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadVision) {
|
||||
return;
|
||||
}
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
void refreshVision(visionLimit);
|
||||
}, pollMs);
|
||||
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [loadVision, pollMs, refreshVision, visionLimit]);
|
||||
|
||||
return {
|
||||
status,
|
||||
lastRestartSummary,
|
||||
recentVisionSummaries,
|
||||
captureTestResult,
|
||||
isCaptureTestRunning,
|
||||
isLoading,
|
||||
isRequestingPermissions,
|
||||
isRestartingCore,
|
||||
isStartingSession,
|
||||
isStoppingSession,
|
||||
isLoadingVision,
|
||||
isFlushingVision,
|
||||
lastError,
|
||||
refreshStatus,
|
||||
requestPermission,
|
||||
refreshPermissionsWithRestart,
|
||||
startSession,
|
||||
stopSession,
|
||||
refreshVision,
|
||||
flushVision,
|
||||
runCaptureTest,
|
||||
clearError: () => setLastError(null),
|
||||
};
|
||||
}
|
||||
@@ -18,7 +18,7 @@ function makeSnapshot(overrides: Partial<CoreAppSnapshot> = {}): CoreAppSnapshot
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { User } from '../../types/api';
|
||||
import type { TeamInvite, TeamMember, TeamWithRole } from '../../types/team';
|
||||
import type { AccessibilityStatus } from '../../utils/tauriCommands/accessibility';
|
||||
import type { LocalAiStatus } from '../../utils/tauriCommands/localAi';
|
||||
import type { ServiceStatus } from '../../utils/tauriCommands/service';
|
||||
|
||||
@@ -32,7 +31,6 @@ export interface CoreLocalState {
|
||||
}
|
||||
|
||||
export interface CoreRuntimeSnapshot {
|
||||
screenIntelligence: AccessibilityStatus | null;
|
||||
localAi: LocalAiStatus | null;
|
||||
service: ServiceStatus | null;
|
||||
}
|
||||
@@ -94,7 +92,7 @@ const emptySnapshot: CoreAppSnapshot = {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
};
|
||||
|
||||
let currentState: CoreState = {
|
||||
|
||||
@@ -7,7 +7,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanion': 'المرافق لسطح المكتب',
|
||||
'settings.assistant.desktopCompanionDesc': 'وضع المرافق الدائم مع اختصار شريط النظام',
|
||||
'pages.settings.features.desktopCompanion': 'الرفيق المكتبي',
|
||||
'pages.settings.features.desktopCompanionDesc': 'مساعد صوتي يدرك الشاشة: يستمع ويرى ويتحدث ويشير',
|
||||
'settings.companion.title': 'رفيق سطح المكتب',
|
||||
'settings.companion.session': 'الجلسة',
|
||||
'settings.companion.activeLabel': 'نشط',
|
||||
@@ -23,8 +22,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'مفتاح التشغيل السريع',
|
||||
'settings.companion.activationMode': 'وضع التنشيط',
|
||||
'settings.companion.sessionTtl': 'مدة صلاحية الجلسة',
|
||||
'settings.companion.screenCapture': 'لقطة الشاشة',
|
||||
'settings.companion.appContext': 'سياق التطبيق',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'مشاركة',
|
||||
'share.modalTitle': 'شارك هذه اللحظة',
|
||||
@@ -109,7 +106,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'اختر نموذج التضمين المستخدم في البحث في الذاكرة.',
|
||||
'connections.header.search': 'اضبط البحث على الويب لوكيلك.',
|
||||
'connections.header.wallet': 'رصيد محفظتك المحلية وعبارة الاسترداد الخاصة بك.',
|
||||
'connections.header.screen': 'دع وكيلك يرى شاشتك ليقدم مساعدة سياقية في الوقت المناسب.',
|
||||
'connections.header.composioKey': 'أضف مفتاح API الخاص بـ Composio لفتح مئات تكاملات التطبيقات.',
|
||||
'connections.llm.apiKeys': 'مفاتيح API والتوجيه',
|
||||
'connections.welcome.nav': 'مرحبًا',
|
||||
@@ -741,8 +737,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.faceMascot': 'الوجه / الشخصية',
|
||||
'settings.assistant.backgroundActivity': 'اللاوعي',
|
||||
'settings.assistant.backgroundActivityDesc': 'تحكم في مدى نشاط مساعدك في الخلفية',
|
||||
'settings.assistant.screenAwareness': 'الوعي بالشاشة',
|
||||
'settings.assistant.screenAwarenessDesc': 'اسمح للمساعد برؤية النافذة النشطة',
|
||||
'settings.assistant.permissions': 'الأذونات',
|
||||
'settings.assistant.permissionsDesc': 'اختر ما يمكن للمساعد فعله والمكان الذي يمكنه العمل فيه',
|
||||
'settings.privacySecurity.privacy': 'الخصوصية',
|
||||
@@ -822,7 +816,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'التفضيلات',
|
||||
'settings.notifications.tabs.routing': 'التوجيه',
|
||||
'settings.features': 'الميزات',
|
||||
'settings.featuresDesc': 'وعي الشاشة والمراسلة والأدوات',
|
||||
'settings.aiModels': 'الذكاء الاصطناعي والنماذج',
|
||||
'settings.aiModelsDesc': 'إعداد نموذج الذكاء الاصطناعي المحلي وتنزيلاته ومزود LLM',
|
||||
'settings.ai': 'إعداد الذكاء الاصطناعي',
|
||||
@@ -914,8 +907,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': 'إيقاف جميع الإشعارات مؤقتًا لفترة محددة',
|
||||
'settings.notifications.channelControls': 'ضوابط لكل قناة',
|
||||
'settings.notifications.channelControlsDesc': 'ضبط تفضيلات الإشعارات لكل قناة',
|
||||
'settings.features.screenAwareness': 'وعي الشاشة',
|
||||
'settings.features.screenAwarenessDesc': 'السماح للمساعد برؤية نافذتك النشطة',
|
||||
'settings.features.messaging': 'المراسلة',
|
||||
'settings.features.messagingDesc': 'إعدادات تكامل القنوات والمراسلة',
|
||||
'settings.features.tools': 'الأدوات',
|
||||
@@ -2157,21 +2148,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'حفظ التغييرات',
|
||||
'settings.tools.preferencesSaved': 'تم حفظ التفضيلات',
|
||||
'settings.tools.saveFailed': 'فشل حفظ التفضيلات. حاول مرة أخرى.',
|
||||
'settings.screenAwareness.mode': 'الوضع',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'الكل باستثناء القائمة السوداء',
|
||||
'settings.screenAwareness.whitelistOnly': 'القائمة البيضاء فقط',
|
||||
'settings.screenAwareness.screenMonitoring': 'مراقبة الشاشة',
|
||||
'settings.screenAwareness.saveSettings': 'حفظ الإعدادات',
|
||||
'settings.screenAwareness.session': 'الجلسة',
|
||||
'settings.screenAwareness.status': 'الحالة',
|
||||
'settings.screenAwareness.active': 'نشط',
|
||||
'settings.screenAwareness.stopped': 'متوقف',
|
||||
'settings.screenAwareness.remaining': 'المتبقي',
|
||||
'settings.screenAwareness.startSession': 'بدء الجلسة',
|
||||
'settings.screenAwareness.stopSession': 'إيقاف الجلسة',
|
||||
'settings.screenAwareness.analyzeNow': 'تحليل الآن',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'التقاط سطح المكتب لوعي الشاشة وضوابط الأذونات مدعومة حاليًا على macOS فقط.',
|
||||
'connections.comingSoon': 'قريبًا',
|
||||
'connections.setUp': 'إعداد',
|
||||
'connections.configured': 'مضبوط',
|
||||
@@ -2518,36 +2494,6 @@ const messages: TranslationMap = {
|
||||
'مفتاح رئيسي. معطّل افتراضيًا: Ollama يبقى خاملاً. عند التشغيل، يستخدم ملخّص الشجرة وذكاء الشاشة والإكمال التلقائي النموذجَ المحلي دائمًا.',
|
||||
'localModel.advancedSettings': 'إعدادات متقدمة',
|
||||
'localModel.debugTitle': 'تصحيح النموذج المحلي',
|
||||
'screenAwareness.debugTitle': 'تصحيح وعي الشاشة',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'التصحيح والتشخيص',
|
||||
'screenAwareness.debug.collapse': 'طي',
|
||||
'screenAwareness.debug.expand': 'توسيع',
|
||||
'screenAwareness.debug.failedToSave': 'فشل حفظ ذكاء الشاشة',
|
||||
'screenAwareness.debug.policyTitle': 'سياسة ذكاء الشاشة',
|
||||
'screenAwareness.debug.baselineFps': 'FPS الأساسي',
|
||||
'screenAwareness.debug.useVisionModel': 'استخدام نموذج الرؤية',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'أرسل لقطات الشاشة إلى نموذج رؤية LLM للحصول على سياق أغنى. عند التعطيل، يُستخدم نص OCR فقط مع نموذج نصي: أسرع ولا يتطلب نموذج رؤية.',
|
||||
'screenAwareness.debug.keepScreenshots': 'الاحتفاظ بلقطات الشاشة',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'احفظ لقطات الشاشة الملتقطة في مساحة العمل بدلاً من حذفها بعد المعالجة',
|
||||
'screenAwareness.debug.allowlist': 'القائمة المسموح بها (قاعدة واحدة لكل سطر)',
|
||||
'screenAwareness.debug.denylist': 'قائمة الرفض (قاعدة واحدة لكل سطر)',
|
||||
'screenAwareness.debug.saveSettings': 'حفظ إعدادات ذكاء الشاشة',
|
||||
'screenAwareness.debug.sessionStats': 'إحصائيات الجلسة',
|
||||
'screenAwareness.debug.framesEphemeral': 'الإطارات (عابرة)',
|
||||
'screenAwareness.debug.panicStop': 'توقف الذعر',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'الرؤية',
|
||||
'screenAwareness.debug.idle': 'خاملة',
|
||||
'screenAwareness.debug.visionQueue': 'قائمة انتظار الرؤية',
|
||||
'screenAwareness.debug.lastVision': 'الرؤية الأخيرة',
|
||||
'screenAwareness.debug.notAvailable': 'غير متوفر',
|
||||
'screenAwareness.debug.visionSummaries': 'الرؤية الملخصات',
|
||||
'screenAwareness.debug.refreshing': 'تحديث...',
|
||||
'screenAwareness.debug.noSummaries': 'لا توجد ملخصات حتى الآن.',
|
||||
'screenAwareness.debug.unknownApp': 'تطبيق غير معروف',
|
||||
'screenAwareness.debug.macosOnly': 'ذكاء الشاشة V1 مدعوم على macOS فقط حالياً.',
|
||||
'memory.debugTitle': 'تصحيح الذاكرة',
|
||||
'memory.documents': 'المستندات',
|
||||
'memory.filterByNamespace': 'التصفية حسب مساحة الاسم...',
|
||||
@@ -3777,33 +3723,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'أحدث مخطط بنية OpenHuman المُولَّد',
|
||||
'intelligence.diagram.refreshesEvery': 'يتجدد كل {seconds} ثانية',
|
||||
'intelligence.memoryText.entityTypePrefix': 'نوع الكيان',
|
||||
'intelligence.screenDebug.active': 'نشط',
|
||||
'intelligence.screenDebug.app': 'التطبيق',
|
||||
'intelligence.screenDebug.bounds': 'الحدود',
|
||||
'intelligence.screenDebug.captureAlt': 'نتيجة اختبار التقاط',
|
||||
'intelligence.screenDebug.captureFailed': 'فشل',
|
||||
'intelligence.screenDebug.captureSuccess': 'نجاح',
|
||||
'intelligence.screenDebug.captureTest': 'اختبار الالتقاط',
|
||||
'intelligence.screenDebug.capturing': 'جارٍ الالتقاط',
|
||||
'intelligence.screenDebug.frames': 'الإطارات',
|
||||
'intelligence.screenDebug.idle': 'خامل',
|
||||
'intelligence.screenDebug.lastApp': 'آخر تطبيق',
|
||||
'intelligence.screenDebug.mode': 'الوضع',
|
||||
'intelligence.screenDebug.permAccessibility': 'إذن إمكانية الوصول',
|
||||
'intelligence.screenDebug.permInput': 'إذن الإدخال',
|
||||
'intelligence.screenDebug.permScreen': 'إمكانية الوصول',
|
||||
'intelligence.screenDebug.permissions': 'الأذونات',
|
||||
'intelligence.screenDebug.platformNotSupported': 'المنصة غير مدعومة',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'ملخصات الرؤية الأخيرة',
|
||||
'intelligence.screenDebug.session': 'الجلسة',
|
||||
'intelligence.screenDebug.size': 'الحجم',
|
||||
'intelligence.screenDebug.status': 'الحالة',
|
||||
'intelligence.screenDebug.testCapture': 'اختبار الالتقاط',
|
||||
'intelligence.screenDebug.time': 'الوقت',
|
||||
'intelligence.screenDebug.title': 'العنوان',
|
||||
'intelligence.screenDebug.unknown': 'غير معروف',
|
||||
'intelligence.screenDebug.visionQueue': 'قائمة انتظار الرؤية',
|
||||
'intelligence.screenDebug.visionState': 'حالة الرؤية',
|
||||
'intelligence.tasks.activeBoardOne': 'لوحة نشطة واحدة عبر المحادثات',
|
||||
'intelligence.tasks.activeBoardOther': '{count} لوحات نشطة عبر المحادثات',
|
||||
'intelligence.tasks.empty': 'لا توجد لوحات مهام للوكيل بعد',
|
||||
@@ -4363,7 +4282,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'إدخال الصوت نشط',
|
||||
'overlay.companion.error': 'خطأ',
|
||||
'overlay.companion.listening': 'يستمع…',
|
||||
'overlay.companion.pointing': 'يشير…',
|
||||
'overlay.companion.speaking': 'يتحدث…',
|
||||
'overlay.companion.thinking': 'يفكر…',
|
||||
'overlay.orbTitle': 'اسحب للتحريك · انقر مرتين لإعادة الضبط',
|
||||
@@ -4393,12 +4311,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'وصف قنوات المراسلة',
|
||||
'pages.settings.features.notifications': 'الإشعارات',
|
||||
'pages.settings.features.notificationsDesc': 'وصف الإشعارات',
|
||||
'pages.settings.features.screenAwareness': 'وعي الشاشة',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'دع وكيلك يرى شاشتك ليقدم مساعدة سياقية في الوقت المناسب.',
|
||||
'pages.settings.features.tools': 'الأدوات',
|
||||
'pages.settings.features.toolsDesc': 'وصف الأدوات',
|
||||
'pages.settings.featuresSection.description': 'وعي الشاشة والمراسلة والأدوات.',
|
||||
'pages.settings.featuresSection.title': 'الميزات',
|
||||
'privacy.dataKind.credentials': 'بيانات الاعتماد',
|
||||
'privacy.dataKind.derived': 'مشتقة',
|
||||
@@ -5145,9 +5059,6 @@ const messages: TranslationMap = {
|
||||
'settings.localModel.status.working': 'جارٍ العمل...',
|
||||
'settings.developerMenu.ai.title': 'إعدادات الذكاء الاصطناعي',
|
||||
'settings.developerMenu.ai.desc': 'مزودو السحابة، ونماذج Ollama المحلية، والتوجيه لكل عبء عمل',
|
||||
'settings.developerMenu.screenAwareness.title': 'الوعي بالشاشة',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'أذونات التقاط الشاشة، وسياسة المراقبة، وعناصر التحكم في الجلسة',
|
||||
'settings.developerMenu.messagingChannels.title': 'قنوات المراسلة',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'تكوين أوضاع مصادقة Telegram/Discord وتوجيه القناة الافتراضي',
|
||||
@@ -5776,20 +5687,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'استبدال التطبيق',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'إمكانية الوصول',
|
||||
'settings.screenIntel.permissions.grantHint': 'تلميح المنح',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'مراقبة الإدخال',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS يطبّق الخصوصية',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'جارٍ الطلب…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'جارٍ التحديث…',
|
||||
'settings.screenIntel.permissions.refreshing': 'جارٍ التحديث…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'جارٍ الطلب…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'جارٍ الطلب…',
|
||||
'settings.screenIntel.permissions.requesting': 'جارٍ الطلب…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'جارٍ إعادة تشغيل النواة…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'جارٍ إعادة تشغيل النواة…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'تسجيل الشاشة',
|
||||
'settings.screenIntel.permissions.title': 'الأذونات',
|
||||
'skills.card.moreActions': 'مزيد من الإجراءات',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6046,36 +5943,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'القوالب',
|
||||
'skills.resource.tree.empty': 'لا توجد موارد مجمّعة.',
|
||||
'skills.search.placeholder': 'بحث',
|
||||
'skills.setup.screenIntel.activeDesc': 'وصف النشاط',
|
||||
'skills.setup.screenIntel.activeTitle': 'ذكاء الشاشة مفعّل',
|
||||
'skills.setup.screenIntel.advancedSettings': 'إعدادات متقدمة',
|
||||
'skills.setup.screenIntel.allGranted': 'تم منح جميع الأذونات',
|
||||
'skills.setup.screenIntel.captureMode': 'وضع الالتقاط',
|
||||
'skills.setup.screenIntel.captureModeValue': 'جميع النوافذ (قابل للضبط لاحقًا)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'بعد منح الأذونات في إعدادات النظام، انقر أدناه لإعادة التشغيل وتطبيق التغييرات.',
|
||||
'skills.setup.screenIntel.enableBtn': 'جارٍ التفعيل...',
|
||||
'skills.setup.screenIntel.enableDesc': 'على شاشتك وتغذية سياق مفيد في وكيلك',
|
||||
'skills.setup.screenIntel.enableError': 'فشل تفعيل ذكاء الشاشة',
|
||||
'skills.setup.screenIntel.enabling': 'جارٍ التفعيل...',
|
||||
'skills.setup.screenIntel.grant': 'جارٍ الفتح...',
|
||||
'skills.setup.screenIntel.granted': 'تم المنح',
|
||||
'skills.setup.screenIntel.macosOnly': 'macOS فقط',
|
||||
'skills.setup.screenIntel.opening': 'جارٍ الفتح...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'مفتاح ساخن للطوارئ',
|
||||
'skills.setup.screenIntel.permAccessibility': 'إمكانية الوصول',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'مراقبة الإدخال',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'تسجيل الشاشة',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'وصف الأذونات',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'يطبق macOS الخصوصية على:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'تحديث الحالة',
|
||||
'skills.setup.screenIntel.restartRefresh': 'جارٍ إعادة التشغيل...',
|
||||
'skills.setup.screenIntel.restarting': 'جارٍ إعادة التشغيل...',
|
||||
'skills.setup.screenIntel.stepEnable': 'تفعيل المهارة',
|
||||
'skills.setup.screenIntel.stepPermissions': 'منح الأذونات',
|
||||
'skills.setup.screenIntel.stepSuccess': 'جاهز للانطلاق',
|
||||
'skills.setup.screenIntel.title': 'ذكاء الشاشة',
|
||||
'skills.setup.screenIntel.visionModel': 'نموذج الرؤية',
|
||||
'skills.setup.voice.activation': 'التفعيل',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6233,9 +6100,6 @@ const messages: TranslationMap = {
|
||||
'للحصول على تعليمات الإعداد، ثم أعد المحاولة عندما يصبح وقت التشغيل قابلاً للوصول.',
|
||||
'devOptions.menuAi': 'تكوين الذكاء الاصطناعي',
|
||||
'devOptions.menuAiDesc': 'موفرو السحابة، ونماذج Ollama المحلية، والتوجيه لكل حمل عمل',
|
||||
'devOptions.menuScreenAware': 'التعرف على الشاشة',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'أذونات التقاط الشاشة، وسياسة المراقبة، وعناصر التحكم في الجلسة',
|
||||
'devOptions.menuMessaging': 'قنوات المراسلة',
|
||||
'devOptions.menuMessagingDesc': 'تكوين أوضاع المصادقة Telegram/Discord وتوجيه القناة الافتراضية',
|
||||
'devOptions.menuTools': 'الأدوات',
|
||||
|
||||
@@ -7,8 +7,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanion': 'ডেস্কটপ সঙ্গী',
|
||||
'settings.assistant.desktopCompanionDesc': 'সিস্টেম ট্রে শর্টকাট সহ সর্বদা-চালু সঙ্গী মোড',
|
||||
'pages.settings.features.desktopCompanion': 'ডেস্কটপ কম্প্যানিয়ন',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'স্ক্রিন সচেতনতা সহ ভয়েস সহকারী: শোনে, দেখে, কথা বলে, নির্দেশ করে',
|
||||
'settings.companion.title': 'ডেস্কটপ সঙ্গী',
|
||||
'settings.companion.session': 'সেশন',
|
||||
'settings.companion.activeLabel': 'সক্রিয়',
|
||||
@@ -24,8 +22,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'হটকি',
|
||||
'settings.companion.activationMode': 'সক্রিয়করণ মোড',
|
||||
'settings.companion.sessionTtl': 'সেশন TTL',
|
||||
'settings.companion.screenCapture': 'স্ক্রিন ক্যাপচার',
|
||||
'settings.companion.appContext': 'অ্যাপ প্রসঙ্গ',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'শেয়ার',
|
||||
'share.modalTitle': 'এই মুহূর্তটি শেয়ার করুন',
|
||||
@@ -111,8 +107,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'মেমরি অনুসন্ধানে ব্যবহৃত এমবেডিং মডেল বেছে নিন।',
|
||||
'connections.header.search': 'আপনার এজেন্টের জন্য ওয়েব অনুসন্ধান কনফিগার করুন।',
|
||||
'connections.header.wallet': 'আপনার স্থানীয় ওয়ালেটের ব্যালেন্স এবং পুনরুদ্ধার বাক্যাংশ।',
|
||||
'connections.header.screen':
|
||||
'সময়োপযোগী, প্রাসঙ্গিক সহায়তা দিতে আপনার এজেন্টকে আপনার স্ক্রিন দেখতে দিন।',
|
||||
'connections.header.composioKey':
|
||||
'শত শত অ্যাপ ইন্টিগ্রেশন আনলক করতে একটি Composio API কী যোগ করুন।',
|
||||
'connections.llm.apiKeys': 'API কী ও রাউটিং',
|
||||
@@ -760,8 +754,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'সাবকনশাস',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'আপনার সহকারী পটভূমিতে কতটা সক্রিয় তা নিয়ন্ত্রণ করুন',
|
||||
'settings.assistant.screenAwareness': 'স্ক্রিন সচেতনতা',
|
||||
'settings.assistant.screenAwarenessDesc': 'সহকারীকে আপনার সক্রিয় উইন্ডো দেখতে দিন',
|
||||
'settings.assistant.permissions': 'অনুমতি',
|
||||
'settings.assistant.permissionsDesc': 'সহকারী কী করতে পারে এবং কোথায় কাজ করতে পারে তা বেছে নিন',
|
||||
'settings.privacySecurity.privacy': 'গোপনীয়তা',
|
||||
@@ -842,7 +834,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'পছন্দ',
|
||||
'settings.notifications.tabs.routing': 'রাউটিং',
|
||||
'settings.features': 'ফিচার',
|
||||
'settings.featuresDesc': 'স্ক্রিন সচেতনতা, মেসেজিং এবং টুলস',
|
||||
'settings.aiModels': 'AI ও মডেল',
|
||||
'settings.aiModelsDesc': 'লোকাল AI মডেল সেটআপ, ডাউনলোড এবং LLM প্রোভাইডার',
|
||||
'settings.ai': 'AI কনফিগারেশন',
|
||||
@@ -934,8 +925,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': 'নির্দিষ্ট সময়ের জন্য সব বিজ্ঞপ্তি বন্ধ রাখুন',
|
||||
'settings.notifications.channelControls': 'চ্যানেল-ভিত্তিক নিয়ন্ত্রণ',
|
||||
'settings.notifications.channelControlsDesc': 'প্রতিটি চ্যানেলের জন্য বিজ্ঞপ্তি পছন্দ সেট করুন',
|
||||
'settings.features.screenAwareness': 'স্ক্রিন সচেতনতা',
|
||||
'settings.features.screenAwarenessDesc': 'অ্যাসিস্ট্যান্টকে আপনার সক্রিয় উইন্ডো দেখতে দিন',
|
||||
'settings.features.messaging': 'মেসেজিং',
|
||||
'settings.features.messagingDesc': 'চ্যানেল ও মেসেজিং ইন্টিগ্রেশন সেটিংস',
|
||||
'settings.features.tools': 'টুলস',
|
||||
@@ -2205,21 +2194,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'পরিবর্তন সংরক্ষণ করুন',
|
||||
'settings.tools.preferencesSaved': 'পছন্দ সংরক্ষিত',
|
||||
'settings.tools.saveFailed': 'পছন্দ সংরক্ষণ ব্যর্থ। আবার চেষ্টা করুন।',
|
||||
'settings.screenAwareness.mode': 'মোড',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'ব্ল্যাকলিস্ট ছাড়া সব',
|
||||
'settings.screenAwareness.whitelistOnly': 'শুধু হোয়াইটলিস্ট',
|
||||
'settings.screenAwareness.screenMonitoring': 'স্ক্রিন মনিটরিং',
|
||||
'settings.screenAwareness.saveSettings': 'সেটিংস সংরক্ষণ করুন',
|
||||
'settings.screenAwareness.session': 'সেশন',
|
||||
'settings.screenAwareness.status': 'স্ট্যাটাস',
|
||||
'settings.screenAwareness.active': 'সক্রিয়',
|
||||
'settings.screenAwareness.stopped': 'বন্ধ',
|
||||
'settings.screenAwareness.remaining': 'অবশিষ্ট',
|
||||
'settings.screenAwareness.startSession': 'সেশন শুরু করুন',
|
||||
'settings.screenAwareness.stopSession': 'সেশন বন্ধ করুন',
|
||||
'settings.screenAwareness.analyzeNow': 'এখনই বিশ্লেষণ করুন',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'স্ক্রিন সচেতনতা ডেস্কটপ ক্যাপচার এবং অনুমতি নিয়ন্ত্রণ বর্তমানে শুধু macOS-এ সমর্থিত।',
|
||||
'connections.comingSoon': 'শীঘ্রই আসছে',
|
||||
'connections.setUp': 'সেটআপ করুন',
|
||||
'connections.configured': 'কনফিগার করা হয়েছে',
|
||||
@@ -2582,36 +2556,6 @@ const messages: TranslationMap = {
|
||||
'মাস্টার সুইচ। ডিফল্টে বন্ধ: Ollama নিষ্ক্রিয় থাকে। চালু হলে, ট্রি সামারাইজার, স্ক্রিন ইন্টেলিজেন্স এবং অটোকমপ্লিট সর্বদা লোকাল মডেল ব্যবহার করে।',
|
||||
'localModel.advancedSettings': 'অ্যাডভান্সড সেটিংস',
|
||||
'localModel.debugTitle': 'লোকাল মডেল ডিবাগ',
|
||||
'screenAwareness.debugTitle': 'স্ক্রিন সচেতনতা ডিবাগ',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'ডিবাগ এবং ডায়াগনস্টিকস',
|
||||
'screenAwareness.debug.collapse': 'সঙ্কুচিত',
|
||||
'screenAwareness.debug.expand': 'প্রসারিত করুন',
|
||||
'screenAwareness.debug.failedToSave': 'স্ক্রীন ইন্টেলিজেন্স সংরক্ষণ করতে ব্যর্থ',
|
||||
'screenAwareness.debug.policyTitle': 'স্ক্রীন ইন্টেলিজেন্স নীতি',
|
||||
'screenAwareness.debug.baselineFps': 'বেসলাইন FPS',
|
||||
'screenAwareness.debug.useVisionModel': 'ভিআইপি ব্যবহার করুন',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'আরও সমৃদ্ধ প্রেক্ষাপটের জন্য একটি ভিশন LLM-এ স্ক্রিনশট পাঠান। বন্ধ থাকলে, শুধুমাত্র OCR টেক্সট একটি টেক্সট LLM-এর সাথে ব্যবহার করা হয়: দ্রুততর এবং কোনো ভিশন মডেল প্রয়োজন নেই।',
|
||||
'screenAwareness.debug.keepScreenshots': 'স্ক্রিনশটগুলি রাখুন',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'প্রক্রিয়াকরণের পরে মুছে না দিয়ে ক্যাপচার করা স্ক্রিনশটগুলি ওয়ার্কস্পেসে সংরক্ষণ করুন',
|
||||
'screenAwareness.debug.allowlist': 'অনুমতি তালিকা (প্রতি লাইনে একটি নিয়ম)',
|
||||
'screenAwareness.debug.denylist': 'অস্বীকারকারী (প্রতি লাইনে একটি নিয়ম)',
|
||||
'screenAwareness.debug.saveSettings': 'স্ক্রীন ইন্টেলিজেন্স সেটিংস সংরক্ষণ করুন',
|
||||
'screenAwareness.debug.sessionStats': 'সেশন পরিসংখ্যান',
|
||||
'screenAwareness.debug.framesEphemeral': 'ফ্রেম (ক্ষণস্থায়ী)',
|
||||
'screenAwareness.debug.panicStop': 'প্যানিক স্টপ',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'দৃষ্টি',
|
||||
'screenAwareness.debug.idle': 'নিষ্ক্রিয়',
|
||||
'screenAwareness.debug.visionQueue': 'দৃষ্টি সারি',
|
||||
'screenAwareness.debug.lastVision': 'শেষ দর্শন',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'ভিশন সারাংশ',
|
||||
'screenAwareness.debug.refreshing': 'রিফ্রেশ করা হচ্ছে...',
|
||||
'screenAwareness.debug.noSummaries': 'এখনও কোনো সারসংক্ষেপ নেই।',
|
||||
'screenAwareness.debug.unknownApp': 'অজানা অ্যাপ',
|
||||
'screenAwareness.debug.macosOnly': 'স্ক্রীন ইন্টেলিজেন্স V1 বর্তমানে শুধুমাত্র macOS-এ সমর্থিত।',
|
||||
'memory.debugTitle': 'মেমোরি ডিবাগ',
|
||||
'memory.documents': 'নথি',
|
||||
'memory.filterByNamespace': 'নামস্থান দ্বারা ফিল্টার করুন...',
|
||||
@@ -3863,33 +3807,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'সর্বশেষ তৈরি OpenHuman আর্কিটেকচার ডায়াগ্রাম',
|
||||
'intelligence.diagram.refreshesEvery': 'প্রতি {seconds}s-এ রিফ্রেশ হয়',
|
||||
'intelligence.memoryText.entityTypePrefix': 'এনটিটি ধরন',
|
||||
'intelligence.screenDebug.active': 'সক্রিয়',
|
||||
'intelligence.screenDebug.app': 'অ্যাপ',
|
||||
'intelligence.screenDebug.bounds': 'সীমানা',
|
||||
'intelligence.screenDebug.captureAlt': 'ক্যাপচার পরীক্ষার ফলাফল',
|
||||
'intelligence.screenDebug.captureFailed': 'ব্যর্থ',
|
||||
'intelligence.screenDebug.captureSuccess': 'সফল',
|
||||
'intelligence.screenDebug.captureTest': 'ক্যাপচার পরীক্ষা',
|
||||
'intelligence.screenDebug.capturing': 'ক্যাপচার হচ্ছে',
|
||||
'intelligence.screenDebug.frames': 'ফ্রেম',
|
||||
'intelligence.screenDebug.idle': 'নিষ্ক্রিয়',
|
||||
'intelligence.screenDebug.lastApp': 'শেষ অ্যাপ',
|
||||
'intelligence.screenDebug.mode': 'মোড',
|
||||
'intelligence.screenDebug.permAccessibility': 'অ্যাক্সেসিবিলিটি অনুমতি',
|
||||
'intelligence.screenDebug.permInput': 'ইনপুট অনুমতি',
|
||||
'intelligence.screenDebug.permScreen': 'অ্যাক্সেসিবিলিটি',
|
||||
'intelligence.screenDebug.permissions': 'অনুমতি',
|
||||
'intelligence.screenDebug.platformNotSupported': 'প্ল্যাটফর্ম সমর্থিত নয়',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'সাম্প্রতিক ভিশন সারসংক্ষেপ',
|
||||
'intelligence.screenDebug.session': 'সেশন',
|
||||
'intelligence.screenDebug.size': 'আকার',
|
||||
'intelligence.screenDebug.status': 'স্ট্যাটাস',
|
||||
'intelligence.screenDebug.testCapture': 'ক্যাপচার পরীক্ষা করুন',
|
||||
'intelligence.screenDebug.time': 'সময়',
|
||||
'intelligence.screenDebug.title': 'শিরোনাম',
|
||||
'intelligence.screenDebug.unknown': 'অজানা',
|
||||
'intelligence.screenDebug.visionQueue': 'ভিশন কিউ',
|
||||
'intelligence.screenDebug.visionState': 'ভিশন স্টেট',
|
||||
'intelligence.tasks.activeBoardOne': 'কথোপকথনজুড়ে ১টি সক্রিয় বোর্ড',
|
||||
'intelligence.tasks.activeBoardOther': 'কথোপকথনজুড়ে {count}টি সক্রিয় বোর্ড',
|
||||
'intelligence.tasks.empty': 'এখনো কোনো এজেন্ট টাস্ক বোর্ড নেই',
|
||||
@@ -4476,7 +4393,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'ভয়েস ইনপুট সক্রিয়',
|
||||
'overlay.companion.error': 'ত্রুটি',
|
||||
'overlay.companion.listening': 'শুনছে…',
|
||||
'overlay.companion.pointing': 'নির্দেশ করছে…',
|
||||
'overlay.companion.speaking': 'বলছে…',
|
||||
'overlay.companion.thinking': 'ভাবছে…',
|
||||
'overlay.orbTitle': 'সরাতে টেনে আনুন · পজিশন রিসেট করতে ডাবল-ক্লিক করুন',
|
||||
@@ -4506,12 +4422,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'মেসেজিং চ্যানেলের বিবরণ',
|
||||
'pages.settings.features.notifications': 'বিজ্ঞপ্তি',
|
||||
'pages.settings.features.notificationsDesc': 'বিজ্ঞপ্তির বিবরণ',
|
||||
'pages.settings.features.screenAwareness': 'স্ক্রিন সচেতনতা',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'সময়োপযোগী, প্রাসঙ্গিক সহায়তা দিতে আপনার এজেন্টকে আপনার স্ক্রিন দেখতে দিন।',
|
||||
'pages.settings.features.tools': 'টুলস',
|
||||
'pages.settings.features.toolsDesc': 'টুলসের বিবরণ',
|
||||
'pages.settings.featuresSection.description': 'স্ক্রিন সচেতনতা, মেসেজিং এবং টুলস।',
|
||||
'pages.settings.featuresSection.title': 'ফিচার',
|
||||
'privacy.dataKind.credentials': 'ক্রেডেনশিয়াল',
|
||||
'privacy.dataKind.derived': 'ডেরাইভড',
|
||||
@@ -5263,9 +5175,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'AI কনফিগারেশন',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'ক্লাউড প্রদানকারী, স্থানীয় Ollama মডেল এবং প্রতি-ওয়ার্কলোড রাউটিং',
|
||||
'settings.developerMenu.screenAwareness.title': 'স্ক্রিন সচেতনতা',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'স্ক্রিন ক্যাপচার অনুমতি, মনিটরিং নীতি এবং সেশন নিয়ন্ত্রণ',
|
||||
'settings.developerMenu.messagingChannels.title': 'মেসেজিং চ্যানেল',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Telegram/Discord অথেন্টিকেশন মোড এবং ডিফল্ট চ্যানেল রাউটিং কনফিগার করুন',
|
||||
@@ -5910,20 +5819,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': '& বদলাও',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'অ্যাক্সেসিবিলিটি',
|
||||
'settings.screenIntel.permissions.grantHint': 'গ্রান্ট হিন্ট',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'ইনপুট মনিটরিং',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS গোপনীয়তা প্রয়োগ করে',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'অনুরোধ করা হচ্ছে…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'রিফ্রেশ হচ্ছে…',
|
||||
'settings.screenIntel.permissions.refreshing': 'রিফ্রেশ হচ্ছে…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'অনুরোধ করা হচ্ছে…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'অনুরোধ করা হচ্ছে…',
|
||||
'settings.screenIntel.permissions.requesting': 'অনুরোধ করা হচ্ছে…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'কোর রিস্টার্ট হচ্ছে…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'কোর রিস্টার্ট হচ্ছে…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'স্ক্রিন রেকর্ডিং',
|
||||
'settings.screenIntel.permissions.title': 'অনুমতি',
|
||||
'skills.card.moreActions': 'আরও কাজ',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6185,36 +6080,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'টেমপ্লেট',
|
||||
'skills.resource.tree.empty': 'কোনো বান্ডেলড রিসোর্স নেই।',
|
||||
'skills.search.placeholder': 'প্লেসহোল্ডার',
|
||||
'skills.setup.screenIntel.activeDesc': 'সক্রিয় বিবরণ',
|
||||
'skills.setup.screenIntel.activeTitle': 'স্ক্রিন ইন্টেলিজেন্স সক্রিয়',
|
||||
'skills.setup.screenIntel.advancedSettings': 'অ্যাডভান্সড সেটিংস',
|
||||
'skills.setup.screenIntel.allGranted': 'সব অনুমতি দেওয়া হয়েছে',
|
||||
'skills.setup.screenIntel.captureMode': 'ক্যাপচার মোড',
|
||||
'skills.setup.screenIntel.captureModeValue': 'সব উইন্ডো (পরে কনফিগারযোগ্য)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'সিস্টেম সেটিংসে অনুমতি দেওয়ার পর, পরিবর্তন নিতে নিচে ক্লিক করে রিস্টার্ট করুন।',
|
||||
'skills.setup.screenIntel.enableBtn': 'সক্রিয় হচ্ছে...',
|
||||
'skills.setup.screenIntel.enableDesc': 'আপনার স্ক্রিনে এবং এজেন্টে দরকারী কন্টেক্সট ফিড করুন',
|
||||
'skills.setup.screenIntel.enableError': 'স্ক্রিন ইন্টেলিজেন্স সক্রিয় করতে ব্যর্থ',
|
||||
'skills.setup.screenIntel.enabling': 'সক্রিয় হচ্ছে...',
|
||||
'skills.setup.screenIntel.grant': 'খোলা হচ্ছে...',
|
||||
'skills.setup.screenIntel.granted': 'দেওয়া হয়েছে',
|
||||
'skills.setup.screenIntel.macosOnly': 'শুধু macOS',
|
||||
'skills.setup.screenIntel.opening': 'খোলা হচ্ছে...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'প্যানিক হটকি',
|
||||
'skills.setup.screenIntel.permAccessibility': 'অ্যাক্সেসিবিলিটি',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'ইনপুট মনিটরিং',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'স্ক্রিন রেকর্ডিং',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'অনুমতির বিবরণ',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS গোপনীয়তা প্রযোজ্য:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'স্ট্যাটাস রিফ্রেশ করুন',
|
||||
'skills.setup.screenIntel.restartRefresh': 'রিস্টার্ট হচ্ছে...',
|
||||
'skills.setup.screenIntel.restarting': 'রিস্টার্ট হচ্ছে...',
|
||||
'skills.setup.screenIntel.stepEnable': 'স্কিল সক্রিয় করুন',
|
||||
'skills.setup.screenIntel.stepPermissions': 'অনুমতি দিন',
|
||||
'skills.setup.screenIntel.stepSuccess': 'যেতে প্রস্তুত',
|
||||
'skills.setup.screenIntel.title': 'স্ক্রিন ইন্টেলিজেন্স',
|
||||
'skills.setup.screenIntel.visionModel': 'ভিশন মডেল',
|
||||
'skills.setup.voice.activation': 'অ্যাক্টিভেশন',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6373,8 +6238,6 @@ const messages: TranslationMap = {
|
||||
'সেটআপ নির্দেশনার জন্য, তারপর আপনার রানটাইম পৌঁছানো গেলে পুনরায় চেষ্টা করুন।',
|
||||
'devOptions.menuAi': 'AI কনফিগারেশন',
|
||||
'devOptions.menuAiDesc': 'ক্লাউড প্রদানকারী, স্থানীয় Ollama মডেল, এবং প্রতি-ওয়ার্কলোড রাউটিং',
|
||||
'devOptions.menuScreenAware': 'স্ক্রীন সচেতনতা',
|
||||
'devOptions.menuScreenAwareDesc': 'স্ক্রীন ক্যাপচার অনুমতি এবং সেশন নিয়ন্ত্রণ, নিরীক্ষণ নীতি',
|
||||
'devOptions.menuMessaging': 'মেসেজিং চ্যানেলগুলি',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'কনফিগার করুন Telegram/Discord প্রমাণীকরণ মোড এবং ডিফল্ট চ্যানেল রাউটিং',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Immer aktiver Begleiter-Modus mit System-Tray-Shortcut',
|
||||
'pages.settings.features.desktopCompanion': 'Desktop-Begleiter',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Sprachassistent mit Bildschirmerkennung – hört zu, sieht, spricht, zeigt',
|
||||
'settings.companion.title': 'Desktop-Begleiter',
|
||||
'settings.companion.session': 'Sitzung',
|
||||
'settings.companion.activeLabel': 'Aktiv',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Hotkey',
|
||||
'settings.companion.activationMode': 'Aktivierungsmodus',
|
||||
'settings.companion.sessionTtl': 'Sitzungs-TTL',
|
||||
'settings.companion.screenCapture': 'Bildschirmaufnahme',
|
||||
'settings.companion.appContext': 'App-Kontext',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Teilen',
|
||||
'share.modalTitle': 'Diesen Moment teilen',
|
||||
@@ -124,8 +120,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.search': 'Konfigurieren Sie die Websuche für Ihren Agenten.',
|
||||
'connections.header.wallet':
|
||||
'Das Guthaben Ihrer lokalen Wallet und Ihre Wiederherstellungsphrase.',
|
||||
'connections.header.screen':
|
||||
'Lassen Sie Ihren Agenten Ihren Bildschirm sehen, um zeitnahe, kontextbezogene Hilfe zu bieten.',
|
||||
'connections.header.composioKey':
|
||||
'Fügen Sie einen Composio-API-Schlüssel hinzu, um Hunderte von App-Integrationen freizuschalten.',
|
||||
'connections.llm.apiKeys': 'API-Schlüssel & Routing',
|
||||
@@ -792,8 +786,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Unterbewusstsein',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Steuern, wie aktiv Ihr Assistent im Hintergrund arbeitet',
|
||||
'settings.assistant.screenAwareness': 'Bildschirmbewusstsein',
|
||||
'settings.assistant.screenAwarenessDesc': 'Dem Assistenten das aktive Fenster zeigen',
|
||||
'settings.assistant.permissions': 'Berechtigungen',
|
||||
'settings.assistant.permissionsDesc': 'Wähle, was der Assistent tun darf und wo er arbeiten kann',
|
||||
'settings.privacySecurity.privacy': 'Datenschutz',
|
||||
@@ -875,7 +867,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Einstellungen',
|
||||
'settings.notifications.tabs.routing': 'Routenführung',
|
||||
'settings.features': 'Funktionen',
|
||||
'settings.featuresDesc': 'Bildschirmbewusstsein, Nachrichten und Tools',
|
||||
'settings.aiModels': 'KI & Modelle',
|
||||
'settings.aiModelsDesc': 'Lokales KI-Modell-Setup, Downloads und LLM-Anbieter',
|
||||
'settings.ai': 'KI-Konfiguration',
|
||||
@@ -973,8 +964,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Steuerung pro Kanal',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Konfiguriere Benachrichtigungseinstellungen für jeden Kanal',
|
||||
'settings.features.screenAwareness': 'Bildschirmbewusstsein',
|
||||
'settings.features.screenAwarenessDesc': 'Lass den Assistenten dein aktives Fenster sehen',
|
||||
'settings.features.messaging': 'Nachrichten',
|
||||
'settings.features.messagingDesc': 'Einstellungen für die Kanal- und Messaging-Integration',
|
||||
'settings.features.tools': 'Werkzeuge',
|
||||
@@ -2277,21 +2266,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.preferencesSaved': 'Einstellungen gespeichert',
|
||||
'settings.tools.saveFailed':
|
||||
'Einstellungen konnten nicht gespeichert werden. Versuche es erneut.',
|
||||
'settings.screenAwareness.mode': 'Modus',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Alle außer Blacklist',
|
||||
'settings.screenAwareness.whitelistOnly': 'Nur Whitelist',
|
||||
'settings.screenAwareness.screenMonitoring': 'Bildschirmüberwachung',
|
||||
'settings.screenAwareness.saveSettings': 'Einstellungen speichern',
|
||||
'settings.screenAwareness.session': 'Sitzung',
|
||||
'settings.screenAwareness.status': 'Status',
|
||||
'settings.screenAwareness.active': 'Aktiv',
|
||||
'settings.screenAwareness.stopped': 'Angehalten',
|
||||
'settings.screenAwareness.remaining': 'Übrig',
|
||||
'settings.screenAwareness.startSession': 'Sitzung starten',
|
||||
'settings.screenAwareness.stopSession': 'Sitzung beenden',
|
||||
'settings.screenAwareness.analyzeNow': 'Jetzt analysieren',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'Die Desktop-Erfassung und Berechtigungssteuerung von Screen Awareness wird derzeit nur auf macOS unterstützt.',
|
||||
'connections.comingSoon': 'Kommt bald',
|
||||
'connections.setUp': 'Einrichten',
|
||||
'connections.configured': 'Konfiguriert',
|
||||
@@ -2657,37 +2631,6 @@ const messages: TranslationMap = {
|
||||
'Hauptschalter. Standardmäßig deaktiviert – Ollama bleibt inaktiv. Wenn diese Option aktiviert ist, verwenden die Baumzusammenfassung, die Bildschirmintelligenz und die automatische Vervollständigung immer das lokale Modell.',
|
||||
'localModel.advancedSettings': 'Erweiterte Einstellungen',
|
||||
'localModel.debugTitle': 'Lokales Modell-Debug',
|
||||
'screenAwareness.debugTitle': 'Debuggen der Bildschirmerkennung',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Debug & Diagnose',
|
||||
'screenAwareness.debug.collapse': 'Reduzieren',
|
||||
'screenAwareness.debug.expand': 'Erweitern',
|
||||
'screenAwareness.debug.failedToSave': 'Bildschirmintelligenz konnte nicht gespeichert werden.',
|
||||
'screenAwareness.debug.policyTitle': 'Bildschirmintelligenzrichtlinie',
|
||||
'screenAwareness.debug.baselineFps': 'Basis-FPS',
|
||||
'screenAwareness.debug.useVisionModel': 'Vision-Modell verwenden',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Screenshots an ein Vision-LLM senden, um reichhaltigeren Kontext zu erhalten. Wenn deaktiviert, wird nur OCR-Text mit einem Text-LLM verwendet – schneller und ohne Vision-Modell.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Screenshots behalten',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Aufgenommene Screenshots im Arbeitsbereich speichern, anstatt sie nach der Verarbeitung zu löschen',
|
||||
'screenAwareness.debug.allowlist': 'Zulassungsliste (eine Regel pro Zeile)',
|
||||
'screenAwareness.debug.denylist': 'Sperrliste (eine Regel pro Zeile)',
|
||||
'screenAwareness.debug.saveSettings': 'Bildschirmintelligenzeinstellungen speichern',
|
||||
'screenAwareness.debug.sessionStats': 'Sitzungsstatistiken',
|
||||
'screenAwareness.debug.framesEphemeral': 'Frames (flüchtig)',
|
||||
'screenAwareness.debug.panicStop': 'Panikstopp',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Befehl+Umschalt+.',
|
||||
'screenAwareness.debug.vision': 'Vision',
|
||||
'screenAwareness.debug.idle': 'inaktiv',
|
||||
'screenAwareness.debug.visionQueue': 'Vision-Warteschlange',
|
||||
'screenAwareness.debug.lastVision': 'Letzte Vision',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Vision-Zusammenfassungen',
|
||||
'screenAwareness.debug.refreshing': 'Erfrischend…',
|
||||
'screenAwareness.debug.noSummaries': 'Noch keine Zusammenfassungen.',
|
||||
'screenAwareness.debug.unknownApp': 'Unbekannte App',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Screen Intelligence V1 wird derzeit nur unter macOS unterstützt.',
|
||||
'memory.debugTitle': 'Speicher-Debug',
|
||||
'memory.documents': 'Dokumente',
|
||||
'memory.filterByNamespace': 'Nach Namespace filtern...',
|
||||
@@ -3970,33 +3913,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Zuletzt generiertes OpenHuman-Architekturdiagramm',
|
||||
'intelligence.diagram.refreshesEvery': 'Aktualisiert alle {seconds} s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Entitätstyp',
|
||||
'intelligence.screenDebug.active': 'Aktiv',
|
||||
'intelligence.screenDebug.app': 'App',
|
||||
'intelligence.screenDebug.bounds': 'Grenzen',
|
||||
'intelligence.screenDebug.captureAlt': 'Testergebnis erfassen',
|
||||
'intelligence.screenDebug.captureFailed': 'Fehlgeschlagen',
|
||||
'intelligence.screenDebug.captureSuccess': 'Erfolg',
|
||||
'intelligence.screenDebug.captureTest': 'Capture-Test',
|
||||
'intelligence.screenDebug.capturing': 'Erfassen',
|
||||
'intelligence.screenDebug.frames': 'Rahmen',
|
||||
'intelligence.screenDebug.idle': 'Leerlauf',
|
||||
'intelligence.screenDebug.lastApp': 'Letzte App',
|
||||
'intelligence.screenDebug.mode': 'Modus',
|
||||
'intelligence.screenDebug.permAccessibility': 'Zugänglichkeit aufrechterhalten',
|
||||
'intelligence.screenDebug.permInput': 'Perm-Eingabe',
|
||||
'intelligence.screenDebug.permScreen': 'Barrierefreiheit',
|
||||
'intelligence.screenDebug.permissions': 'Berechtigungen',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Plattform nicht unterstützt',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Aktuelle Visionszusammenfassungen',
|
||||
'intelligence.screenDebug.session': 'Sitzung',
|
||||
'intelligence.screenDebug.size': 'Größe',
|
||||
'intelligence.screenDebug.status': 'Status',
|
||||
'intelligence.screenDebug.testCapture': 'Testaufnahme',
|
||||
'intelligence.screenDebug.time': 'Zeit',
|
||||
'intelligence.screenDebug.title': 'Titel',
|
||||
'intelligence.screenDebug.unknown': 'Unbekannt',
|
||||
'intelligence.screenDebug.visionQueue': 'Vision-Warteschlange',
|
||||
'intelligence.screenDebug.visionState': 'Visionszustand',
|
||||
'intelligence.tasks.activeBoardOne': '1 aktives Forum für alle Gespräche',
|
||||
'intelligence.tasks.activeBoardOther': '{count} aktive Boards in allen Gesprächen',
|
||||
'intelligence.tasks.empty': 'Noch keine Agenten-Taskboards',
|
||||
@@ -4599,7 +4515,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Spracheingabe aktiv',
|
||||
'overlay.companion.error': 'Fehler',
|
||||
'overlay.companion.listening': 'Zuhören…',
|
||||
'overlay.companion.pointing': 'Zeigen…',
|
||||
'overlay.companion.speaking': 'Apropos…',
|
||||
'overlay.companion.thinking': 'Denken…',
|
||||
'overlay.orbTitle': 'Zum Verschieben ziehen · Doppelklicken, um die Position zurückzusetzen',
|
||||
@@ -4633,12 +4548,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Nachrichtenkanäle, Abschn',
|
||||
'pages.settings.features.notifications': 'Benachrichtigungen',
|
||||
'pages.settings.features.notificationsDesc': 'Benachrichtigungen absch',
|
||||
'pages.settings.features.screenAwareness': 'Bildschirmbewusstsein',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Lassen Sie Ihren Agenten Ihren Bildschirm sehen, um zeitnahe, kontextbezogene Hilfe zu bieten.',
|
||||
'pages.settings.features.tools': 'Werkzeuge',
|
||||
'pages.settings.features.toolsDesc': 'Werkzeugbeschr',
|
||||
'pages.settings.featuresSection.description': 'Bildschirmbewusstsein, Nachrichten und Tools.',
|
||||
'pages.settings.featuresSection.title': 'Funktionen',
|
||||
'privacy.dataKind.credentials': 'Anmeldeinformationen',
|
||||
'privacy.dataKind.derived': 'Abgeleitet',
|
||||
@@ -5410,9 +5321,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'KI-Konfiguration',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Cloud-Anbieter, lokale Ollama-Modelle und Routing pro Workload',
|
||||
'settings.developerMenu.screenAwareness.title': 'Bildschirmbewusstsein',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Bildschirmaufnahmeberechtigungen, Überwachungsrichtlinien und Sitzungskontrollen',
|
||||
'settings.developerMenu.messagingChannels.title': 'Messaging-Kanäle',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Konfiguriere die Authentifizierungsmodi Telegram/Discord und das Standardkanalrouting',
|
||||
@@ -6075,21 +5983,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Ersatz anwenden',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Barrierefreiheit',
|
||||
'settings.screenIntel.permissions.grantHint': 'Grant-Hinweis',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Eingabeüberwachung',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy':
|
||||
'macOS verwaltet diese Berechtigungen unter Systemeinstellungen → Datenschutz & Sicherheit.',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Bitte um …',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Erfrischend…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Erfrischend…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Bitte um …',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Bitte um …',
|
||||
'settings.screenIntel.permissions.requesting': 'Bitte um …',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Kern wird neu gestartet...',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Kern wird neu gestartet...',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Bildschirmaufzeichnung',
|
||||
'settings.screenIntel.permissions.title': 'Berechtigungen',
|
||||
'skills.card.moreActions': 'Weitere Aktionen',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6353,39 +6246,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Vorlagen',
|
||||
'skills.resource.tree.empty': 'Keine gebündelten Ressourcen.',
|
||||
'skills.search.placeholder': 'Platzhalter',
|
||||
'skills.setup.screenIntel.activeDesc':
|
||||
'Screen Intelligence läuft und liest dein aktives Fenster.',
|
||||
'skills.setup.screenIntel.activeTitle': 'Bildschirmintelligenz ist aktiviert',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Erweiterte Einstellungen',
|
||||
'skills.setup.screenIntel.allGranted': 'Alle Berechtigungen erteilt',
|
||||
'skills.setup.screenIntel.captureMode': 'Aufnahmemodus',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Alle Fenster (später konfigurierbar)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Nachdem du in den Systemeinstellungen Berechtigungen erteilt hast, klicke unten, um neu zu starten und die Änderungen zu übernehmen.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Aktivieren...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
'Liest, was auf deinem Bildschirm ist, und gibt deinem Agenten nützlichen Kontext.',
|
||||
'skills.setup.screenIntel.enableError': 'Screen Intelligence konnte nicht aktiviert werden',
|
||||
'skills.setup.screenIntel.enabling': 'Aktivieren...',
|
||||
'skills.setup.screenIntel.grant': 'Öffnen...',
|
||||
'skills.setup.screenIntel.granted': 'Erteilt',
|
||||
'skills.setup.screenIntel.macosOnly': 'Nur macOS',
|
||||
'skills.setup.screenIntel.opening': 'Öffnen...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Panik-Hotkey',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Barrierefreiheit',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Eingabeüberwachung',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Bildschirmaufzeichnung',
|
||||
'skills.setup.screenIntel.permissionsDesc':
|
||||
'Screen Intelligence benötigt Berechtigungen für Bedienungshilfen, Eingabeüberwachung und Bildschirmaufnahme.',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS wendet Datenschutz an:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Status aktualisieren',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Neustart...',
|
||||
'skills.setup.screenIntel.restarting': 'Neustart...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Aktiviere den Skill',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Berechtigungen erteilen',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Bereit zu gehen',
|
||||
'skills.setup.screenIntel.title': 'Bildschirmintelligenz',
|
||||
'skills.setup.screenIntel.visionModel': 'Vision-Modell',
|
||||
'skills.setup.voice.activation': 'Aktivierung',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6547,9 +6407,6 @@ const messages: TranslationMap = {
|
||||
'für Setup-Anweisungen, dann versuche es erneut, sobald deine Laufzeit erreichbar ist.',
|
||||
'devOptions.menuAi': 'KI-Konfiguration',
|
||||
'devOptions.menuAiDesc': 'Cloud-Anbieter, lokale Ollama-Modelle und Routing pro Workload',
|
||||
'devOptions.menuScreenAware': 'Bildschirmbewusstsein',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Bildschirmaufnahmeberechtigungen, Überwachungsrichtlinien und Sitzungskontrollen',
|
||||
'devOptions.menuMessaging': 'Messaging-Kanäle',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Konfiguriere die Authentifizierungsmodi Telegram/Discord und das Standardkanalrouting',
|
||||
|
||||
+1
-147
@@ -5,8 +5,6 @@ const en: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc': 'Always-on companion mode with a system-tray shortcut',
|
||||
'connections.header.companion': 'A floating desktop companion for quick, always-on help.',
|
||||
'pages.settings.features.desktopCompanion': 'Desktop Companion',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Voice assistant with screen awareness: listens, sees, speaks, points',
|
||||
'settings.companion.title': 'Desktop Companion',
|
||||
'settings.companion.session': 'Session',
|
||||
'settings.companion.activeLabel': 'Active',
|
||||
@@ -22,8 +20,6 @@ const en: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Hotkey',
|
||||
'settings.companion.activationMode': 'Activation Mode',
|
||||
'settings.companion.sessionTtl': 'Session TTL',
|
||||
'settings.companion.screenCapture': 'Screen Capture',
|
||||
'settings.companion.appContext': 'App Context',
|
||||
'skills.recallCalendar.title': 'Google Calendar',
|
||||
'skills.recallCalendar.description': 'Auto-join Google Meet calls via Recall.ai',
|
||||
// Navigation
|
||||
@@ -520,8 +516,6 @@ const en: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Subconscious',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Control how actively your assistant works in the background',
|
||||
'settings.assistant.screenAwareness': 'Screen awareness',
|
||||
'settings.assistant.screenAwarenessDesc': 'Let the assistant see your active window',
|
||||
'settings.assistant.permissions': 'Permissions',
|
||||
'settings.assistant.permissionsDesc': 'Choose what the assistant can do and where it can work',
|
||||
|
||||
@@ -599,7 +593,6 @@ const en: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferences',
|
||||
'settings.notifications.tabs.routing': 'Routing',
|
||||
'settings.features': 'Features',
|
||||
'settings.featuresDesc': 'Screen awareness, messaging, and tools',
|
||||
'settings.aiModels': 'AI & Models',
|
||||
'settings.aiModelsDesc': 'Local AI model setup, downloads, and LLM provider',
|
||||
'settings.ai': 'AI Configuration',
|
||||
@@ -703,8 +696,6 @@ const en: TranslationMap = {
|
||||
'Configure notification preferences for each channel',
|
||||
|
||||
// Settings: Features
|
||||
'settings.features.screenAwareness': 'Screen Awareness',
|
||||
'settings.features.screenAwarenessDesc': 'Let the assistant see your active window',
|
||||
'settings.features.messaging': 'Messaging',
|
||||
'settings.features.messagingDesc': 'Channel and messaging integration settings',
|
||||
'settings.features.tools': 'Tools',
|
||||
@@ -930,7 +921,6 @@ const en: TranslationMap = {
|
||||
'connections.header.embeddings': 'Choose the embedding model used for memory search.',
|
||||
'connections.header.search': 'Configure web search for your agent.',
|
||||
'connections.header.wallet': 'Your local wallet balance and recovery phrase.',
|
||||
'connections.header.screen': 'Let your agent see your screen to offer timely, in-context help.',
|
||||
'connections.header.composioKey':
|
||||
'Add a Composio API key to unlock hundreds of app integrations.',
|
||||
'connections.llm.apiKeys': 'API keys & routing',
|
||||
@@ -2306,23 +2296,6 @@ const en: TranslationMap = {
|
||||
'settings.tools.preferencesSaved': 'Preferences saved',
|
||||
'settings.tools.saveFailed': 'Failed to save preferences. Try again.',
|
||||
|
||||
// Settings: Screen Awareness
|
||||
'settings.screenAwareness.mode': 'Mode',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'All Except Blacklist',
|
||||
'settings.screenAwareness.whitelistOnly': 'Whitelist Only',
|
||||
'settings.screenAwareness.screenMonitoring': 'Screen Monitoring',
|
||||
'settings.screenAwareness.saveSettings': 'Save Settings',
|
||||
'settings.screenAwareness.session': 'Session',
|
||||
'settings.screenAwareness.status': 'Status',
|
||||
'settings.screenAwareness.active': 'Active',
|
||||
'settings.screenAwareness.stopped': 'Stopped',
|
||||
'settings.screenAwareness.remaining': 'Remaining',
|
||||
'settings.screenAwareness.startSession': 'Start Session',
|
||||
'settings.screenAwareness.stopSession': 'Stop Session',
|
||||
'settings.screenAwareness.analyzeNow': 'Analyze Now',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'Screen Awareness desktop capture and permission controls are currently supported on macOS only.',
|
||||
|
||||
// Connections
|
||||
'connections.comingSoon': 'Coming soon',
|
||||
'connections.setUp': 'Set up',
|
||||
@@ -2706,42 +2679,10 @@ const en: TranslationMap = {
|
||||
'Choose which subsystems run on the local model. Anything off uses the cloud.',
|
||||
'localModel.enableRuntime': 'Enable local AI runtime',
|
||||
'localModel.enableRuntimeDesc':
|
||||
'Master switch. Off by default: Ollama stays idle. When on, the tree summarizer, screen intelligence, and autocomplete always use the local model.',
|
||||
'Master switch. Off by default: Ollama stays idle. When on, the tree summarizer and autocomplete always use the local model.',
|
||||
'localModel.advancedSettings': 'Advanced settings',
|
||||
'localModel.debugTitle': 'Local Model Debug',
|
||||
|
||||
// Screen Awareness
|
||||
'screenAwareness.debugTitle': 'Screen Awareness Debug',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Debug & Diagnostics',
|
||||
'screenAwareness.debug.collapse': 'Collapse',
|
||||
'screenAwareness.debug.expand': 'Expand',
|
||||
'screenAwareness.debug.failedToSave': 'Failed to save screen intelligence',
|
||||
'screenAwareness.debug.policyTitle': 'Screen Intelligence Policy',
|
||||
'screenAwareness.debug.baselineFps': 'Baseline FPS',
|
||||
'screenAwareness.debug.useVisionModel': 'Use Vision Model',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Send screenshots to a vision LLM for richer context. When off, only OCR text is used with a text LLM: faster and no vision model required.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Keep Screenshots',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Save captured screenshots to the workspace instead of deleting after processing',
|
||||
'screenAwareness.debug.allowlist': 'Allowlist (one rule per line)',
|
||||
'screenAwareness.debug.denylist': 'Denylist (one rule per line)',
|
||||
'screenAwareness.debug.saveSettings': 'Save Screen Intelligence Settings',
|
||||
'screenAwareness.debug.sessionStats': 'Session Stats',
|
||||
'screenAwareness.debug.framesEphemeral': 'Frames (ephemeral)',
|
||||
'screenAwareness.debug.panicStop': 'Panic stop',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Vision',
|
||||
'screenAwareness.debug.idle': 'idle',
|
||||
'screenAwareness.debug.visionQueue': 'Vision queue',
|
||||
'screenAwareness.debug.lastVision': 'Last vision',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Vision Summaries',
|
||||
'screenAwareness.debug.refreshing': 'Refreshing…',
|
||||
'screenAwareness.debug.noSummaries': 'No summaries yet.',
|
||||
'screenAwareness.debug.unknownApp': 'Unknown App',
|
||||
'screenAwareness.debug.macosOnly': 'Screen Intelligence V1 is currently supported on macOS only.',
|
||||
|
||||
// Memory
|
||||
'memory.debugTitle': 'Memory Debug',
|
||||
'memory.documents': 'Documents',
|
||||
@@ -4252,33 +4193,6 @@ const en: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram',
|
||||
'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Entity type',
|
||||
'intelligence.screenDebug.active': 'Active',
|
||||
'intelligence.screenDebug.app': 'App',
|
||||
'intelligence.screenDebug.bounds': 'Bounds',
|
||||
'intelligence.screenDebug.captureAlt': 'Capture test result',
|
||||
'intelligence.screenDebug.captureFailed': 'Failed',
|
||||
'intelligence.screenDebug.captureSuccess': 'Success',
|
||||
'intelligence.screenDebug.captureTest': 'Capture test',
|
||||
'intelligence.screenDebug.capturing': 'Capturing',
|
||||
'intelligence.screenDebug.frames': 'Frames',
|
||||
'intelligence.screenDebug.idle': 'Idle',
|
||||
'intelligence.screenDebug.lastApp': 'Last App',
|
||||
'intelligence.screenDebug.mode': 'Mode',
|
||||
'intelligence.screenDebug.permAccessibility': 'Perm accessibility',
|
||||
'intelligence.screenDebug.permInput': 'Perm input',
|
||||
'intelligence.screenDebug.permScreen': 'Accessibility',
|
||||
'intelligence.screenDebug.permissions': 'Permissions',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Platform not supported',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Recent vision summaries',
|
||||
'intelligence.screenDebug.session': 'Session',
|
||||
'intelligence.screenDebug.size': 'Size',
|
||||
'intelligence.screenDebug.status': 'Status',
|
||||
'intelligence.screenDebug.testCapture': 'Test capture',
|
||||
'intelligence.screenDebug.time': 'Time',
|
||||
'intelligence.screenDebug.title': 'Title',
|
||||
'intelligence.screenDebug.unknown': 'Unknown',
|
||||
'intelligence.screenDebug.visionQueue': 'Vision Queue',
|
||||
'intelligence.screenDebug.visionState': 'Vision State',
|
||||
'intelligence.tasks.activeBoardOne': '1 active board across conversations',
|
||||
'intelligence.tasks.activeBoardOther': '{count} active boards across conversations',
|
||||
'intelligence.tasks.empty': 'No agent task boards yet',
|
||||
@@ -5063,7 +4977,6 @@ const en: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Voice input active',
|
||||
'overlay.companion.error': 'Error',
|
||||
'overlay.companion.listening': 'Listening…',
|
||||
'overlay.companion.pointing': 'Pointing…',
|
||||
'overlay.companion.speaking': 'Speaking…',
|
||||
'overlay.companion.thinking': 'Thinking…',
|
||||
'overlay.orbTitle': 'Drag to move · Double-click to reset position',
|
||||
@@ -5095,12 +5008,8 @@ const en: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Messaging channels desc',
|
||||
'pages.settings.features.notifications': 'Notifications',
|
||||
'pages.settings.features.notificationsDesc': 'Notifications desc',
|
||||
'pages.settings.features.screenAwareness': 'Screen awareness',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Let your agent see your screen to offer timely, in-context help.',
|
||||
'pages.settings.features.tools': 'Tools',
|
||||
'pages.settings.features.toolsDesc': 'Tools desc',
|
||||
'pages.settings.featuresSection.description': 'Screen awareness, messaging, and tools.',
|
||||
'pages.settings.featuresSection.title': 'Features',
|
||||
'privacy.dataKind.credentials': 'Credentials',
|
||||
'privacy.dataKind.derived': 'Derived',
|
||||
@@ -5856,9 +5765,6 @@ const en: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'AI Configuration',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Cloud providers, local Ollama models, and per-workload routing',
|
||||
'settings.developerMenu.screenAwareness.title': 'Screen Awareness',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Screen capture permissions, monitoring policy, and session controls',
|
||||
'settings.developerMenu.messagingChannels.title': 'Messaging Channels',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Configure Telegram/Discord auth modes and default channel routing',
|
||||
@@ -6502,22 +6408,6 @@ const en: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Apply Replacement',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Accessibility',
|
||||
'settings.screenIntel.permissions.grantHint':
|
||||
'Grant these permissions in System Settings, then restart the core.',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Input Monitoring',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy':
|
||||
'macOS controls these under System Settings → Privacy & Security.',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Open Input Monitoring',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Refreshing…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Refreshing…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Request Accessibility',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Request Screen Recording',
|
||||
'settings.screenIntel.permissions.requesting': 'Requesting…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Restarting core…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Restarting core…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Screen Recording',
|
||||
'settings.screenIntel.permissions.title': 'Permissions',
|
||||
'skills.card.moreActions': 'More actions',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6787,39 +6677,6 @@ const en: TranslationMap = {
|
||||
'skills.resource.tree.scripts': 'Scripts',
|
||||
'skills.resource.tree.templates': 'Templates',
|
||||
'skills.search.placeholder': 'Search skills…',
|
||||
'skills.setup.screenIntel.activeDesc':
|
||||
'Screen Intelligence is running and reading your active window.',
|
||||
'skills.setup.screenIntel.activeTitle': 'Screen Intelligence is Enabled',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Advanced settings',
|
||||
'skills.setup.screenIntel.allGranted': 'All permissions granted',
|
||||
'skills.setup.screenIntel.captureMode': 'Capture mode',
|
||||
'skills.setup.screenIntel.captureModeValue': 'All windows (configurable later)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'After granting permissions in System Settings, click below to restart and pick up the changes.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Enabling...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
'Reads what is on your screen and feeds useful context into your agent.',
|
||||
'skills.setup.screenIntel.enableError': 'Failed to enable Screen Intelligence',
|
||||
'skills.setup.screenIntel.enabling': 'Enabling...',
|
||||
'skills.setup.screenIntel.grant': 'Opening...',
|
||||
'skills.setup.screenIntel.granted': 'Granted',
|
||||
'skills.setup.screenIntel.macosOnly': 'macOS only',
|
||||
'skills.setup.screenIntel.opening': 'Opening...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Panic hotkey',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Accessibility',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Input Monitoring',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Screen Recording',
|
||||
'skills.setup.screenIntel.permissionsDesc':
|
||||
'Screen Intelligence requires accessibility, input monitoring, and screen recording permissions.',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS applies privacy to:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Refresh status',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Restarting...',
|
||||
'skills.setup.screenIntel.restarting': 'Restarting...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Enable the skill',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Grant permissions',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Ready to go',
|
||||
'skills.setup.screenIntel.title': 'Screen Intelligence',
|
||||
'skills.setup.screenIntel.visionModel': 'Vision model',
|
||||
'skills.setup.voice.activation': 'Activation',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6978,9 +6835,6 @@ const en: TranslationMap = {
|
||||
// Developer options menu items (#2225)
|
||||
'devOptions.menuAi': 'AI Configuration',
|
||||
'devOptions.menuAiDesc': 'Cloud providers, local Ollama models, and per-workload routing',
|
||||
'devOptions.menuScreenAware': 'Screen Awareness',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Screen capture permissions, monitoring policy, and session controls',
|
||||
'devOptions.menuMessaging': 'Messaging Channels',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Configure Telegram/Discord auth modes and default channel routing',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Modo compañero siempre activo con acceso directo en la bandeja del sistema',
|
||||
'pages.settings.features.desktopCompanion': 'Acompañante de escritorio',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Asistente de voz con conciencia de pantalla: escucha, ve, habla, señala',
|
||||
'settings.companion.title': 'Compañero de escritorio',
|
||||
'settings.companion.session': 'Sesión',
|
||||
'settings.companion.activeLabel': 'Activo',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Atajo de teclado',
|
||||
'settings.companion.activationMode': 'Modo de activación',
|
||||
'settings.companion.sessionTtl': 'TTL de sesión',
|
||||
'settings.companion.screenCapture': 'Captura de pantalla',
|
||||
'settings.companion.appContext': 'Contexto de la aplicación',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Compartir',
|
||||
'share.modalTitle': 'Comparte este momento',
|
||||
@@ -116,8 +112,6 @@ const messages: TranslationMap = {
|
||||
'Elige el modelo de embeddings usado para la búsqueda en la memoria.',
|
||||
'connections.header.search': 'Configura la búsqueda web para tu agente.',
|
||||
'connections.header.wallet': 'El saldo de tu monedero local y tu frase de recuperación.',
|
||||
'connections.header.screen':
|
||||
'Deja que tu agente vea tu pantalla para ofrecerte ayuda oportuna y en contexto.',
|
||||
'connections.header.composioKey':
|
||||
'Añade una clave de API de Composio para desbloquear cientos de integraciones de apps.',
|
||||
'connections.llm.apiKeys': 'Claves de API y enrutamiento',
|
||||
@@ -774,8 +768,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Subconsciente',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Controla qué tan activo trabaja tu asistente en segundo plano',
|
||||
'settings.assistant.screenAwareness': 'Conciencia de pantalla',
|
||||
'settings.assistant.screenAwarenessDesc': 'Permite al asistente ver tu ventana activa',
|
||||
'settings.assistant.permissions': 'Permisos',
|
||||
'settings.assistant.permissionsDesc': 'Elige qué puede hacer el asistente y dónde puede trabajar',
|
||||
'settings.privacySecurity.privacy': 'Privacidad',
|
||||
@@ -856,7 +848,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferencias',
|
||||
'settings.notifications.tabs.routing': 'Enrutamiento',
|
||||
'settings.features': 'Funciones',
|
||||
'settings.featuresDesc': 'Conciencia de pantalla, mensajería y herramientas',
|
||||
'settings.aiModels': 'IA y modelos',
|
||||
'settings.aiModelsDesc': 'Configuración de modelos de IA locales, descargas y proveedor LLM',
|
||||
'settings.ai': 'Configuración de IA',
|
||||
@@ -956,8 +947,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Controles por canal',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Configura las preferencias de notificación para cada canal',
|
||||
'settings.features.screenAwareness': 'Conciencia de pantalla',
|
||||
'settings.features.screenAwarenessDesc': 'Permite que el asistente vea tu ventana activa',
|
||||
'settings.features.messaging': 'Mensajería',
|
||||
'settings.features.messagingDesc': 'Configuración de integración de canales y mensajería',
|
||||
'settings.features.tools': 'Herramientas',
|
||||
@@ -2249,21 +2238,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Guardar cambios',
|
||||
'settings.tools.preferencesSaved': 'Preferencias guardadas',
|
||||
'settings.tools.saveFailed': 'No se pudieron guardar las preferencias. Inténtalo de nuevo.',
|
||||
'settings.screenAwareness.mode': 'Modo',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Todo excepto lista negra',
|
||||
'settings.screenAwareness.whitelistOnly': 'Solo lista blanca',
|
||||
'settings.screenAwareness.screenMonitoring': 'Monitoreo de pantalla',
|
||||
'settings.screenAwareness.saveSettings': 'Guardar configuración',
|
||||
'settings.screenAwareness.session': 'Sesión',
|
||||
'settings.screenAwareness.status': 'Estado',
|
||||
'settings.screenAwareness.active': 'Activo',
|
||||
'settings.screenAwareness.stopped': 'Detenido',
|
||||
'settings.screenAwareness.remaining': 'Restante',
|
||||
'settings.screenAwareness.startSession': 'Iniciar sesión',
|
||||
'settings.screenAwareness.stopSession': 'Detener sesión',
|
||||
'settings.screenAwareness.analyzeNow': 'Analizar ahora',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'La captura de pantalla y los controles de permisos de Conciencia de pantalla actualmente solo son compatibles con macOS.',
|
||||
'connections.comingSoon': 'Próximamente',
|
||||
'connections.setUp': 'Configurar',
|
||||
'connections.configured': 'Configurado',
|
||||
@@ -2629,37 +2603,6 @@ const messages: TranslationMap = {
|
||||
'Interruptor principal. Desactivado por defecto: Ollama permanece inactivo. Cuando está activado, el resumidor de árbol, la inteligencia de pantalla y el autocompletado siempre usan el modelo local.',
|
||||
'localModel.advancedSettings': 'Configuración avanzada',
|
||||
'localModel.debugTitle': 'Depuración de modelo local',
|
||||
'screenAwareness.debugTitle': 'Depuración de Conciencia de pantalla',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Depuración y diagnóstico',
|
||||
'screenAwareness.debug.collapse': 'Colapso',
|
||||
'screenAwareness.debug.expand': 'Expandir',
|
||||
'screenAwareness.debug.failedToSave': 'No se pudo guardar la inteligencia de pantalla',
|
||||
'screenAwareness.debug.policyTitle': 'Política de inteligencia de pantalla',
|
||||
'screenAwareness.debug.baselineFps': 'FPS de referencia',
|
||||
'screenAwareness.debug.useVisionModel': 'Usar modelo de visión',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Envía capturas de pantalla a un LLM de visión para obtener más contexto. Cuando está desactivado, solo se usa texto OCR con un LLM de texto, lo que es más rápido y no requiere modelo de visión.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Mantener capturas de pantalla',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Guarda las capturas de pantalla en el espacio de trabajo en lugar de eliminarlas tras el procesamiento',
|
||||
'screenAwareness.debug.allowlist': 'Lista de permitidos (una regla por línea)',
|
||||
'screenAwareness.debug.denylist': 'Lista de rechazados (una regla por línea)',
|
||||
'screenAwareness.debug.saveSettings': 'Guardar configuración de inteligencia de pantalla',
|
||||
'screenAwareness.debug.sessionStats': 'Estadísticas de sesión',
|
||||
'screenAwareness.debug.framesEphemeral': 'Marcos (efímeros)',
|
||||
'screenAwareness.debug.panicStop': 'parada de pánico',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Mayús+.',
|
||||
'screenAwareness.debug.vision': 'Visión',
|
||||
'screenAwareness.debug.idle': 'inactivo',
|
||||
'screenAwareness.debug.visionQueue': 'Cola de visión',
|
||||
'screenAwareness.debug.lastVision': 'ultima vision',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Resúmenes de visión',
|
||||
'screenAwareness.debug.refreshing': 'Refrescante…',
|
||||
'screenAwareness.debug.noSummaries': 'Aún no hay resúmenes.',
|
||||
'screenAwareness.debug.unknownApp': 'Aplicación desconocida',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Screen Intelligence V1 actualmente solo es compatible con macOS.',
|
||||
'memory.debugTitle': 'Depuración de memoria',
|
||||
'memory.documents': 'Documentos',
|
||||
'memory.filterByNamespace': 'Filtrar por espacio de nombres...',
|
||||
@@ -3929,33 +3872,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Último diagrama de arquitectura de OpenHuman generado',
|
||||
'intelligence.diagram.refreshesEvery': 'Se actualiza cada {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Tipo de entidad',
|
||||
'intelligence.screenDebug.active': 'Activo',
|
||||
'intelligence.screenDebug.app': 'Aplicación',
|
||||
'intelligence.screenDebug.bounds': 'Límites',
|
||||
'intelligence.screenDebug.captureAlt': 'Resultado de prueba de captura',
|
||||
'intelligence.screenDebug.captureFailed': 'Falló',
|
||||
'intelligence.screenDebug.captureSuccess': 'Éxito',
|
||||
'intelligence.screenDebug.captureTest': 'Prueba de captura',
|
||||
'intelligence.screenDebug.capturing': 'Capturando',
|
||||
'intelligence.screenDebug.frames': 'Fotogramas',
|
||||
'intelligence.screenDebug.idle': 'Inactivo',
|
||||
'intelligence.screenDebug.lastApp': 'Última app',
|
||||
'intelligence.screenDebug.mode': 'Modo',
|
||||
'intelligence.screenDebug.permAccessibility': 'Permiso de accesibilidad',
|
||||
'intelligence.screenDebug.permInput': 'Permiso de entrada',
|
||||
'intelligence.screenDebug.permScreen': 'Accesibilidad',
|
||||
'intelligence.screenDebug.permissions': 'Permisos',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Plataforma no compatible',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Resúmenes de visión recientes',
|
||||
'intelligence.screenDebug.session': 'Sesión',
|
||||
'intelligence.screenDebug.size': 'Tamaño',
|
||||
'intelligence.screenDebug.status': 'Estado',
|
||||
'intelligence.screenDebug.testCapture': 'Prueba de captura',
|
||||
'intelligence.screenDebug.time': 'Tiempo',
|
||||
'intelligence.screenDebug.title': 'Título',
|
||||
'intelligence.screenDebug.unknown': 'Desconocido',
|
||||
'intelligence.screenDebug.visionQueue': 'Cola de visión',
|
||||
'intelligence.screenDebug.visionState': 'Estado de visión',
|
||||
'intelligence.tasks.activeBoardOne': '1 tablero activo entre conversaciones',
|
||||
'intelligence.tasks.activeBoardOther': '{count} tableros activos entre conversaciones',
|
||||
'intelligence.tasks.empty': 'Sin tableros de tareas de agente aún',
|
||||
@@ -4550,7 +4466,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Entrada de voz activa',
|
||||
'overlay.companion.error': 'error',
|
||||
'overlay.companion.listening': 'Escuchando…',
|
||||
'overlay.companion.pointing': 'Señalando…',
|
||||
'overlay.companion.speaking': 'Hablando…',
|
||||
'overlay.companion.thinking': 'Pensando…',
|
||||
'overlay.orbTitle': 'Arrastra para mover · Doble clic para restablecer posición',
|
||||
@@ -4582,13 +4497,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Descripción de canales de mensajería',
|
||||
'pages.settings.features.notifications': 'Notificaciones',
|
||||
'pages.settings.features.notificationsDesc': 'Descripción de notificaciones',
|
||||
'pages.settings.features.screenAwareness': 'Conciencia de pantalla',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Deja que tu agente vea tu pantalla para ofrecerte ayuda oportuna y en contexto.',
|
||||
'pages.settings.features.tools': 'Herramientas',
|
||||
'pages.settings.features.toolsDesc': 'Descripción de herramientas',
|
||||
'pages.settings.featuresSection.description':
|
||||
'Conciencia de pantalla, mensajería y herramientas.',
|
||||
'pages.settings.featuresSection.title': 'Funciones',
|
||||
'privacy.dataKind.credentials': 'Credenciales',
|
||||
'privacy.dataKind.derived': 'Derivado',
|
||||
@@ -5355,9 +5265,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Configuración de IA',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Proveedores en la nube, modelos locales de Ollama y enrutamiento por carga de trabajo',
|
||||
'settings.developerMenu.screenAwareness.title': 'Conciencia de pantalla',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Permisos de captura de pantalla, política de supervisión y controles de sesión',
|
||||
'settings.developerMenu.messagingChannels.title': 'Canales de mensajería',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Configura los modos de autenticación de Telegram/Discord y el enrutamiento de canal predeterminado',
|
||||
@@ -6022,20 +5929,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Aplicar reemplazo',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Accesibilidad',
|
||||
'settings.screenIntel.permissions.grantHint': 'Sugerencia de permiso',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Monitoreo de entrada',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS aplica privacidad',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Actualizando…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Actualizando…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.requesting': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Reiniciando core…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Reiniciando core…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Grabación de pantalla',
|
||||
'settings.screenIntel.permissions.title': 'Permisos',
|
||||
'skills.card.moreActions': 'Más acciones',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6300,36 +6193,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Plantillas',
|
||||
'skills.resource.tree.empty': 'Sin recursos incluidos.',
|
||||
'skills.search.placeholder': 'Marcador de posición',
|
||||
'skills.setup.screenIntel.activeDesc': 'Descripción activa',
|
||||
'skills.setup.screenIntel.activeTitle': 'Inteligencia de pantalla está activada',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Configuración avanzada',
|
||||
'skills.setup.screenIntel.allGranted': 'Todos los permisos otorgados',
|
||||
'skills.setup.screenIntel.captureMode': 'Modo de captura',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Todas las ventanas (configurable después)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Después de otorgar permisos en Configuración del sistema, haz clic abajo para reiniciar y aplicar los cambios.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Activando...',
|
||||
'skills.setup.screenIntel.enableDesc': 's en tu pantalla y añade contexto útil a tu agente',
|
||||
'skills.setup.screenIntel.enableError': 'No se pudo activar la Inteligencia de pantalla',
|
||||
'skills.setup.screenIntel.enabling': 'Activando...',
|
||||
'skills.setup.screenIntel.grant': 'Abriendo...',
|
||||
'skills.setup.screenIntel.granted': 'Otorgado',
|
||||
'skills.setup.screenIntel.macosOnly': 'Solo macOS',
|
||||
'skills.setup.screenIntel.opening': 'Abriendo...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Tecla de pánico',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Accesibilidad',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Monitoreo de entrada',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Grabación de pantalla',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Descripción de permisos',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS aplica la privacidad a:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Actualizar estado',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Reiniciando...',
|
||||
'skills.setup.screenIntel.restarting': 'Reiniciando...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Activar el skill',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Otorgar permisos',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Listo para usar',
|
||||
'skills.setup.screenIntel.title': 'Inteligencia de pantalla',
|
||||
'skills.setup.screenIntel.visionModel': 'Modelo de visión',
|
||||
'skills.setup.voice.activation': 'Activación',
|
||||
'skills.setup.voice.activeDescPrefix': 'fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'fn',
|
||||
@@ -6491,9 +6354,6 @@ const messages: TranslationMap = {
|
||||
'devOptions.menuAi': 'Configuración de IA',
|
||||
'devOptions.menuAiDesc':
|
||||
'Proveedores de nube, modelos Ollama locales y enrutamiento por carga de trabajo',
|
||||
'devOptions.menuScreenAware': 'Conciencia de pantalla',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Permisos de captura de pantalla, política de monitoreo y controles de sesión',
|
||||
'devOptions.menuMessaging': 'Canales de mensajería',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Configurar los modos de autenticación Telegram/Discord y el enrutamiento de canales predeterminado',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Mode compagnon toujours actif avec raccourci dans la barre système',
|
||||
'pages.settings.features.desktopCompanion': 'Compagnon de bureau',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
"Assistant vocal avec conscience de l'écran: écoute, voit, parle, pointe",
|
||||
'settings.companion.title': 'Compagnon de bureau',
|
||||
'settings.companion.session': 'Session',
|
||||
'settings.companion.activeLabel': 'Actif',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Raccourci clavier',
|
||||
'settings.companion.activationMode': "Mode d'activation",
|
||||
'settings.companion.sessionTtl': 'Durée de vie de la session',
|
||||
'settings.companion.screenCapture': "Capture d'écran",
|
||||
'settings.companion.appContext': "Contexte de l'application",
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Partager',
|
||||
'share.modalTitle': 'Partagez ce moment',
|
||||
@@ -122,8 +118,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.search': 'Configurez la recherche web pour votre agent.',
|
||||
'connections.header.wallet':
|
||||
'Le solde de votre portefeuille local et votre phrase de récupération.',
|
||||
'connections.header.screen':
|
||||
'Laissez votre agent voir votre écran pour offrir une aide opportune et contextuelle.',
|
||||
'connections.header.composioKey':
|
||||
'Ajoutez une clé API Composio pour débloquer des centaines d’intégrations d’applications.',
|
||||
'connections.llm.apiKeys': 'Clés API et routage',
|
||||
@@ -784,8 +778,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Subconscient',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
"Contrôler l'activité en arrière-plan de votre assistant",
|
||||
'settings.assistant.screenAwareness': "Conscience de l'écran",
|
||||
'settings.assistant.screenAwarenessDesc': "Permettre à l'assistant de voir votre fenêtre active",
|
||||
'settings.assistant.permissions': 'Autorisations',
|
||||
'settings.assistant.permissionsDesc':
|
||||
"Choisissez ce que l'assistant peut faire et où il peut travailler",
|
||||
@@ -869,7 +861,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Préférences',
|
||||
'settings.notifications.tabs.routing': 'Routage',
|
||||
'settings.features': 'Fonctionnalités',
|
||||
'settings.featuresDesc': "Surveillance de l'écran, messagerie et outils",
|
||||
'settings.aiModels': 'IA & Modèles',
|
||||
'settings.aiModelsDesc':
|
||||
'Configuration des modèles IA locaux, téléchargements et fournisseur LLM',
|
||||
@@ -968,8 +959,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Contrôles par canal',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Configurer les préférences de notifications pour chaque canal',
|
||||
'settings.features.screenAwareness': "Surveillance de l'écran",
|
||||
'settings.features.screenAwarenessDesc': "Permettre à l'assistant de voir ta fenêtre active",
|
||||
'settings.features.messaging': 'Messagerie',
|
||||
'settings.features.messagingDesc': "Paramètres d'intégration des canaux et de la messagerie",
|
||||
'settings.features.tools': 'Outils',
|
||||
@@ -2271,21 +2260,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Enregistrer les modifications',
|
||||
'settings.tools.preferencesSaved': 'Préférences enregistrées',
|
||||
'settings.tools.saveFailed': "Échec de l'enregistrement des préférences. Réessaie.",
|
||||
'settings.screenAwareness.mode': 'Mode',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Tout sauf la liste noire',
|
||||
'settings.screenAwareness.whitelistOnly': 'Liste blanche uniquement',
|
||||
'settings.screenAwareness.screenMonitoring': "Surveillance de l'écran",
|
||||
'settings.screenAwareness.saveSettings': 'Enregistrer les paramètres',
|
||||
'settings.screenAwareness.session': 'Session',
|
||||
'settings.screenAwareness.status': 'État',
|
||||
'settings.screenAwareness.active': 'Actif',
|
||||
'settings.screenAwareness.stopped': 'Arrêté',
|
||||
'settings.screenAwareness.remaining': 'Restant',
|
||||
'settings.screenAwareness.startSession': 'Démarrer la session',
|
||||
'settings.screenAwareness.stopSession': 'Arrêter la session',
|
||||
'settings.screenAwareness.analyzeNow': 'Analyser maintenant',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
"La capture d'écran et les contrôles d'autorisation de la surveillance de l'écran sont actuellement pris en charge sur macOS uniquement.",
|
||||
'connections.comingSoon': 'Bientôt disponible',
|
||||
'connections.setUp': 'Configurer',
|
||||
'connections.configured': 'Configuré',
|
||||
@@ -2652,37 +2626,6 @@ const messages: TranslationMap = {
|
||||
"Interrupteur principal. Désactivé par défaut: Ollama reste en veille. Quand activé, le résumeur d'arbre, l'intelligence d'écran et l'autocomplétion utilisent toujours le modèle local.",
|
||||
'localModel.advancedSettings': 'Paramètres avancés',
|
||||
'localModel.debugTitle': 'Débogage du modèle local',
|
||||
'screenAwareness.debugTitle': "Débogage de la surveillance de l'écran",
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Débogage et diagnostics',
|
||||
'screenAwareness.debug.collapse': 'Réduire',
|
||||
'screenAwareness.debug.expand': 'Développer',
|
||||
'screenAwareness.debug.failedToSave': "Échec de la sauvegarde de l'intelligence écran",
|
||||
'screenAwareness.debug.policyTitle': "Politique d'intelligence écran",
|
||||
'screenAwareness.debug.baselineFps': 'FPS de référence',
|
||||
'screenAwareness.debug.useVisionModel': 'Utiliser le modèle de vision',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
"Envoyer les captures d'écran à un LLM vision pour un contexte plus riche. Désactivé, seul le texte OCR est utilisé avec un LLM texte: plus rapide et sans modèle vision requis.",
|
||||
'screenAwareness.debug.keepScreenshots': "Conserver les captures d'écran",
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
"Enregistrer les captures dans l'espace de travail au lieu de les supprimer après traitement",
|
||||
'screenAwareness.debug.allowlist': "Liste d'autorisation (une règle par ligne)",
|
||||
'screenAwareness.debug.denylist': 'Liste de refus (une règle par ligne)',
|
||||
'screenAwareness.debug.saveSettings': "Enregistrer les paramètres d'intelligence écran",
|
||||
'screenAwareness.debug.sessionStats': 'Statistiques de session',
|
||||
'screenAwareness.debug.framesEphemeral': 'Trames (éphémères)',
|
||||
'screenAwareness.debug.panicStop': 'Arrêt de panique',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Vision',
|
||||
'screenAwareness.debug.idle': 'inactif',
|
||||
'screenAwareness.debug.visionQueue': "File d'attente vision",
|
||||
'screenAwareness.debug.lastVision': 'Dernière vision',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Résumés de vision',
|
||||
'screenAwareness.debug.refreshing': 'Actualisation…',
|
||||
'screenAwareness.debug.noSummaries': "Aucun résumé pour l'instant.",
|
||||
'screenAwareness.debug.unknownApp': 'Application inconnue',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Screen Intelligence V1 est actuellement pris en charge sur macOS uniquement.',
|
||||
'memory.debugTitle': 'Débogage de la mémoire',
|
||||
'memory.documents': 'Documents',
|
||||
'memory.filterByNamespace': 'Filtrer par espace de noms...',
|
||||
@@ -3956,33 +3899,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': "Dernier diagramme d'architecture OpenHuman généré",
|
||||
'intelligence.diagram.refreshesEvery': 'Actualise toutes les {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': "Type d'entité",
|
||||
'intelligence.screenDebug.active': 'Actif',
|
||||
'intelligence.screenDebug.app': 'Application',
|
||||
'intelligence.screenDebug.bounds': 'Limites',
|
||||
'intelligence.screenDebug.captureAlt': 'Résultat du test de capture',
|
||||
'intelligence.screenDebug.captureFailed': 'Échec',
|
||||
'intelligence.screenDebug.captureSuccess': 'Succès',
|
||||
'intelligence.screenDebug.captureTest': 'Test de capture',
|
||||
'intelligence.screenDebug.capturing': 'Capture en cours',
|
||||
'intelligence.screenDebug.frames': 'Images',
|
||||
'intelligence.screenDebug.idle': 'En veille',
|
||||
'intelligence.screenDebug.lastApp': 'Dernière app',
|
||||
'intelligence.screenDebug.mode': 'Mode',
|
||||
'intelligence.screenDebug.permAccessibility': 'Permission accessibilité',
|
||||
'intelligence.screenDebug.permInput': 'Permission saisie',
|
||||
'intelligence.screenDebug.permScreen': 'Accessibilité',
|
||||
'intelligence.screenDebug.permissions': 'Autorisations',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Plateforme non prise en charge',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Résumés de vision récents',
|
||||
'intelligence.screenDebug.session': 'Session',
|
||||
'intelligence.screenDebug.size': 'Taille',
|
||||
'intelligence.screenDebug.status': 'État',
|
||||
'intelligence.screenDebug.testCapture': 'Test de capture',
|
||||
'intelligence.screenDebug.time': 'Temps',
|
||||
'intelligence.screenDebug.title': 'Titre',
|
||||
'intelligence.screenDebug.unknown': 'Inconnu',
|
||||
'intelligence.screenDebug.visionQueue': 'File de vision',
|
||||
'intelligence.screenDebug.visionState': 'État de la vision',
|
||||
'intelligence.tasks.activeBoardOne': '1 tableau actif dans les conversations',
|
||||
'intelligence.tasks.activeBoardOther': '{count} tableaux actifs dans les conversations',
|
||||
'intelligence.tasks.empty': "Aucun tableau de tâches agent pour l'instant",
|
||||
@@ -4579,7 +4495,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Saisie vocale active',
|
||||
'overlay.companion.error': 'Erreur',
|
||||
'overlay.companion.listening': 'À l’écoute…',
|
||||
'overlay.companion.pointing': 'En train de pointer…',
|
||||
'overlay.companion.speaking': 'En train de parler…',
|
||||
'overlay.companion.thinking': 'Réflexion…',
|
||||
'overlay.orbTitle': 'Glisse pour déplacer · Double-clique pour réinitialiser la position',
|
||||
@@ -4611,12 +4526,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Description des canaux de messagerie',
|
||||
'pages.settings.features.notifications': 'Notifications',
|
||||
'pages.settings.features.notificationsDesc': 'Description des notifications',
|
||||
'pages.settings.features.screenAwareness': "Surveillance de l'écran",
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Laissez votre agent voir votre écran pour offrir une aide opportune et contextuelle.',
|
||||
'pages.settings.features.tools': 'Outils',
|
||||
'pages.settings.features.toolsDesc': 'Description des outils',
|
||||
'pages.settings.featuresSection.description': "Surveillance de l'écran, messagerie et outils.",
|
||||
'pages.settings.featuresSection.title': 'Fonctionnalités',
|
||||
'privacy.dataKind.credentials': 'Identifiants',
|
||||
'privacy.dataKind.derived': 'Dérivé',
|
||||
@@ -5387,9 +5298,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Configuration IA',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Fournisseurs cloud, modèles Ollama locaux et routage par charge de travail',
|
||||
'settings.developerMenu.screenAwareness.title': "Conscience de l'écran",
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
"Autorisations de capture d'écran, politique de surveillance et contrôles de session",
|
||||
'settings.developerMenu.messagingChannels.title': 'Canaux de messagerie',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
"Configure les modes d'authentification Telegram/Discord et le routage de canal par défaut",
|
||||
@@ -6054,20 +5962,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Appliquer le remplacement',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Accessibilité',
|
||||
'settings.screenIntel.permissions.grantHint': "Indice d'autorisation",
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Surveillance des entrées',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS applique la confidentialité',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Demande en cours…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Actualisation…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Actualisation…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Demande en cours…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Demande en cours…',
|
||||
'settings.screenIntel.permissions.requesting': 'Demande en cours…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Redémarrage du core…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Redémarrage du core…',
|
||||
'settings.screenIntel.permissions.screenRecording': "Enregistrement d'écran",
|
||||
'settings.screenIntel.permissions.title': 'Autorisations',
|
||||
'skills.card.moreActions': "Plus d'actions",
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6332,37 +6226,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Modèles',
|
||||
'skills.resource.tree.empty': 'Aucune ressource groupée.',
|
||||
'skills.search.placeholder': 'Espace réservé',
|
||||
'skills.setup.screenIntel.activeDesc': 'Description active',
|
||||
'skills.setup.screenIntel.activeTitle': "L'intelligence d'écran est activée",
|
||||
'skills.setup.screenIntel.advancedSettings': 'Paramètres avancés',
|
||||
'skills.setup.screenIntel.allGranted': 'Toutes les permissions accordées',
|
||||
'skills.setup.screenIntel.captureMode': 'Mode de capture',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Toutes les fenêtres (configurable plus tard)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Après avoir accordé les permissions dans les Paramètres système, clique ci-dessous pour redémarrer et prendre en compte les modifications.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Activation…',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
's sur ton écran et injecte du contexte utile dans ton agent',
|
||||
'skills.setup.screenIntel.enableError': "Échec de l'activation de l'intelligence d'écran",
|
||||
'skills.setup.screenIntel.enabling': 'Activation…',
|
||||
'skills.setup.screenIntel.grant': 'Ouverture…',
|
||||
'skills.setup.screenIntel.granted': 'Accordé',
|
||||
'skills.setup.screenIntel.macosOnly': 'macOS uniquement',
|
||||
'skills.setup.screenIntel.opening': 'Ouverture…',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Raccourci de panique',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Accessibilité',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Surveillance des entrées',
|
||||
'skills.setup.screenIntel.permScreenRecording': "Enregistrement d'écran",
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Description des permissions',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS applique la confidentialité à :',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Actualiser le statut',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Redémarrage…',
|
||||
'skills.setup.screenIntel.restarting': 'Redémarrage…',
|
||||
'skills.setup.screenIntel.stepEnable': 'Activer la compétence',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Accorder les permissions',
|
||||
'skills.setup.screenIntel.stepSuccess': "Prêt à l'emploi",
|
||||
'skills.setup.screenIntel.title': "Intelligence d'écran",
|
||||
'skills.setup.screenIntel.visionModel': 'Modèle de vision',
|
||||
'skills.setup.voice.activation': 'Activation',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6524,9 +6387,6 @@ const messages: TranslationMap = {
|
||||
'devOptions.menuAi': "Configuration de l'IA",
|
||||
'devOptions.menuAiDesc':
|
||||
'Fournisseurs de cloud, modèles Ollama locaux et routage par charge de travail',
|
||||
'devOptions.menuScreenAware': "Connaissance de l'écran",
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
"Autorisations de capture d'écran, stratégie de surveillance et contrôles de session",
|
||||
'devOptions.menuMessaging': 'Canaux de messagerie',
|
||||
'devOptions.menuMessagingDesc':
|
||||
"Configurer les modes d'authentification Telegram/Discord et le routage des canaux par défaut",
|
||||
|
||||
+1
-138
@@ -7,8 +7,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanion': 'डेस्कटॉप साथी',
|
||||
'settings.assistant.desktopCompanionDesc': 'सिस्टम ट्रे शॉर्टकट के साथ हमेशा-चालू साथी मोड',
|
||||
'pages.settings.features.desktopCompanion': 'डेस्कटॉप कंपैनियन',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'स्क्रीन जागरूकता के साथ वॉयस सहायक: सुनता है, देखता है, बोलता है, इशारा करता है',
|
||||
'settings.companion.title': 'डेस्कटॉप साथी',
|
||||
'settings.companion.session': 'सत्र',
|
||||
'settings.companion.activeLabel': 'सक्रिय',
|
||||
@@ -24,8 +22,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'हॉटकी',
|
||||
'settings.companion.activationMode': 'सक्रियण मोड',
|
||||
'settings.companion.sessionTtl': 'सत्र टीटीएल',
|
||||
'settings.companion.screenCapture': 'स्क्रीन कैप्चर',
|
||||
'settings.companion.appContext': 'ऐप संदर्भ',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'साझा करें',
|
||||
'share.modalTitle': 'इस पल को साझा करें',
|
||||
@@ -111,8 +107,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'मेमोरी खोज के लिए उपयोग किया जाने वाला एम्बेडिंग मॉडल चुनें।',
|
||||
'connections.header.search': 'अपने एजेंट के लिए वेब खोज कॉन्फ़िगर करें।',
|
||||
'connections.header.wallet': 'आपके स्थानीय वॉलेट का बैलेंस और रिकवरी वाक्यांश।',
|
||||
'connections.header.screen':
|
||||
'समय पर, संदर्भ के अनुरूप मदद देने के लिए अपने एजेंट को अपनी स्क्रीन देखने दें।',
|
||||
'connections.header.composioKey':
|
||||
'सैकड़ों ऐप एकीकरण अनलॉक करने के लिए एक Composio API कुंजी जोड़ें।',
|
||||
'connections.llm.apiKeys': 'API कुंजियाँ और रूटिंग',
|
||||
@@ -760,8 +754,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'सबकॉन्शस',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'नियंत्रित करें कि आपका सहायक पृष्ठभूमि में कितना सक्रिय काम करे',
|
||||
'settings.assistant.screenAwareness': 'स्क्रीन जागरूकता',
|
||||
'settings.assistant.screenAwarenessDesc': 'सहायक को आपकी सक्रिय विंडो देखने दें',
|
||||
'settings.assistant.permissions': 'अनुमतियाँ',
|
||||
'settings.assistant.permissionsDesc': 'चुनें कि सहायक क्या कर सकता है और कहाँ काम कर सकता है',
|
||||
'settings.privacySecurity.privacy': 'गोपनीयता',
|
||||
@@ -841,7 +833,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'प्राथमिकताएँ',
|
||||
'settings.notifications.tabs.routing': 'रूटिंग',
|
||||
'settings.features': 'फीचर्स',
|
||||
'settings.featuresDesc': 'स्क्रीन अवेयरनेस, मैसेजिंग और टूल्स',
|
||||
'settings.aiModels': 'AI और मॉडल्स',
|
||||
'settings.aiModelsDesc': 'लोकल AI मॉडल सेटअप, डाउनलोड और LLM प्रोवाइडर',
|
||||
'settings.ai': 'AI कॉन्फिगरेशन',
|
||||
@@ -933,8 +924,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': 'तय समय के लिए सभी नोटिफिकेशन रोकें',
|
||||
'settings.notifications.channelControls': 'चैनल-वाइज़ कंट्रोल',
|
||||
'settings.notifications.channelControlsDesc': 'हर चैनल के लिए नोटिफिकेशन प्रेफरेंस सेट करें',
|
||||
'settings.features.screenAwareness': 'स्क्रीन अवेयरनेस',
|
||||
'settings.features.screenAwarenessDesc': 'असिस्टेंट को आपकी एक्टिव विंडो देखने दें',
|
||||
'settings.features.messaging': 'मैसेजिंग',
|
||||
'settings.features.messagingDesc': 'चैनल और मैसेजिंग इंटीग्रेशन सेटिंग्स',
|
||||
'settings.features.tools': 'टूल्स',
|
||||
@@ -2203,21 +2192,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'बदलाव सेव करें',
|
||||
'settings.tools.preferencesSaved': 'प्रेफरेंस सेव हो गई',
|
||||
'settings.tools.saveFailed': 'प्रेफरेंस सेव नहीं हो पाई। दोबारा कोशिश करें।',
|
||||
'settings.screenAwareness.mode': 'मोड',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'ब्लैकलिस्ट छोड़कर सब',
|
||||
'settings.screenAwareness.whitelistOnly': 'केवल व्हाइटलिस्ट',
|
||||
'settings.screenAwareness.screenMonitoring': 'स्क्रीन मॉनिटरिंग',
|
||||
'settings.screenAwareness.saveSettings': 'सेटिंग्स सेव करें',
|
||||
'settings.screenAwareness.session': 'सेशन',
|
||||
'settings.screenAwareness.status': 'स्टेटस',
|
||||
'settings.screenAwareness.active': 'एक्टिव',
|
||||
'settings.screenAwareness.stopped': 'रुका हुआ',
|
||||
'settings.screenAwareness.remaining': 'बचा हुआ',
|
||||
'settings.screenAwareness.startSession': 'सेशन शुरू करें',
|
||||
'settings.screenAwareness.stopSession': 'सेशन रोकें',
|
||||
'settings.screenAwareness.analyzeNow': 'अभी एनालाइज़ करें',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'स्क्रीन अवेयरनेस डेस्कटॉप कैप्चर और परमिशन कंट्रोल अभी केवल macOS पर सपोर्ट हैं।',
|
||||
'connections.comingSoon': 'जल्द आ रहा है',
|
||||
'connections.setUp': 'सेट अप करें',
|
||||
'connections.configured': 'कॉन्फिगर हो गया',
|
||||
@@ -2574,39 +2548,9 @@ const messages: TranslationMap = {
|
||||
'चुनें कि कौन से सबसिस्टम लोकल मॉडल पर चलें। बंद होने पर क्लाउड इस्तेमाल होगा।',
|
||||
'localModel.enableRuntime': 'लोकल AI रनटाइम चालू करें',
|
||||
'localModel.enableRuntimeDesc':
|
||||
'मास्टर स्विच। डिफ़ॉल्ट रूप से बंद: Ollama आइडल रहता है। चालू होने पर tree summarizer, screen intelligence और autocomplete हमेशा लोकल मॉडल इस्तेमाल करते हैं।',
|
||||
'मास्टर स्विच। डिफ़ॉल्ट रूप से बंद: Ollama आइडल रहता है। चालू होने पर tree summarizer और autocomplete हमेशा लोकल मॉडल इस्तेमाल करते हैं।',
|
||||
'localModel.advancedSettings': 'एडवांस्ड सेटिंग्स',
|
||||
'localModel.debugTitle': 'लोकल मॉडल डिबग',
|
||||
'screenAwareness.debugTitle': 'स्क्रीन अवेयरनेस डिबग',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'डिबग और डायग्नोस्टिक्स',
|
||||
'screenAwareness.debug.collapse': 'पतन',
|
||||
'screenAwareness.debug.expand': 'विस्तार करें',
|
||||
'screenAwareness.debug.failedToSave': 'स्क्रीन इंटेलिजेंस सहेजने में विफल',
|
||||
'screenAwareness.debug.policyTitle': 'स्क्रीन इंटेलिजेंस नीति',
|
||||
'screenAwareness.debug.baselineFps': 'बेसलाइन एफपीएस',
|
||||
'screenAwareness.debug.useVisionModel': 'विज़न मॉडल का उपयोग करें',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'समृद्ध संदर्भ के लिए स्क्रीनशॉट को विज़न LLM को भेजें। बंद होने पर केवल OCR टेक्स्ट का उपयोग टेक्स्ट LLM के साथ किया जाता है: तेज़ और विज़न मॉडल की आवश्यकता नहीं।',
|
||||
'screenAwareness.debug.keepScreenshots': 'स्क्रीनशॉट रखें',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'प्रोसेसिंग के बाद हटाने की बजाय कैप्चर किए गए स्क्रीनशॉट वर्कस्पेस में सहेजें',
|
||||
'screenAwareness.debug.allowlist': 'अनुमति सूची (प्रति पंक्ति एक नियम)',
|
||||
'screenAwareness.debug.denylist': 'अस्वीकृत सूची (प्रति पंक्ति एक नियम)',
|
||||
'screenAwareness.debug.saveSettings': 'स्क्रीन इंटेलिजेंस सेटिंग्स सहेजें',
|
||||
'screenAwareness.debug.sessionStats': 'सत्र आँकड़े',
|
||||
'screenAwareness.debug.framesEphemeral': 'फ़्रेम (क्षणिक)',
|
||||
'screenAwareness.debug.panicStop': 'घबराना बंद करो',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'सीएमडी+शिफ्ट+.',
|
||||
'screenAwareness.debug.vision': 'दृष्टि',
|
||||
'screenAwareness.debug.idle': 'निष्क्रिय',
|
||||
'screenAwareness.debug.visionQueue': 'दृष्टि कतार',
|
||||
'screenAwareness.debug.lastVision': 'अंतिम दर्शन',
|
||||
'screenAwareness.debug.notAvailable': 'एन/ए',
|
||||
'screenAwareness.debug.visionSummaries': 'दृष्टि सारांश',
|
||||
'screenAwareness.debug.refreshing': 'ताज़ा...',
|
||||
'screenAwareness.debug.noSummaries': 'अभी तक कोई सारांश नहीं.',
|
||||
'screenAwareness.debug.unknownApp': 'अज्ञात ऐप',
|
||||
'screenAwareness.debug.macosOnly': 'स्क्रीन इंटेलिजेंस V1 वर्तमान में केवल macOS पर समर्थित है।',
|
||||
'memory.debugTitle': 'मेमोरी डिबग',
|
||||
'memory.documents': 'दस्तावेज़',
|
||||
'memory.filterByNamespace': 'नामस्थान के अनुसार फ़िल्टर करें...',
|
||||
@@ -3862,33 +3806,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'OpenHuman का नवीनतम जेनरेटेड आर्किटेक्चर डायग्राम',
|
||||
'intelligence.diagram.refreshesEvery': 'हर {seconds}s पर रीफ्रेश होता है',
|
||||
'intelligence.memoryText.entityTypePrefix': 'इकाई प्रकार',
|
||||
'intelligence.screenDebug.active': 'एक्टिव',
|
||||
'intelligence.screenDebug.app': 'ऐप',
|
||||
'intelligence.screenDebug.bounds': 'बाउंड्स',
|
||||
'intelligence.screenDebug.captureAlt': 'कैप्चर टेस्ट रिज़ल्ट',
|
||||
'intelligence.screenDebug.captureFailed': 'विफल',
|
||||
'intelligence.screenDebug.captureSuccess': 'सफल',
|
||||
'intelligence.screenDebug.captureTest': 'कैप्चर टेस्ट',
|
||||
'intelligence.screenDebug.capturing': 'कैप्चर हो रहा है',
|
||||
'intelligence.screenDebug.frames': 'फ्रेम',
|
||||
'intelligence.screenDebug.idle': 'आइडल',
|
||||
'intelligence.screenDebug.lastApp': 'आखिरी ऐप',
|
||||
'intelligence.screenDebug.mode': 'मोड',
|
||||
'intelligence.screenDebug.permAccessibility': 'Accessibility परमिशन',
|
||||
'intelligence.screenDebug.permInput': 'इनपुट परमिशन',
|
||||
'intelligence.screenDebug.permScreen': 'स्क्रीन रिकॉर्डिंग अनुमति',
|
||||
'intelligence.screenDebug.permissions': 'परमिशन',
|
||||
'intelligence.screenDebug.platformNotSupported': 'प्लेटफॉर्म सपोर्टेड नहीं',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'हाल के विज़न समरीज़',
|
||||
'intelligence.screenDebug.session': 'सेशन',
|
||||
'intelligence.screenDebug.size': 'साइज़',
|
||||
'intelligence.screenDebug.status': 'स्टेटस',
|
||||
'intelligence.screenDebug.testCapture': 'टेस्ट कैप्चर',
|
||||
'intelligence.screenDebug.time': 'समय',
|
||||
'intelligence.screenDebug.title': 'टाइटल',
|
||||
'intelligence.screenDebug.unknown': 'अज्ञात',
|
||||
'intelligence.screenDebug.visionQueue': 'विज़न क्यू',
|
||||
'intelligence.screenDebug.visionState': 'विज़न स्टेट',
|
||||
'intelligence.tasks.activeBoardOne': 'बातचीतों में 1 सक्रिय बोर्ड',
|
||||
'intelligence.tasks.activeBoardOther': 'बातचीतों में {count} सक्रिय बोर्ड',
|
||||
'intelligence.tasks.empty': 'अभी कोई एजेंट टास्क बोर्ड नहीं',
|
||||
@@ -4472,7 +4389,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'वॉइस इनपुट एक्टिव',
|
||||
'overlay.companion.error': 'त्रुटि',
|
||||
'overlay.companion.listening': 'सुन रहा है…',
|
||||
'overlay.companion.pointing': 'इशारा कर रहा है…',
|
||||
'overlay.companion.speaking': 'बोल रहा है…',
|
||||
'overlay.companion.thinking': 'सोच रहा है…',
|
||||
'overlay.orbTitle': 'मूव करने के लिए खींचें · पोज़िशन रीसेट के लिए डबल-क्लिक करें',
|
||||
@@ -4502,12 +4418,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'मैसेजिंग चैनल विवरण',
|
||||
'pages.settings.features.notifications': 'नोटिफिकेशन',
|
||||
'pages.settings.features.notificationsDesc': 'नोटिफ़िकेशन विवरण',
|
||||
'pages.settings.features.screenAwareness': 'स्क्रीन अवेयरनेस',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'समय पर, संदर्भ के अनुरूप मदद देने के लिए अपने एजेंट को अपनी स्क्रीन देखने दें।',
|
||||
'pages.settings.features.tools': 'टूल्स',
|
||||
'pages.settings.features.toolsDesc': 'टूल्स विवरण',
|
||||
'pages.settings.featuresSection.description': 'स्क्रीन अवेयरनेस, मैसेजिंग और टूल्स।',
|
||||
'pages.settings.featuresSection.title': 'फीचर्स',
|
||||
'privacy.dataKind.credentials': 'क्रेडेंशियल',
|
||||
'privacy.dataKind.derived': 'डिराइव्ड',
|
||||
@@ -5263,9 +5175,6 @@ const messages: TranslationMap = {
|
||||
'settings.localModel.status.working': 'काम हो रहा है...',
|
||||
'settings.developerMenu.ai.title': 'AI कॉन्फ़िगरेशन',
|
||||
'settings.developerMenu.ai.desc': 'क्लाउड प्रदाता, स्थानीय Ollama मॉडल, और प्रति-वर्कलोड रूटिंग',
|
||||
'settings.developerMenu.screenAwareness.title': 'स्क्रीन अवेयरनेस',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'स्क्रीन कैप्चर अनुमतियां, मॉनिटरिंग नीति, और सत्र नियंत्रण',
|
||||
'settings.developerMenu.messagingChannels.title': 'मैसेजिंग चैनल',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Telegram/Discord प्रमाणीकरण मोड और डिफ़ॉल्ट चैनल रूटिंग कॉन्फ़िगर करें',
|
||||
@@ -5908,20 +5817,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'प्रतिस्थापन लागू करें',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'एक्सेसिबिलिटी',
|
||||
'settings.screenIntel.permissions.grantHint': 'स्वीकृति संकेत',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'इनपुट मॉनिटरिंग',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS गोपनीयता लागू करता है',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'रिक्वेस्ट हो रही है…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'रिफ्रेश हो रहा है…',
|
||||
'settings.screenIntel.permissions.refreshing': 'रिफ्रेश हो रहा है…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'रिक्वेस्ट हो रही है…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'रिक्वेस्ट हो रही है…',
|
||||
'settings.screenIntel.permissions.requesting': 'रिक्वेस्ट हो रही है…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'कोर रीस्टार्ट हो रहा है…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'कोर रीस्टार्ट हो रहा है…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'स्क्रीन रिकॉर्डिंग',
|
||||
'settings.screenIntel.permissions.title': 'परमिशन',
|
||||
'skills.card.moreActions': 'और एक्शन',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6183,36 +6078,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'टेम्पलेट',
|
||||
'skills.resource.tree.empty': 'कोई बंडल्ड रिसोर्स नहीं।',
|
||||
'skills.search.placeholder': 'प्लेसहोल्डर',
|
||||
'skills.setup.screenIntel.activeDesc': 'सक्रिय विवरण',
|
||||
'skills.setup.screenIntel.activeTitle': 'Screen Intelligence चालू है',
|
||||
'skills.setup.screenIntel.advancedSettings': 'एडवांस्ड सेटिंग्स',
|
||||
'skills.setup.screenIntel.allGranted': 'सभी परमिशन मिल गईं',
|
||||
'skills.setup.screenIntel.captureMode': 'कैप्चर मोड',
|
||||
'skills.setup.screenIntel.captureModeValue': 'सभी विंडो (बाद में बदल सकते हैं)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'System Settings में परमिशन देने के बाद, बदलाव लागू करने के लिए नीचे क्लिक करके रीस्टार्ट करें।',
|
||||
'skills.setup.screenIntel.enableBtn': 'चालू हो रहा है...',
|
||||
'skills.setup.screenIntel.enableDesc': 'आपकी स्क्रीन पर और एजेंट को उपयोगी कॉन्टेक्स्ट दें',
|
||||
'skills.setup.screenIntel.enableError': 'Screen Intelligence चालू नहीं हो पाई',
|
||||
'skills.setup.screenIntel.enabling': 'चालू हो रहा है...',
|
||||
'skills.setup.screenIntel.grant': 'खुल रहा है...',
|
||||
'skills.setup.screenIntel.granted': 'मिल गई',
|
||||
'skills.setup.screenIntel.macosOnly': 'केवल macOS',
|
||||
'skills.setup.screenIntel.opening': 'खुल रहा है...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'पैनिक हॉटकी',
|
||||
'skills.setup.screenIntel.permAccessibility': 'एक्सेसिबिलिटी',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'इनपुट मॉनिटरिंग',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'स्क्रीन रिकॉर्डिंग',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'अनुमतियाँ विवरण',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS निम्नलिखित पर गोपनीयता लागू करता है:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'स्टेटस रिफ्रेश करें',
|
||||
'skills.setup.screenIntel.restartRefresh': 'रीस्टार्ट हो रहा है...',
|
||||
'skills.setup.screenIntel.restarting': 'रीस्टार्ट हो रहा है...',
|
||||
'skills.setup.screenIntel.stepEnable': 'स्किल चालू करें',
|
||||
'skills.setup.screenIntel.stepPermissions': 'परमिशन दें',
|
||||
'skills.setup.screenIntel.stepSuccess': 'तैयार है',
|
||||
'skills.setup.screenIntel.title': 'स्क्रीन इंटेलिजेंस',
|
||||
'skills.setup.screenIntel.visionModel': 'विज़न मॉडल',
|
||||
'skills.setup.voice.activation': 'एक्टिवेशन',
|
||||
'skills.setup.voice.activeDescPrefix': 'एफ.एन',
|
||||
'skills.setup.voice.activeDescSuffix': 'एफ.एन',
|
||||
@@ -6372,8 +6237,6 @@ const messages: TranslationMap = {
|
||||
'सेटअप निर्देशों के लिए, फिर आपका रनटाइम पहुँच योग्य होने के बाद पुनः प्रयास करें।',
|
||||
'devOptions.menuAi': 'एआई कॉन्फ़िगरेशन',
|
||||
'devOptions.menuAiDesc': 'क्लाउड प्रदाता, स्थानीय Ollama मॉडल, और प्रति-वर्कलोड रूटिंग',
|
||||
'devOptions.menuScreenAware': 'स्क्रीन जागरूकता',
|
||||
'devOptions.menuScreenAwareDesc': 'स्क्रीन कैप्चर अनुमतियाँ, निगरानी नीति और सत्र नियंत्रण',
|
||||
'devOptions.menuMessaging': 'मैसेजिंग चैनल',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Telegram/Discord प्रमाणीकरण मोड और डिफ़ॉल्ट चैनल रूटिंग कॉन्फ़िगर करें',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Mode pendamping selalu aktif dengan pintasan baki sistem',
|
||||
'pages.settings.features.desktopCompanion': 'Pendamping Desktop',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Asisten suara dengan kesadaran layar: mendengar, melihat, berbicara, menunjuk',
|
||||
'settings.companion.title': 'Pendamping Desktop',
|
||||
'settings.companion.session': 'Sesi',
|
||||
'settings.companion.activeLabel': 'Aktif',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Tombol Pintas',
|
||||
'settings.companion.activationMode': 'Mode Aktivasi',
|
||||
'settings.companion.sessionTtl': 'TTL Sesi',
|
||||
'settings.companion.screenCapture': 'Tangkapan Layar',
|
||||
'settings.companion.appContext': 'Konteks Aplikasi',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Bagikan',
|
||||
'share.modalTitle': 'Bagikan momen ini',
|
||||
@@ -116,8 +112,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'Pilih model embedding yang digunakan untuk pencarian memori.',
|
||||
'connections.header.search': 'Konfigurasikan pencarian web untuk agen Anda.',
|
||||
'connections.header.wallet': 'Saldo dompet lokal Anda dan frasa pemulihan Anda.',
|
||||
'connections.header.screen':
|
||||
'Biarkan agen Anda melihat layar Anda untuk memberikan bantuan tepat waktu dan sesuai konteks.',
|
||||
'connections.header.composioKey':
|
||||
'Tambahkan kunci API Composio untuk membuka ratusan integrasi aplikasi.',
|
||||
'connections.llm.apiKeys': 'Kunci API & perutean',
|
||||
@@ -767,8 +761,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Bawah sadar',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Kontrol seberapa aktif asisten Anda bekerja di latar belakang',
|
||||
'settings.assistant.screenAwareness': 'Kesadaran layar',
|
||||
'settings.assistant.screenAwarenessDesc': 'Biarkan asisten melihat jendela aktif Anda',
|
||||
'settings.assistant.permissions': 'Izin',
|
||||
'settings.assistant.permissionsDesc':
|
||||
'Pilih apa yang dapat dilakukan asisten dan di mana ia dapat bekerja',
|
||||
@@ -851,7 +843,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferensi',
|
||||
'settings.notifications.tabs.routing': 'Perutean',
|
||||
'settings.features': 'Fitur',
|
||||
'settings.featuresDesc': 'Kesadaran layar, pesan, dan alat',
|
||||
'settings.aiModels': 'AI & Model',
|
||||
'settings.aiModelsDesc': 'Pengaturan model AI lokal, unduhan, dan penyedia LLM',
|
||||
'settings.ai': 'AI',
|
||||
@@ -944,8 +935,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': 'Jeda semua notifikasi selama periode tertentu',
|
||||
'settings.notifications.channelControls': 'Kontrol Per Kanal',
|
||||
'settings.notifications.channelControlsDesc': 'Atur preferensi notifikasi untuk setiap kanal',
|
||||
'settings.features.screenAwareness': 'Kesadaran Layar',
|
||||
'settings.features.screenAwarenessDesc': 'Izinkan asisten melihat jendela aktif Anda',
|
||||
'settings.features.messaging': 'Pesan',
|
||||
'settings.features.messagingDesc': 'Pengaturan kanal dan integrasi pesan',
|
||||
'settings.features.tools': 'Alat',
|
||||
@@ -2219,21 +2208,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Simpan Perubahan',
|
||||
'settings.tools.preferencesSaved': 'Preferensi tersimpan',
|
||||
'settings.tools.saveFailed': 'Gagal menyimpan preferensi. Coba lagi.',
|
||||
'settings.screenAwareness.mode': 'Mode',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Semua Kecuali Blacklist',
|
||||
'settings.screenAwareness.whitelistOnly': 'Whitelist Saja',
|
||||
'settings.screenAwareness.screenMonitoring': 'Pemantauan Layar',
|
||||
'settings.screenAwareness.saveSettings': 'Simpan Pengaturan',
|
||||
'settings.screenAwareness.session': 'Sesi',
|
||||
'settings.screenAwareness.status': 'Status',
|
||||
'settings.screenAwareness.active': 'Aktif',
|
||||
'settings.screenAwareness.stopped': 'Berhenti',
|
||||
'settings.screenAwareness.remaining': 'Tersisa',
|
||||
'settings.screenAwareness.startSession': 'Mulai Sesi',
|
||||
'settings.screenAwareness.stopSession': 'Hentikan Sesi',
|
||||
'settings.screenAwareness.analyzeNow': 'Analisis Sekarang',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'Tangkapan layar desktop dan kontrol izin Screen Awareness saat ini hanya didukung di macOS.',
|
||||
'connections.comingSoon': 'Segera hadir',
|
||||
'connections.setUp': 'Atur',
|
||||
'connections.configured': 'Dikonfigurasi',
|
||||
@@ -2591,36 +2565,6 @@ const messages: TranslationMap = {
|
||||
'Sakelar utama. Nonaktif secara default; Ollama tetap idle. Saat aktif, peringkas tree, kecerdasan layar, dan autocomplete selalu memakai model lokal.',
|
||||
'localModel.advancedSettings': 'Pengaturan lanjutan',
|
||||
'localModel.debugTitle': 'Debug Model Lokal',
|
||||
'screenAwareness.debugTitle': 'Debug Kesadaran Layar',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Debug & Diagnostik',
|
||||
'screenAwareness.debug.collapse': 'Ciutkan',
|
||||
'screenAwareness.debug.expand': 'Perluas',
|
||||
'screenAwareness.debug.failedToSave': 'Gagal menyimpan kecerdasan layar',
|
||||
'screenAwareness.debug.policyTitle': 'Kebijakan Intelijen Layar',
|
||||
'screenAwareness.debug.baselineFps': 'FPS Dasar',
|
||||
'screenAwareness.debug.useVisionModel': 'Gunakan Model Visi',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Kirim tangkapan layar ke LLM vision untuk konteks yang lebih kaya. Jika dinonaktifkan, hanya teks OCR yang digunakan dengan LLM teks: lebih cepat dan tidak memerlukan model vision.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Simpan Tangkapan Layar',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Simpan tangkapan layar yang diambil ke workspace alih-alih menghapusnya setelah diproses',
|
||||
'screenAwareness.debug.allowlist': 'Daftar yang diizinkan (satu aturan per baris)',
|
||||
'screenAwareness.debug.denylist': 'Daftar Tolak (satu aturan per baris)',
|
||||
'screenAwareness.debug.saveSettings': 'Simpan Pengaturan Kecerdasan Layar',
|
||||
'screenAwareness.debug.sessionStats': 'Statistik Sesi',
|
||||
'screenAwareness.debug.framesEphemeral': 'Bingkai (sementara)',
|
||||
'screenAwareness.debug.panicStop': 'Berhenti panik',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Vision',
|
||||
'screenAwareness.debug.idle': 'idle',
|
||||
'screenAwareness.debug.visionQueue': 'Antrean vision',
|
||||
'screenAwareness.debug.lastVision': 'Vision terakhir',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Ringkasan Vision',
|
||||
'screenAwareness.debug.refreshing': 'Menyegarkan…',
|
||||
'screenAwareness.debug.noSummaries': 'Belum ada ringkasan.',
|
||||
'screenAwareness.debug.unknownApp': 'Aplikasi Tidak Dikenal',
|
||||
'screenAwareness.debug.macosOnly': 'Screen Intelligence V1 saat ini hanya didukung di macOS.',
|
||||
'memory.debugTitle': 'Debug Memori',
|
||||
'memory.documents': 'Dokumen',
|
||||
'memory.filterByNamespace': 'Filter berdasarkan namespace...',
|
||||
@@ -3878,33 +3822,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Diagram arsitektur OpenHuman terbaru yang dihasilkan',
|
||||
'intelligence.diagram.refreshesEvery': 'Diperbarui setiap {seconds}d',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Tipe entitas',
|
||||
'intelligence.screenDebug.active': 'Aktif',
|
||||
'intelligence.screenDebug.app': 'Aplikasi',
|
||||
'intelligence.screenDebug.bounds': 'Batas',
|
||||
'intelligence.screenDebug.captureAlt': 'Hasil tes tangkapan',
|
||||
'intelligence.screenDebug.captureFailed': 'Gagal',
|
||||
'intelligence.screenDebug.captureSuccess': 'Berhasil',
|
||||
'intelligence.screenDebug.captureTest': 'Tes tangkapan',
|
||||
'intelligence.screenDebug.capturing': 'Menangkap',
|
||||
'intelligence.screenDebug.frames': 'Frame',
|
||||
'intelligence.screenDebug.idle': 'Siaga',
|
||||
'intelligence.screenDebug.lastApp': 'Aplikasi Terakhir',
|
||||
'intelligence.screenDebug.mode': 'Mode',
|
||||
'intelligence.screenDebug.permAccessibility': 'Izin aksesibilitas',
|
||||
'intelligence.screenDebug.permInput': 'Izin input',
|
||||
'intelligence.screenDebug.permScreen': 'Aksesibilitas',
|
||||
'intelligence.screenDebug.permissions': 'Izin',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Platform tidak didukung',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Ringkasan visi terbaru',
|
||||
'intelligence.screenDebug.session': 'Sesi',
|
||||
'intelligence.screenDebug.size': 'Ukuran',
|
||||
'intelligence.screenDebug.status': 'Status',
|
||||
'intelligence.screenDebug.testCapture': 'Tes tangkapan',
|
||||
'intelligence.screenDebug.time': 'Waktu',
|
||||
'intelligence.screenDebug.title': 'Judul',
|
||||
'intelligence.screenDebug.unknown': 'Tidak diketahui',
|
||||
'intelligence.screenDebug.visionQueue': 'Antrean Visi',
|
||||
'intelligence.screenDebug.visionState': 'Status Visi',
|
||||
'intelligence.tasks.activeBoardOne': '1 board aktif di seluruh percakapan',
|
||||
'intelligence.tasks.activeBoardOther': '{count} board aktif di seluruh percakapan',
|
||||
'intelligence.tasks.empty': 'Belum ada papan tugas agen',
|
||||
@@ -4489,7 +4406,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Input suara aktif',
|
||||
'overlay.companion.error': 'Kesalahan',
|
||||
'overlay.companion.listening': 'Mendengarkan…',
|
||||
'overlay.companion.pointing': 'Menunjuk…',
|
||||
'overlay.companion.speaking': 'Berbicara…',
|
||||
'overlay.companion.thinking': 'Berpikir…',
|
||||
'overlay.orbTitle': 'Seret untuk memindahkan · Klik dua kali untuk mereset posisi',
|
||||
@@ -4521,12 +4437,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Deskripsi kanal pesan',
|
||||
'pages.settings.features.notifications': 'Notifikasi',
|
||||
'pages.settings.features.notificationsDesc': 'Deskripsi notifikasi',
|
||||
'pages.settings.features.screenAwareness': 'Kesadaran layar',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Biarkan agen Anda melihat layar Anda untuk memberikan bantuan tepat waktu dan sesuai konteks.',
|
||||
'pages.settings.features.tools': 'Alat',
|
||||
'pages.settings.features.toolsDesc': 'Deskripsi alat',
|
||||
'pages.settings.featuresSection.description': 'Kesadaran layar, pesan, dan alat.',
|
||||
'pages.settings.featuresSection.title': 'Fitur',
|
||||
'privacy.dataKind.credentials': 'Kredensial',
|
||||
'privacy.dataKind.derived': 'Turunan',
|
||||
@@ -5287,9 +5199,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Konfigurasi AI',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Penyedia cloud, model Ollama lokal, dan routing per beban kerja',
|
||||
'settings.developerMenu.screenAwareness.title': 'Kesadaran Layar',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Izin tangkapan layar, kebijakan pemantauan, dan kontrol sesi',
|
||||
'settings.developerMenu.messagingChannels.title': 'Kanal Pesan',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Atur mode autentikasi Telegram/Discord dan routing kanal default',
|
||||
@@ -5937,20 +5846,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Terapkan Penggantian',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Aksesibilitas',
|
||||
'settings.screenIntel.permissions.grantHint': 'Petunjuk izin',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Pemantauan Input',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS menerapkan privasi',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Meminta...',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Menyegarkan...',
|
||||
'settings.screenIntel.permissions.refreshing': 'Menyegarkan...',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Meminta...',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Meminta...',
|
||||
'settings.screenIntel.permissions.requesting': 'Meminta...',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Memulai ulang core...',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Memulai ulang core...',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Perekaman Layar',
|
||||
'settings.screenIntel.permissions.title': 'Izin',
|
||||
'skills.card.moreActions': 'Tindakan lainnya',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6212,37 +6107,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Templat',
|
||||
'skills.resource.tree.empty': 'Tidak ada sumber daya bundel.',
|
||||
'skills.search.placeholder': 'Teks placeholder',
|
||||
'skills.setup.screenIntel.activeDesc': 'Deskripsi aktif',
|
||||
'skills.setup.screenIntel.activeTitle': 'Kecerdasan Layar Diaktifkan',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Pengaturan lanjutan',
|
||||
'skills.setup.screenIntel.allGranted': 'Semua izin diberikan',
|
||||
'skills.setup.screenIntel.captureMode': 'Mode tangkap',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Semua jendela (dapat dikonfigurasi nanti)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Setelah memberikan izin di Pengaturan Sistem, klik di bawah untuk memulai ulang dan mengambil perubahan.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Mengaktifkan...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
's di layar Anda dan memberikan konteks berguna ke agen Anda',
|
||||
'skills.setup.screenIntel.enableError': 'Gagal mengaktifkan Kecerdasan Layar',
|
||||
'skills.setup.screenIntel.enabling': 'Mengaktifkan...',
|
||||
'skills.setup.screenIntel.grant': 'Membuka...',
|
||||
'skills.setup.screenIntel.granted': 'Diberikan',
|
||||
'skills.setup.screenIntel.macosOnly': 'Hanya macOS',
|
||||
'skills.setup.screenIntel.opening': 'Membuka...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Hotkey panik',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Aksesibilitas',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Pemantauan Input',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Perekaman Layar',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Deskripsi izin',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS menerapkan privasi ke:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Segarkan status',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Memulai ulang...',
|
||||
'skills.setup.screenIntel.restarting': 'Memulai ulang...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Aktifkan skill',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Berikan izin',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Siap digunakan',
|
||||
'skills.setup.screenIntel.title': 'Kecerdasan Layar',
|
||||
'skills.setup.screenIntel.visionModel': 'Model visi',
|
||||
'skills.setup.voice.activation': 'Aktivasi',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6403,8 +6267,6 @@ const messages: TranslationMap = {
|
||||
'untuk instruksi pengaturan, lalu coba lagi setelah runtime Anda dapat dijangkau.',
|
||||
'devOptions.menuAi': 'Konfigurasi AI',
|
||||
'devOptions.menuAiDesc': 'Penyedia cloud, model Ollama lokal, dan routing per beban kerja',
|
||||
'devOptions.menuScreenAware': 'Kesadaran Layar',
|
||||
'devOptions.menuScreenAwareDesc': 'Izin tangkapan layar, kebijakan pemantauan, dan kontrol sesi',
|
||||
'devOptions.menuMessaging': 'Channel Pesan',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Konfigurasikan mode autentikasi Telegram/Discord dan routing channel bawaan',
|
||||
|
||||
+1
-142
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Modalità compagno sempre attiva con scorciatoia nella barra di sistema',
|
||||
'pages.settings.features.desktopCompanion': 'Companion Desktop',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Assistente vocale con consapevolezza dello schermo: ascolta, vede, parla, indica',
|
||||
'settings.companion.title': 'Compagno da scrivania',
|
||||
'settings.companion.session': 'Sessione',
|
||||
'settings.companion.activeLabel': 'Attivo',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Tasto di scelta rapida',
|
||||
'settings.companion.activationMode': 'Modalità di attivazione',
|
||||
'settings.companion.sessionTtl': 'Sessione TTL',
|
||||
'settings.companion.screenCapture': 'Acquisizione schermata',
|
||||
'settings.companion.appContext': 'Contesto app',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Condividi',
|
||||
'share.modalTitle': 'Condividi questo momento',
|
||||
@@ -117,8 +113,6 @@ const messages: TranslationMap = {
|
||||
'Scegli il modello di embedding usato per la ricerca nella memoria.',
|
||||
'connections.header.search': 'Configura la ricerca web per il tuo agente.',
|
||||
'connections.header.wallet': 'Il saldo del tuo wallet locale e la tua frase di recupero.',
|
||||
'connections.header.screen':
|
||||
'Lascia che il tuo agente veda il tuo schermo per offrire un aiuto tempestivo e contestuale.',
|
||||
'connections.header.composioKey':
|
||||
'Aggiungi una chiave API Composio per sbloccare centinaia di integrazioni di app.',
|
||||
'connections.llm.apiKeys': 'Chiavi API e instradamento',
|
||||
@@ -778,8 +772,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Subconscio',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Controlla quanto attivamente il tuo assistente lavora in background',
|
||||
'settings.assistant.screenAwareness': 'Consapevolezza dello schermo',
|
||||
'settings.assistant.screenAwarenessDesc': "Consenti all'assistente di vedere la finestra attiva",
|
||||
'settings.assistant.permissions': 'Autorizzazioni',
|
||||
'settings.assistant.permissionsDesc': "Scegli cosa può fare l'assistente e dove può lavorare",
|
||||
'settings.privacySecurity.privacy': 'Privacy',
|
||||
@@ -862,7 +854,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferenze',
|
||||
'settings.notifications.tabs.routing': 'Instradamento',
|
||||
'settings.features': 'Funzionalità',
|
||||
'settings.featuresDesc': 'Consapevolezza schermo, messaggistica e strumenti',
|
||||
'settings.aiModels': 'AI e modelli',
|
||||
'settings.aiModelsDesc': 'Configurazione modello AI locale, download e provider LLM',
|
||||
'settings.ai': 'Configurazione AI',
|
||||
@@ -959,8 +950,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Controlli per canale',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Configura le preferenze di notifica per ogni canale',
|
||||
'settings.features.screenAwareness': 'Consapevolezza schermo',
|
||||
'settings.features.screenAwarenessDesc': "Consenti all'assistente di vedere la finestra attiva",
|
||||
'settings.features.messaging': 'Messaggistica',
|
||||
'settings.features.messagingDesc': 'Impostazioni canale e integrazione messaggistica',
|
||||
'settings.features.tools': 'Strumenti',
|
||||
@@ -2250,21 +2239,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Salva modifiche',
|
||||
'settings.tools.preferencesSaved': 'Preferenze salvate',
|
||||
'settings.tools.saveFailed': 'Salvataggio preferenze fallito. Riprova.',
|
||||
'settings.screenAwareness.mode': 'Modalità',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Tutti tranne blacklist',
|
||||
'settings.screenAwareness.whitelistOnly': 'Solo whitelist',
|
||||
'settings.screenAwareness.screenMonitoring': 'Monitoraggio schermo',
|
||||
'settings.screenAwareness.saveSettings': 'Salva impostazioni',
|
||||
'settings.screenAwareness.session': 'Sessione',
|
||||
'settings.screenAwareness.status': 'Stato',
|
||||
'settings.screenAwareness.active': 'Attivo',
|
||||
'settings.screenAwareness.stopped': 'Fermato',
|
||||
'settings.screenAwareness.remaining': 'Rimanente',
|
||||
'settings.screenAwareness.startSession': 'Avvia sessione',
|
||||
'settings.screenAwareness.stopSession': 'Ferma sessione',
|
||||
'settings.screenAwareness.analyzeNow': 'Analizza ora',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'La cattura desktop di Consapevolezza schermo e i controlli dei permessi sono attualmente supportati solo su macOS.',
|
||||
'connections.comingSoon': 'In arrivo',
|
||||
'connections.setUp': 'Configura',
|
||||
'connections.configured': 'Configurato',
|
||||
@@ -2625,40 +2599,9 @@ const messages: TranslationMap = {
|
||||
'Scegli quali sottosistemi girano sul modello locale. Quelli disattivati usano il cloud.',
|
||||
'localModel.enableRuntime': 'Abilita runtime AI locale',
|
||||
'localModel.enableRuntimeDesc':
|
||||
"Interruttore principale. Disattivato di default: Ollama resta inattivo. Quando attivo, il tree summarizer, lo screen intelligence e l'autocompletamento usano sempre il modello locale.",
|
||||
"Interruttore principale. Disattivato di default: Ollama resta inattivo. Quando attivo, il tree summarizer e l'autocompletamento usano sempre il modello locale.",
|
||||
'localModel.advancedSettings': 'Impostazioni avanzate',
|
||||
'localModel.debugTitle': 'Debug modello locale',
|
||||
'screenAwareness.debugTitle': 'Debug consapevolezza schermo',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Debug e diagnostica',
|
||||
'screenAwareness.debug.collapse': 'Comprimi',
|
||||
'screenAwareness.debug.expand': 'Espandi',
|
||||
'screenAwareness.debug.failedToSave': 'Impossibile salvare i dati di Screen Intelligence',
|
||||
'screenAwareness.debug.policyTitle': 'Policy di Screen Intelligence',
|
||||
'screenAwareness.debug.baselineFps': 'FPS di base',
|
||||
'screenAwareness.debug.useVisionModel': 'Utilizza modello di visione',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Invia gli screenshot a un LLM con visione per un contesto più ricco. Se disattivato, viene utilizzato solo il testo OCR con un LLM testuale: più veloce e senza modello visivo.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Conserva screenshot',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
"Salva gli screenshot acquisiti nell'area di lavoro invece di eliminarli dopo l'elaborazione",
|
||||
'screenAwareness.debug.allowlist': 'Lista consentita (una regola per riga)',
|
||||
'screenAwareness.debug.denylist': 'Lista vietata (una regola per riga)',
|
||||
'screenAwareness.debug.saveSettings': 'Salva impostazioni di Screen Intelligence',
|
||||
'screenAwareness.debug.sessionStats': 'Statistiche sessione',
|
||||
'screenAwareness.debug.framesEphemeral': 'Frame (effimeri)',
|
||||
'screenAwareness.debug.panicStop': 'Arresto antipanico',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Maiusc+.',
|
||||
'screenAwareness.debug.vision': 'Visione',
|
||||
'screenAwareness.debug.idle': 'inattiva',
|
||||
'screenAwareness.debug.visionQueue': 'Coda visioni',
|
||||
'screenAwareness.debug.lastVision': 'Ultima visione',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Riepiloghi visione',
|
||||
'screenAwareness.debug.refreshing': 'Aggiornamento in corso…',
|
||||
'screenAwareness.debug.noSummaries': 'Ancora nessun riepilogo.',
|
||||
'screenAwareness.debug.unknownApp': 'App sconosciuta',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Screen Intelligence V1 è attualmente supportato solo su macOS.',
|
||||
'memory.debugTitle': 'Debug memoria',
|
||||
'memory.documents': 'Documenti',
|
||||
'memory.filterByNamespace': 'Filtra per spazio dei nomi...',
|
||||
@@ -3928,33 +3871,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': "Ultimo diagramma dell'architettura OpenHuman generato",
|
||||
'intelligence.diagram.refreshesEvery': 'Si aggiorna ogni {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Tipo di entità',
|
||||
'intelligence.screenDebug.active': 'Attivo',
|
||||
'intelligence.screenDebug.app': 'App',
|
||||
'intelligence.screenDebug.bounds': 'Limiti',
|
||||
'intelligence.screenDebug.captureAlt': 'Risultato test di cattura',
|
||||
'intelligence.screenDebug.captureFailed': 'Fallito',
|
||||
'intelligence.screenDebug.captureSuccess': 'Successo',
|
||||
'intelligence.screenDebug.captureTest': 'Test di cattura',
|
||||
'intelligence.screenDebug.capturing': 'Cattura in corso',
|
||||
'intelligence.screenDebug.frames': 'Frame',
|
||||
'intelligence.screenDebug.idle': 'Inattivo',
|
||||
'intelligence.screenDebug.lastApp': 'Ultima app',
|
||||
'intelligence.screenDebug.mode': 'Modalità',
|
||||
'intelligence.screenDebug.permAccessibility': 'Permesso accessibilità',
|
||||
'intelligence.screenDebug.permInput': 'Permesso input',
|
||||
'intelligence.screenDebug.permScreen': 'Accessibilità',
|
||||
'intelligence.screenDebug.permissions': 'Permessi',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Piattaforma non supportata',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Riassunti vision recenti',
|
||||
'intelligence.screenDebug.session': 'Sessione',
|
||||
'intelligence.screenDebug.size': 'Dimensione',
|
||||
'intelligence.screenDebug.status': 'Stato',
|
||||
'intelligence.screenDebug.testCapture': 'Test cattura',
|
||||
'intelligence.screenDebug.time': 'Ora',
|
||||
'intelligence.screenDebug.title': 'Titolo',
|
||||
'intelligence.screenDebug.unknown': 'Sconosciuto',
|
||||
'intelligence.screenDebug.visionQueue': 'Coda vision',
|
||||
'intelligence.screenDebug.visionState': 'Stato vision',
|
||||
'intelligence.tasks.activeBoardOne': '1 board attiva tra le conversazioni',
|
||||
'intelligence.tasks.activeBoardOther': '{count} board attive tra le conversazioni',
|
||||
'intelligence.tasks.empty': "Nessuna board di attività dell'agente",
|
||||
@@ -4547,7 +4463,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Input vocale attivo',
|
||||
'overlay.companion.error': 'Errore',
|
||||
'overlay.companion.listening': 'In ascolto…',
|
||||
'overlay.companion.pointing': 'Sta puntando…',
|
||||
'overlay.companion.speaking': 'Sta parlando…',
|
||||
'overlay.companion.thinking': 'Sta pensando…',
|
||||
'overlay.orbTitle': 'Trascina per spostare · Doppio clic per ripristinare la posizione',
|
||||
@@ -4579,13 +4494,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Descrizione canali di messaggistica',
|
||||
'pages.settings.features.notifications': 'Notifiche',
|
||||
'pages.settings.features.notificationsDesc': 'Descrizione notifiche',
|
||||
'pages.settings.features.screenAwareness': 'Consapevolezza schermo',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Lascia che il tuo agente veda il tuo schermo per offrire un aiuto tempestivo e contestuale.',
|
||||
'pages.settings.features.tools': 'Strumenti',
|
||||
'pages.settings.features.toolsDesc': 'Descrizione strumenti',
|
||||
'pages.settings.featuresSection.description':
|
||||
'Consapevolezza schermo, messaggistica e strumenti.',
|
||||
'pages.settings.featuresSection.title': 'Funzionalità',
|
||||
'privacy.dataKind.credentials': 'Credenziali',
|
||||
'privacy.dataKind.derived': 'Derivati',
|
||||
@@ -5348,9 +5258,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Configurazione IA',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Provider cloud, modelli Ollama locali e routing per carico di lavoro',
|
||||
'settings.developerMenu.screenAwareness.title': 'Consapevolezza schermo',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Permessi di cattura schermo, criteri di monitoraggio e controlli di sessione',
|
||||
'settings.developerMenu.messagingChannels.title': 'Canali di messaggistica',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Configura le modalità di autenticazione Telegram/Discord e il routing predefinito del canale',
|
||||
@@ -6012,20 +5919,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Applica sostituzione',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Accessibilità',
|
||||
'settings.screenIntel.permissions.grantHint': 'Suggerimento concessione',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Monitoraggio input',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS applica privacy',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Richiesta…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Aggiornamento…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Aggiornamento…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Richiesta…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Richiesta…',
|
||||
'settings.screenIntel.permissions.requesting': 'Richiesta…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Riavvio core…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Riavvio core…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Registrazione schermo',
|
||||
'settings.screenIntel.permissions.title': 'Permessi',
|
||||
'skills.card.moreActions': 'Altre azioni',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6288,37 +6181,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Modelli',
|
||||
'skills.resource.tree.empty': 'Nessuna risorsa inclusa.',
|
||||
'skills.search.placeholder': 'Segnaposto',
|
||||
'skills.setup.screenIntel.activeDesc': 'Descrizione attivo',
|
||||
'skills.setup.screenIntel.activeTitle': 'Screen Intelligence abilitato',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Impostazioni avanzate',
|
||||
'skills.setup.screenIntel.allGranted': 'Tutti i permessi concessi',
|
||||
'skills.setup.screenIntel.captureMode': 'Modalità di cattura',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Tutte le finestre (configurabile in seguito)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Dopo aver concesso i permessi in Impostazioni di sistema, clicca sotto per riavviare e raccogliere le modifiche.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Abilitazione...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
's sul tuo schermo e fornisce contesto utile al tuo agente',
|
||||
'skills.setup.screenIntel.enableError': 'Abilitazione Screen Intelligence fallita',
|
||||
'skills.setup.screenIntel.enabling': 'Abilitazione...',
|
||||
'skills.setup.screenIntel.grant': 'Apertura...',
|
||||
'skills.setup.screenIntel.granted': 'Concesso',
|
||||
'skills.setup.screenIntel.macosOnly': 'Solo macOS',
|
||||
'skills.setup.screenIntel.opening': 'Apertura...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Hotkey panico',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Accessibilità',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Monitoraggio input',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Registrazione schermo',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Descrizione permessi',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS applica la privacy a:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Aggiorna stato',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Riavvio...',
|
||||
'skills.setup.screenIntel.restarting': 'Riavvio...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Abilita la skill',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Concedi permessi',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Pronto',
|
||||
'skills.setup.screenIntel.title': 'Intelligenza dello schermo',
|
||||
'skills.setup.screenIntel.visionModel': 'Modello vision',
|
||||
'skills.setup.voice.activation': 'Attivazione',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6478,9 +6340,6 @@ const messages: TranslationMap = {
|
||||
'per le istruzioni di configurazione, poi riprova quando il runtime è raggiungibile.',
|
||||
'devOptions.menuAi': 'Configurazione AI',
|
||||
'devOptions.menuAiDesc': 'Provider cloud, modelli Ollama locali e routing per carico di lavoro',
|
||||
'devOptions.menuScreenAware': 'Riconoscimento schermo',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
"Autorizzazioni per l'acquisizione dello schermo, policy di monitoraggio e controlli della sessione",
|
||||
'devOptions.menuMessaging': 'Canali di messaggistica',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Configura le modalità di autenticazione Telegram/Discord e il routing del canale predefinito',
|
||||
|
||||
@@ -8,8 +8,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'시스템 트레이 단축키가 있는 항상 활성화된 동반자 모드',
|
||||
'pages.settings.features.desktopCompanion': '데스크탑 동반자',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'화면 인식 기능이 있는 음성 도우미: 듣고, 보고, 말하고, 가리킵니다.',
|
||||
'settings.companion.title': '데스크톱 컴패니언',
|
||||
'settings.companion.session': '세션',
|
||||
'settings.companion.activeLabel': '활성',
|
||||
@@ -25,8 +23,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': '단축키',
|
||||
'settings.companion.activationMode': '활성화 모드',
|
||||
'settings.companion.sessionTtl': '세션 TTL',
|
||||
'settings.companion.screenCapture': '화면 캡처',
|
||||
'settings.companion.appContext': '앱 컨텍스트',
|
||||
// Share cards (#5006).
|
||||
'share.button': '공유',
|
||||
'share.modalTitle': '이 순간을 공유하세요',
|
||||
@@ -111,8 +107,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': '메모리 검색에 사용할 임베딩 모델을 선택하세요.',
|
||||
'connections.header.search': '에이전트를 위한 웹 검색을 구성하세요.',
|
||||
'connections.header.wallet': '로컬 지갑 잔액과 복구 문구입니다.',
|
||||
'connections.header.screen':
|
||||
'에이전트가 화면을 볼 수 있게 하여 시의적절하고 맥락에 맞는 도움을 제공하세요.',
|
||||
'connections.header.composioKey':
|
||||
'Composio API 키를 추가하여 수백 개의 앱 통합을 잠금 해제하세요.',
|
||||
'connections.llm.apiKeys': 'API 키 및 라우팅',
|
||||
@@ -754,8 +748,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': '잠재의식',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'어시스턴트가 백그라운드에서 얼마나 활발히 작동하는지 제어',
|
||||
'settings.assistant.screenAwareness': '화면 인식',
|
||||
'settings.assistant.screenAwarenessDesc': '어시스턴트가 활성 창을 볼 수 있도록 허용',
|
||||
'settings.assistant.permissions': '권한',
|
||||
'settings.assistant.permissionsDesc':
|
||||
'어시스턴트가 무엇을 할 수 있고 어디서 작업할 수 있는지 선택하세요',
|
||||
@@ -835,7 +827,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': '기본 설정',
|
||||
'settings.notifications.tabs.routing': '라우팅',
|
||||
'settings.features': '기능',
|
||||
'settings.featuresDesc': '화면 인식, 메시징 및 도구',
|
||||
'settings.aiModels': 'AI 및 모델',
|
||||
'settings.aiModelsDesc': '로컬 AI 모델 설정, 다운로드 및 LLM 제공업체',
|
||||
'settings.ai': 'AI 구성',
|
||||
@@ -926,8 +917,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': '정해진 시간 동안 모든 알림을 일시 중지합니다',
|
||||
'settings.notifications.channelControls': '채널별 설정',
|
||||
'settings.notifications.channelControlsDesc': '각 채널의 알림 기본 설정을 구성합니다',
|
||||
'settings.features.screenAwareness': '화면 인식',
|
||||
'settings.features.screenAwarenessDesc': '어시스턴트가 현재 활성 창을 볼 수 있게 합니다',
|
||||
'settings.features.messaging': '메시징',
|
||||
'settings.features.messagingDesc': '채널 및 메시징 통합 설정',
|
||||
'settings.features.tools': '도구',
|
||||
@@ -2184,21 +2173,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': '변경 사항 저장',
|
||||
'settings.tools.preferencesSaved': '기본 설정이 저장되었습니다',
|
||||
'settings.tools.saveFailed': '기본 설정 저장에 실패했습니다. 다시 시도하세요.',
|
||||
'settings.screenAwareness.mode': '모드',
|
||||
'settings.screenAwareness.allExceptBlacklist': '블랙리스트 제외 모두',
|
||||
'settings.screenAwareness.whitelistOnly': '화이트리스트만',
|
||||
'settings.screenAwareness.screenMonitoring': '화면 모니터링',
|
||||
'settings.screenAwareness.saveSettings': '설정 저장',
|
||||
'settings.screenAwareness.session': '세션',
|
||||
'settings.screenAwareness.status': '상태',
|
||||
'settings.screenAwareness.active': '활성',
|
||||
'settings.screenAwareness.stopped': '중지됨',
|
||||
'settings.screenAwareness.remaining': '남은 시간',
|
||||
'settings.screenAwareness.startSession': '세션 시작',
|
||||
'settings.screenAwareness.stopSession': '세션 중지',
|
||||
'settings.screenAwareness.analyzeNow': '지금 분석',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'화면 인식 데스크톱 캡처 및 권한 제어는 현재 macOS에서만 지원됩니다.',
|
||||
'connections.comingSoon': '곧 제공 예정',
|
||||
'connections.setUp': '설정',
|
||||
'connections.configured': '구성됨',
|
||||
@@ -2551,36 +2525,6 @@ const messages: TranslationMap = {
|
||||
'마스터 스위치입니다. 기본값은 꺼짐이며 Ollama는 유휴 상태로 유지됩니다. 켜면 트리 요약기, 화면 인텔리전스, 자동 완성이 항상 로컬 모델을 사용합니다.',
|
||||
'localModel.advancedSettings': '고급 설정',
|
||||
'localModel.debugTitle': '로컬 모델 디버그',
|
||||
'screenAwareness.debugTitle': '화면 인식 디버그',
|
||||
'screenAwareness.debug.debugAndDiagnostics': '디버그 및 진단',
|
||||
'screenAwareness.debug.collapse': '축소',
|
||||
'screenAwareness.debug.expand': '확장',
|
||||
'screenAwareness.debug.failedToSave': '화면 인텔리전스 저장 실패',
|
||||
'screenAwareness.debug.policyTitle': '화면 인텔리전스 정책',
|
||||
'screenAwareness.debug.baselineFps': '기준 FPS',
|
||||
'screenAwareness.debug.useVisionModel': '비전 모델 사용',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'더 풍부한 컨텍스트를 위해 비전 LLM에 스크린샷을 전송합니다. 꺼져 있으면 텍스트 LLM에서 OCR 텍스트만 사용하므로 더 빠르고 비전 모델이 필요 없습니다.',
|
||||
'screenAwareness.debug.keepScreenshots': '스크린샷 유지',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'처리 후 삭제하지 않고 캡처한 스크린샷을 워크스페이스에 저장합니다',
|
||||
'screenAwareness.debug.allowlist': '허용 목록(한 줄에 하나의 규칙)',
|
||||
'screenAwareness.debug.denylist': '차단 목록(한 줄에 하나의 규칙)',
|
||||
'screenAwareness.debug.saveSettings': '화면 인텔리전스 설정 저장',
|
||||
'screenAwareness.debug.sessionStats': '세션 통계',
|
||||
'screenAwareness.debug.framesEphemeral': '프레임(임시)',
|
||||
'screenAwareness.debug.panicStop': '패닉 중지',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': '비전',
|
||||
'screenAwareness.debug.idle': '유휴',
|
||||
'screenAwareness.debug.visionQueue': '비전 대기열',
|
||||
'screenAwareness.debug.lastVision': '마지막 비전',
|
||||
'screenAwareness.debug.notAvailable': '해당 없음',
|
||||
'screenAwareness.debug.visionSummaries': '비전 요약',
|
||||
'screenAwareness.debug.refreshing': '새로 고침…',
|
||||
'screenAwareness.debug.noSummaries': '아직 요약이 없습니다.',
|
||||
'screenAwareness.debug.unknownApp': '알 수 없는 앱',
|
||||
'screenAwareness.debug.macosOnly': 'Screen Intelligence V1은 현재 macOS에서만 지원됩니다.',
|
||||
'memory.debugTitle': '메모리 디버그',
|
||||
'memory.documents': '문서',
|
||||
'memory.filterByNamespace': '네임스페이스로 필터링...',
|
||||
@@ -3827,33 +3771,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': '최신 생성된 OpenHuman 아키텍처 다이어그램',
|
||||
'intelligence.diagram.refreshesEvery': '{seconds}초마다 새로 고침',
|
||||
'intelligence.memoryText.entityTypePrefix': '엔터티 유형',
|
||||
'intelligence.screenDebug.active': '활성',
|
||||
'intelligence.screenDebug.app': '앱',
|
||||
'intelligence.screenDebug.bounds': '경계',
|
||||
'intelligence.screenDebug.captureAlt': '캡처 테스트 결과',
|
||||
'intelligence.screenDebug.captureFailed': '실패',
|
||||
'intelligence.screenDebug.captureSuccess': '성공',
|
||||
'intelligence.screenDebug.captureTest': '캡처 테스트',
|
||||
'intelligence.screenDebug.capturing': '캡처 중',
|
||||
'intelligence.screenDebug.frames': '프레임',
|
||||
'intelligence.screenDebug.idle': '유휴',
|
||||
'intelligence.screenDebug.lastApp': '마지막 앱',
|
||||
'intelligence.screenDebug.mode': '모드',
|
||||
'intelligence.screenDebug.permAccessibility': '접근성 권한',
|
||||
'intelligence.screenDebug.permInput': '입력 권한',
|
||||
'intelligence.screenDebug.permScreen': '접근성',
|
||||
'intelligence.screenDebug.permissions': '권한',
|
||||
'intelligence.screenDebug.platformNotSupported': '플랫폼이 지원되지 않음',
|
||||
'intelligence.screenDebug.recentVisionSummaries': '최근 비전 요약',
|
||||
'intelligence.screenDebug.session': '세션',
|
||||
'intelligence.screenDebug.size': '크기',
|
||||
'intelligence.screenDebug.status': '상태',
|
||||
'intelligence.screenDebug.testCapture': '테스트 캡처',
|
||||
'intelligence.screenDebug.time': '시간',
|
||||
'intelligence.screenDebug.title': '제목',
|
||||
'intelligence.screenDebug.unknown': '알 수 없음',
|
||||
'intelligence.screenDebug.visionQueue': '비전 대기열',
|
||||
'intelligence.screenDebug.visionState': '비전 상태',
|
||||
'intelligence.tasks.activeBoardOne': '대화 전반에 활성 보드 1개',
|
||||
'intelligence.tasks.activeBoardOther': '대화 전반에 활성 보드 {count}개',
|
||||
'intelligence.tasks.empty': '아직 에이전트 작업 보드가 없습니다',
|
||||
@@ -4422,7 +4339,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': '음성 입력 활성',
|
||||
'overlay.companion.error': '오류',
|
||||
'overlay.companion.listening': '듣는 중…',
|
||||
'overlay.companion.pointing': '가리키는 중…',
|
||||
'overlay.companion.speaking': '말하는 중…',
|
||||
'overlay.companion.thinking': '생각 중...',
|
||||
'overlay.orbTitle': '드래그하여 이동 · 두 번 클릭하여 위치 초기화',
|
||||
@@ -4452,12 +4368,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': '메시징 채널 설명',
|
||||
'pages.settings.features.notifications': '알림',
|
||||
'pages.settings.features.notificationsDesc': '알림 설명',
|
||||
'pages.settings.features.screenAwareness': '화면 인식',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'에이전트가 화면을 볼 수 있게 하여 시의적절하고 맥락에 맞는 도움을 제공하세요.',
|
||||
'pages.settings.features.tools': '도구',
|
||||
'pages.settings.features.toolsDesc': '도구 설명',
|
||||
'pages.settings.featuresSection.description': '화면 인식, 메시징 및 도구.',
|
||||
'pages.settings.featuresSection.title': '기능',
|
||||
'privacy.dataKind.credentials': '자격 증명',
|
||||
'privacy.dataKind.derived': '파생됨',
|
||||
@@ -5206,8 +5118,6 @@ const messages: TranslationMap = {
|
||||
'settings.localModel.status.working': '작업 중...',
|
||||
'settings.developerMenu.ai.title': 'AI 구성',
|
||||
'settings.developerMenu.ai.desc': '클라우드 공급자, 로컬 Ollama 모델 및 워크로드별 라우팅',
|
||||
'settings.developerMenu.screenAwareness.title': '화면 인식',
|
||||
'settings.developerMenu.screenAwareness.desc': '화면 캡처 권한, 모니터링 정책 및 세션 제어',
|
||||
'settings.developerMenu.messagingChannels.title': '메시징 채널',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Telegram/Discord 인증 모드 및 기본 채널 라우팅 구성',
|
||||
@@ -5840,20 +5750,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': '교체 적용',
|
||||
'settings.modelHealth.tag.cheaper': '더 저렴함',
|
||||
'settings.modelHealth.tag.better': '더 좋음',
|
||||
'settings.screenIntel.permissions.accessibility': '접근성',
|
||||
'settings.screenIntel.permissions.grantHint': '권한 허용 안내',
|
||||
'settings.screenIntel.permissions.inputMonitoring': '입력 모니터링',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS 개인정보 보호 적용',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': '요청 중…',
|
||||
'settings.screenIntel.permissions.refreshStatus': '새로고침 중…',
|
||||
'settings.screenIntel.permissions.refreshing': '새로고침 중…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': '요청 중…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': '요청 중…',
|
||||
'settings.screenIntel.permissions.requesting': '요청 중…',
|
||||
'settings.screenIntel.permissions.restartRefresh': '코어 다시 시작 중…',
|
||||
'settings.screenIntel.permissions.restartingCore': '코어 다시 시작 중…',
|
||||
'settings.screenIntel.permissions.screenRecording': '화면 녹화',
|
||||
'settings.screenIntel.permissions.title': '권한',
|
||||
'skills.card.moreActions': '추가 작업',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6112,37 +6008,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': '템플릿',
|
||||
'skills.resource.tree.empty': '번들 리소스가 없습니다.',
|
||||
'skills.search.placeholder': '플레이스홀더',
|
||||
'skills.setup.screenIntel.activeDesc': '활성 설명',
|
||||
'skills.setup.screenIntel.activeTitle': '화면 인텔리전스가 활성화됨',
|
||||
'skills.setup.screenIntel.advancedSettings': '고급 설정',
|
||||
'skills.setup.screenIntel.allGranted': '모든 권한 허용됨',
|
||||
'skills.setup.screenIntel.captureMode': '캡처 모드',
|
||||
'skills.setup.screenIntel.captureModeValue': '모든 창(나중에 구성 가능)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'시스템 설정에서 권한을 허용한 후 아래를 클릭하여 다시 시작하고 변경 사항을 적용하세요.',
|
||||
'skills.setup.screenIntel.enableBtn': '활성화 중...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
'화면의 내용을 읽고 유용한 컨텍스트를 에이전트에 제공합니다',
|
||||
'skills.setup.screenIntel.enableError': '화면 인텔리전스를 활성화하지 못했습니다',
|
||||
'skills.setup.screenIntel.enabling': '활성화 중...',
|
||||
'skills.setup.screenIntel.grant': '여는 중...',
|
||||
'skills.setup.screenIntel.granted': '허용됨',
|
||||
'skills.setup.screenIntel.macosOnly': 'macOS 전용',
|
||||
'skills.setup.screenIntel.opening': '여는 중...',
|
||||
'skills.setup.screenIntel.panicHotkey': '패닉 핫키',
|
||||
'skills.setup.screenIntel.permAccessibility': '접근성',
|
||||
'skills.setup.screenIntel.permInputMonitoring': '입력 모니터링',
|
||||
'skills.setup.screenIntel.permScreenRecording': '화면 녹화',
|
||||
'skills.setup.screenIntel.permissionsDesc': '권한 설명',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS는 다음에 개인 정보 보호를 적용합니다.',
|
||||
'skills.setup.screenIntel.refreshStatus': '상태 새로고침',
|
||||
'skills.setup.screenIntel.restartRefresh': '다시 시작 중...',
|
||||
'skills.setup.screenIntel.restarting': '다시 시작 중...',
|
||||
'skills.setup.screenIntel.stepEnable': '스킬 활성화',
|
||||
'skills.setup.screenIntel.stepPermissions': '권한 허용',
|
||||
'skills.setup.screenIntel.stepSuccess': '사용 준비 완료',
|
||||
'skills.setup.screenIntel.title': '화면 인텔리전스',
|
||||
'skills.setup.screenIntel.visionModel': '비전 모델',
|
||||
'skills.setup.voice.activation': '활성화',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6301,8 +6166,6 @@ const messages: TranslationMap = {
|
||||
'설정 지침을 확인한 다음 런타임에 연결할 수 있게 되면 다시 시도하세요.',
|
||||
'devOptions.menuAi': 'AI 구성',
|
||||
'devOptions.menuAiDesc': '클라우드 공급자, 로컬 Ollama 모델 및 워크로드별 라우팅',
|
||||
'devOptions.menuScreenAware': '화면 인식',
|
||||
'devOptions.menuScreenAwareDesc': '화면 캡처 권한, 모니터링 정책 및 세션 제어',
|
||||
'devOptions.menuMessaging': '메시징 채널',
|
||||
'devOptions.menuMessagingDesc': 'Telegram/Discord 인증 모드 및 기본 채널 라우팅 구성',
|
||||
'devOptions.menuTools': '도구',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Tryb towarzyski zawsze aktywny ze skrótem w zasobniku systemowym',
|
||||
'pages.settings.features.desktopCompanion': 'Towarzysz pulpitu',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Asystent głosowy ze świadomością ekranu: słucha, widzi, mówi, wskazuje',
|
||||
'settings.companion.title': 'Towarzysz komputerowy',
|
||||
'settings.companion.session': 'Sesja',
|
||||
'settings.companion.activeLabel': 'Aktywna',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Skrót klawiszowy',
|
||||
'settings.companion.activationMode': 'Tryb aktywacji',
|
||||
'settings.companion.sessionTtl': 'TTL sesji',
|
||||
'settings.companion.screenCapture': 'Przechwytywanie ekranu',
|
||||
'settings.companion.appContext': 'Kontekst aplikacji',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Udostępnij',
|
||||
'share.modalTitle': 'Udostępnij tę chwilę',
|
||||
@@ -115,8 +111,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'Wybierz model osadzeń używany do wyszukiwania w pamięci.',
|
||||
'connections.header.search': 'Skonfiguruj wyszukiwanie internetowe dla swojego agenta.',
|
||||
'connections.header.wallet': 'Saldo Twojego lokalnego portfela i fraza odzyskiwania.',
|
||||
'connections.header.screen':
|
||||
'Pozwól agentowi widzieć Twój ekran, aby oferować pomoc w odpowiednim czasie i kontekście.',
|
||||
'connections.header.composioKey':
|
||||
'Dodaj klucz API Composio, aby odblokować setki integracji aplikacji.',
|
||||
'connections.llm.apiKeys': 'Klucze API i routing',
|
||||
@@ -773,8 +767,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.faceMascot': 'Twarz / Maskotka',
|
||||
'settings.assistant.backgroundActivity': 'Podświadomość',
|
||||
'settings.assistant.backgroundActivityDesc': 'Kontroluj, jak aktywnie asystent pracuje w tle',
|
||||
'settings.assistant.screenAwareness': 'Świadomość ekranu',
|
||||
'settings.assistant.screenAwarenessDesc': 'Pozwól asystentowi widzieć aktywne okno',
|
||||
'settings.assistant.permissions': 'Uprawnienia',
|
||||
'settings.assistant.permissionsDesc': 'Wybierz, co może robić asystent i gdzie może pracować',
|
||||
'settings.privacySecurity.privacy': 'Prywatność',
|
||||
@@ -856,7 +848,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferencje',
|
||||
'settings.notifications.tabs.routing': 'Reguły routingu',
|
||||
'settings.features': 'Funkcje',
|
||||
'settings.featuresDesc': 'Świadomość ekranu, wiadomości i narzędzia',
|
||||
'settings.aiModels': 'AI i modele',
|
||||
'settings.aiModelsDesc': 'Konfiguracja lokalnych modeli AI, pobierania i dostawcy LLM',
|
||||
'settings.ai': 'Konfiguracja AI',
|
||||
@@ -952,8 +943,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Sterowanie per kanał',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Skonfiguruj preferencje powiadomień dla każdego kanału',
|
||||
'settings.features.screenAwareness': 'Świadomość ekranu',
|
||||
'settings.features.screenAwarenessDesc': 'Pozwól asystentowi widzieć Twoje aktywne okno',
|
||||
'settings.features.messaging': 'Wiadomości',
|
||||
'settings.features.messagingDesc': 'Ustawienia kanałów i integracji wiadomości',
|
||||
'settings.features.tools': 'Narzędzia',
|
||||
@@ -2239,21 +2228,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Zapisz zmiany',
|
||||
'settings.tools.preferencesSaved': 'Preferencje zapisane',
|
||||
'settings.tools.saveFailed': 'Nie udało się zapisać preferencji. Spróbuj ponownie.',
|
||||
'settings.screenAwareness.mode': 'Tryb',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Wszystko oprócz czarnej listy',
|
||||
'settings.screenAwareness.whitelistOnly': 'Tylko biała lista',
|
||||
'settings.screenAwareness.screenMonitoring': 'Monitorowanie ekranu',
|
||||
'settings.screenAwareness.saveSettings': 'Zapisz ustawienia',
|
||||
'settings.screenAwareness.session': 'Sesja',
|
||||
'settings.screenAwareness.status': 'Stan',
|
||||
'settings.screenAwareness.active': 'Aktywna',
|
||||
'settings.screenAwareness.stopped': 'Zatrzymana',
|
||||
'settings.screenAwareness.remaining': 'Pozostało',
|
||||
'settings.screenAwareness.startSession': 'Rozpocznij sesję',
|
||||
'settings.screenAwareness.stopSession': 'Zatrzymaj sesję',
|
||||
'settings.screenAwareness.analyzeNow': 'Analizuj teraz',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'Przechwytywanie ekranu i zarządzanie uprawnieniami są obecnie obsługiwane tylko na macOS.',
|
||||
'connections.comingSoon': 'Wkrótce',
|
||||
'connections.setUp': 'Skonfiguruj',
|
||||
'connections.configured': 'Skonfigurowano',
|
||||
@@ -2614,37 +2588,6 @@ const messages: TranslationMap = {
|
||||
'Główny przełącznik. Domyślnie wyłączony: Ollama pozostaje bezczynna. Gdy włączony, podsumowywanie drzewa, inteligencja ekranu i autouzupełnianie zawsze korzystają z modelu lokalnego.',
|
||||
'localModel.advancedSettings': 'Ustawienia zaawansowane',
|
||||
'localModel.debugTitle': 'Debug modelu lokalnego',
|
||||
'screenAwareness.debugTitle': 'Debug świadomości ekranu',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Debug i diagnostyka',
|
||||
'screenAwareness.debug.collapse': 'Zwiń',
|
||||
'screenAwareness.debug.expand': 'Rozwiń',
|
||||
'screenAwareness.debug.failedToSave': 'Nie udało się zapisać świadomości ekranu',
|
||||
'screenAwareness.debug.policyTitle': 'Polityka świadomości ekranu',
|
||||
'screenAwareness.debug.baselineFps': 'Bazowe FPS',
|
||||
'screenAwareness.debug.useVisionModel': 'Użyj modelu wizji',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Wysyłaj zrzuty ekranu do LLM wizyjnego po bogatszy kontekst. Gdy wyłączone, używany jest tylko tekst OCR z LLM tekstowym: szybciej i bez modelu wizji.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Zachowuj zrzuty ekranu',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Zapisuj zrzuty ekranu w przestrzeni roboczej zamiast usuwać po przetworzeniu',
|
||||
'screenAwareness.debug.allowlist': 'Lista dozwolonych (jedna reguła na linię)',
|
||||
'screenAwareness.debug.denylist': 'Lista blokowanych (jedna reguła na linię)',
|
||||
'screenAwareness.debug.saveSettings': 'Zapisz ustawienia świadomości ekranu',
|
||||
'screenAwareness.debug.sessionStats': 'Statystyki sesji',
|
||||
'screenAwareness.debug.framesEphemeral': 'Klatki (ulotne)',
|
||||
'screenAwareness.debug.panicStop': 'Awaryjne zatrzymanie',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Wizja',
|
||||
'screenAwareness.debug.idle': 'bezczynne',
|
||||
'screenAwareness.debug.visionQueue': 'Kolejka wizji',
|
||||
'screenAwareness.debug.lastVision': 'Ostatnia wizja',
|
||||
'screenAwareness.debug.notAvailable': 'n/d',
|
||||
'screenAwareness.debug.visionSummaries': 'Podsumowania wizji',
|
||||
'screenAwareness.debug.refreshing': 'Odświeżanie…',
|
||||
'screenAwareness.debug.noSummaries': 'Brak podsumowań.',
|
||||
'screenAwareness.debug.unknownApp': 'Nieznana aplikacja',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Świadomość ekranu V1 jest obecnie obsługiwana tylko na macOS.',
|
||||
'memory.debugTitle': 'Debug pamięci',
|
||||
'memory.documents': 'Dokumenty',
|
||||
'memory.filterByNamespace': 'Filtruj po przestrzeni nazw...',
|
||||
@@ -3911,33 +3854,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Najnowszy wygenerowany diagram architektury OpenHuman',
|
||||
'intelligence.diagram.refreshesEvery': 'Odświeża co {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Typ encji',
|
||||
'intelligence.screenDebug.active': 'Aktywne',
|
||||
'intelligence.screenDebug.app': 'Aplikacja',
|
||||
'intelligence.screenDebug.bounds': 'Granice',
|
||||
'intelligence.screenDebug.captureAlt': 'Wynik testu przechwytywania',
|
||||
'intelligence.screenDebug.captureFailed': 'Niepowodzenie',
|
||||
'intelligence.screenDebug.captureSuccess': 'Powodzenie',
|
||||
'intelligence.screenDebug.captureTest': 'Test przechwytywania',
|
||||
'intelligence.screenDebug.capturing': 'Przechwytywanie',
|
||||
'intelligence.screenDebug.frames': 'Klatki',
|
||||
'intelligence.screenDebug.idle': 'Bezczynne',
|
||||
'intelligence.screenDebug.lastApp': 'Ostatnia aplikacja',
|
||||
'intelligence.screenDebug.mode': 'Tryb',
|
||||
'intelligence.screenDebug.permAccessibility': 'Uprawnienie: dostępność',
|
||||
'intelligence.screenDebug.permInput': 'Uprawnienie: wejście',
|
||||
'intelligence.screenDebug.permScreen': 'Dostępność',
|
||||
'intelligence.screenDebug.permissions': 'Uprawnienia',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Platforma nieobsługiwana',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Ostatnie podsumowania wizji',
|
||||
'intelligence.screenDebug.session': 'Sesja',
|
||||
'intelligence.screenDebug.size': 'Rozmiar',
|
||||
'intelligence.screenDebug.status': 'Stan',
|
||||
'intelligence.screenDebug.testCapture': 'Testuj przechwytywanie',
|
||||
'intelligence.screenDebug.time': 'Czas',
|
||||
'intelligence.screenDebug.title': 'Tytuł',
|
||||
'intelligence.screenDebug.unknown': 'Nieznane',
|
||||
'intelligence.screenDebug.visionQueue': 'Kolejka wizji',
|
||||
'intelligence.screenDebug.visionState': 'Stan wizji',
|
||||
'intelligence.tasks.activeBoardOne': '1 aktywna tablica w rozmowach',
|
||||
'intelligence.tasks.activeBoardOther': '{count} aktywnych tablic w rozmowach',
|
||||
'intelligence.tasks.empty': 'Brak tablic zadań agenta',
|
||||
@@ -4534,7 +4450,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Wejście głosowe aktywne',
|
||||
'overlay.companion.error': 'Błąd',
|
||||
'overlay.companion.listening': 'Słucham…',
|
||||
'overlay.companion.pointing': 'Wskazuję…',
|
||||
'overlay.companion.speaking': 'Mówię…',
|
||||
'overlay.companion.thinking': 'Myślę…',
|
||||
'overlay.orbTitle': 'Przeciągnij, aby przesunąć · Kliknij dwukrotnie, aby zresetować pozycję',
|
||||
@@ -4570,12 +4485,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.notifications': 'Powiadomienia',
|
||||
'pages.settings.features.notificationsDesc':
|
||||
'Steruj alertami systemowymi i kategoriami powiadomień',
|
||||
'pages.settings.features.screenAwareness': 'Świadomość ekranu',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Pozwól agentowi widzieć Twój ekran, aby oferować pomoc w odpowiednim czasie i kontekście.',
|
||||
'pages.settings.features.tools': 'Narzędzia',
|
||||
'pages.settings.features.toolsDesc': 'Wybierz, z jakich możliwości OpenHuman może korzystać',
|
||||
'pages.settings.featuresSection.description': 'Świadomość ekranu, komunikacja i narzędzia.',
|
||||
'pages.settings.featuresSection.title': 'Funkcje',
|
||||
'privacy.dataKind.credentials': 'Poświadczenia',
|
||||
'privacy.dataKind.derived': 'Wyprowadzone',
|
||||
@@ -5344,9 +5255,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Konfiguracja AI',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Dostawcy chmurowi, lokalne modele Ollama i trasowanie per workload',
|
||||
'settings.developerMenu.screenAwareness.title': 'Świadomość ekranu',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Uprawnienia do przechwytywania ekranu, polityka monitorowania i kontrola sesji',
|
||||
'settings.developerMenu.messagingChannels.title': 'Kanały komunikacji',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Konfiguruj tryby uwierzytelniania Telegram/Discord i domyślne trasowanie',
|
||||
@@ -5998,20 +5906,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Zastosuj zamianę',
|
||||
'settings.modelHealth.tag.cheaper': 'TAŃSZY',
|
||||
'settings.modelHealth.tag.better': 'LEPSZY',
|
||||
'settings.screenIntel.permissions.accessibility': 'Dostępność',
|
||||
'settings.screenIntel.permissions.grantHint': 'System otworzy okno żądania uprawnień.',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Monitorowanie wejścia',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS stosuje politykę prywatności do:',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Żądanie…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Odświeżanie…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Odświeżanie…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Żądanie…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Żądanie…',
|
||||
'settings.screenIntel.permissions.requesting': 'Żądanie…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Restartowanie rdzenia…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Restartowanie rdzenia…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Nagrywanie ekranu',
|
||||
'settings.screenIntel.permissions.title': 'Uprawnienia',
|
||||
'skills.card.moreActions': 'Więcej akcji',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6274,37 +6168,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Szablony',
|
||||
'skills.resource.tree.empty': 'Brak dołączonych zasobów.',
|
||||
'skills.search.placeholder': 'Szukaj umiejętności...',
|
||||
'skills.setup.screenIntel.activeDesc': 'Inteligencja ekranu działa. Wybrane okna są obserwowane.',
|
||||
'skills.setup.screenIntel.activeTitle': 'Inteligencja ekranu jest włączona',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Ustawienia zaawansowane',
|
||||
'skills.setup.screenIntel.allGranted': 'Wszystkie uprawnienia przyznane',
|
||||
'skills.setup.screenIntel.captureMode': 'Tryb przechwytywania',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Wszystkie okna (konfigurowalne później)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Po przyznaniu uprawnień w Ustawieniach systemowych kliknij poniżej, aby zrestartować i odświeżyć zmiany.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Włączanie...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
's na Twoim ekranie i dostarczają użytecznego kontekstu agentowi',
|
||||
'skills.setup.screenIntel.enableError': 'Nie udało się włączyć inteligencji ekranu',
|
||||
'skills.setup.screenIntel.enabling': 'Włączanie...',
|
||||
'skills.setup.screenIntel.grant': 'Otwieranie...',
|
||||
'skills.setup.screenIntel.granted': 'Przyznano',
|
||||
'skills.setup.screenIntel.macosOnly': 'Tylko macOS',
|
||||
'skills.setup.screenIntel.opening': 'Otwieranie...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Skrót awaryjny',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Dostępność',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Monitorowanie wejścia',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Nagrywanie ekranu',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Inteligencja ekranu wymaga uprawnień systemowych:',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS stosuje politykę prywatności do:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Odśwież status',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Restartowanie...',
|
||||
'skills.setup.screenIntel.restarting': 'Restartowanie...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Włącz umiejętność',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Przyznaj uprawnienia',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Gotowe',
|
||||
'skills.setup.screenIntel.title': 'Inteligencja ekranu',
|
||||
'skills.setup.screenIntel.visionModel': 'Model wizji',
|
||||
'skills.setup.voice.activation': 'Aktywacja',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6465,9 +6328,6 @@ const messages: TranslationMap = {
|
||||
'po instrukcje konfiguracji, a następnie spróbuj ponownie, gdy środowisko będzie osiągalne.',
|
||||
'devOptions.menuAi': 'Konfiguracja AI',
|
||||
'devOptions.menuAiDesc': 'Dostawcy chmurowi, lokalne modele Ollama i trasowanie per workload',
|
||||
'devOptions.menuScreenAware': 'Świadomość ekranu',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Uprawnienia do przechwytywania ekranu, polityka monitorowania i kontrola sesji',
|
||||
'devOptions.menuMessaging': 'Kanały komunikacji',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Konfiguruj tryby uwierzytelniania Telegram/Discord i domyślne trasowanie',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Modo companheiro sempre ativo com atalho na bandeja do sistema',
|
||||
'pages.settings.features.desktopCompanion': 'Companion Desktop',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Assistente de voz com consciência da tela: escuta, vê, fala, aponta',
|
||||
'settings.companion.title': 'Companheiro de Área de Trabalho',
|
||||
'settings.companion.session': 'Sessão',
|
||||
'settings.companion.activeLabel': 'Ativo',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Tecla de atalho',
|
||||
'settings.companion.activationMode': 'Modo de ativação',
|
||||
'settings.companion.sessionTtl': 'Sessão TTL',
|
||||
'settings.companion.screenCapture': 'Captura de tela',
|
||||
'settings.companion.appContext': 'Contexto do aplicativo',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Partilhar',
|
||||
'share.modalTitle': 'Partilhe este momento',
|
||||
@@ -114,8 +110,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'Escolha o modelo de embeddings usado na pesquisa da memória.',
|
||||
'connections.header.search': 'Configure a pesquisa na web para o seu agente.',
|
||||
'connections.header.wallet': 'O saldo da sua carteira local e a sua frase de recuperação.',
|
||||
'connections.header.screen':
|
||||
'Deixe o seu agente ver o seu ecrã para oferecer ajuda oportuna e contextual.',
|
||||
'connections.header.composioKey':
|
||||
'Adicione uma chave de API do Composio para desbloquear centenas de integrações de aplicações.',
|
||||
'connections.llm.apiKeys': 'Chaves de API e encaminhamento',
|
||||
@@ -769,8 +763,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Subconsciente',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Controle o quão ativamente seu assistente trabalha em segundo plano',
|
||||
'settings.assistant.screenAwareness': 'Consciência de tela',
|
||||
'settings.assistant.screenAwarenessDesc': 'Permitir que o assistente veja sua janela ativa',
|
||||
'settings.assistant.permissions': 'Permissões',
|
||||
'settings.assistant.permissionsDesc':
|
||||
'Escolha o que o assistente pode fazer e onde pode trabalhar',
|
||||
@@ -853,7 +845,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Preferências',
|
||||
'settings.notifications.tabs.routing': 'Roteamento',
|
||||
'settings.features': 'Recursos',
|
||||
'settings.featuresDesc': 'Reconhecimento de tela, mensagens e ferramentas',
|
||||
'settings.aiModels': 'IA e Modelos',
|
||||
'settings.aiModelsDesc': 'Configuração de modelos de IA local, downloads e provedor LLM',
|
||||
'settings.ai': 'Configuração de IA',
|
||||
@@ -951,8 +942,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.channelControls': 'Controles por Canal',
|
||||
'settings.notifications.channelControlsDesc':
|
||||
'Configurar preferências de notificação para cada canal',
|
||||
'settings.features.screenAwareness': 'Reconhecimento de Tela',
|
||||
'settings.features.screenAwarenessDesc': 'Deixar o assistente ver sua janela ativa',
|
||||
'settings.features.messaging': 'Mensagens',
|
||||
'settings.features.messagingDesc': 'Configurações de integração de canais e mensagens',
|
||||
'settings.features.tools': 'Ferramentas',
|
||||
@@ -2248,21 +2237,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Salvar Alterações',
|
||||
'settings.tools.preferencesSaved': 'Preferências salvas',
|
||||
'settings.tools.saveFailed': 'Falha ao salvar preferências. Tente novamente.',
|
||||
'settings.screenAwareness.mode': 'Modo',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Todos Exceto Lista Negra',
|
||||
'settings.screenAwareness.whitelistOnly': 'Somente Lista Branca',
|
||||
'settings.screenAwareness.screenMonitoring': 'Monitoramento de Tela',
|
||||
'settings.screenAwareness.saveSettings': 'Salvar Configurações',
|
||||
'settings.screenAwareness.session': 'Sessão',
|
||||
'settings.screenAwareness.status': 'Status',
|
||||
'settings.screenAwareness.active': 'Ativo',
|
||||
'settings.screenAwareness.stopped': 'Parado',
|
||||
'settings.screenAwareness.remaining': 'Restante',
|
||||
'settings.screenAwareness.startSession': 'Iniciar Sessão',
|
||||
'settings.screenAwareness.stopSession': 'Encerrar Sessão',
|
||||
'settings.screenAwareness.analyzeNow': 'Analisar Agora',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'A captura de tela e os controles de permissão do Reconhecimento de Tela são suportados atualmente apenas no macOS.',
|
||||
'connections.comingSoon': 'Em breve',
|
||||
'connections.setUp': 'Configurar',
|
||||
'connections.configured': 'Configurado',
|
||||
@@ -2626,37 +2600,6 @@ const messages: TranslationMap = {
|
||||
'Chave mestre. Desligado por padrão: Ollama fica inativo. Quando ligado, o sumarizador de árvore, inteligência de tela e autocompletar sempre usam o modelo local.',
|
||||
'localModel.advancedSettings': 'Configurações avançadas',
|
||||
'localModel.debugTitle': 'Depuração de Modelo Local',
|
||||
'screenAwareness.debugTitle': 'Depuração de Reconhecimento de Tela',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Depuração e diagnóstico',
|
||||
'screenAwareness.debug.collapse': 'Collapse',
|
||||
'screenAwareness.debug.expand': 'Expandir',
|
||||
'screenAwareness.debug.failedToSave': 'Falha ao salvar inteligência de tela',
|
||||
'screenAwareness.debug.policyTitle': 'Política de inteligência de tela',
|
||||
'screenAwareness.debug.baselineFps': 'FPS de linha de base',
|
||||
'screenAwareness.debug.useVisionModel': 'Usar modelo de visão',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Enviar capturas de tela para um LLM de visão para contexto mais rico. Quando desativado, apenas o texto de OCR é usado com um LLM de texto: mais rápido e sem necessidade de modelo de visão.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Manter capturas de tela',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Salvar capturas de tela no workspace em vez de excluí-las após o processamento',
|
||||
'screenAwareness.debug.allowlist': 'Lista de permissões (uma regra por linha)',
|
||||
'screenAwareness.debug.denylist': 'Lista de bloqueios (uma regra por linha)',
|
||||
'screenAwareness.debug.saveSettings': 'Salvar configurações de inteligência de tela',
|
||||
'screenAwareness.debug.sessionStats': 'Estatísticas de sessão',
|
||||
'screenAwareness.debug.framesEphemeral': 'Quadros (efêmeros)',
|
||||
'screenAwareness.debug.panicStop': 'Parada de pânico',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Visão',
|
||||
'screenAwareness.debug.idle': 'inativa',
|
||||
'screenAwareness.debug.visionQueue': 'Fila de visão',
|
||||
'screenAwareness.debug.lastVision': 'Última visão',
|
||||
'screenAwareness.debug.notAvailable': 'n/a',
|
||||
'screenAwareness.debug.visionSummaries': 'Resumos de visão',
|
||||
'screenAwareness.debug.refreshing': 'Atualizando…',
|
||||
'screenAwareness.debug.noSummaries': 'Ainda não há resumos.',
|
||||
'screenAwareness.debug.unknownApp': 'Aplicativo desconhecido',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'O Screen Intelligence V1 é suportado apenas no macOS no momento.',
|
||||
'memory.debugTitle': 'Depuração de Memória',
|
||||
'memory.documents': 'Documentos',
|
||||
'memory.filterByNamespace': 'Filtrar por namespace...',
|
||||
@@ -3921,33 +3864,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Último diagrama de arquitetura OpenHuman gerado',
|
||||
'intelligence.diagram.refreshesEvery': 'Atualiza a cada {seconds}s',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Tipo de entidade',
|
||||
'intelligence.screenDebug.active': 'Ativo',
|
||||
'intelligence.screenDebug.app': 'Aplicativo',
|
||||
'intelligence.screenDebug.bounds': 'Limites',
|
||||
'intelligence.screenDebug.captureAlt': 'Resultado do teste de captura',
|
||||
'intelligence.screenDebug.captureFailed': 'Falhou',
|
||||
'intelligence.screenDebug.captureSuccess': 'Sucesso',
|
||||
'intelligence.screenDebug.captureTest': 'Teste de captura',
|
||||
'intelligence.screenDebug.capturing': 'Capturando',
|
||||
'intelligence.screenDebug.frames': 'Quadros',
|
||||
'intelligence.screenDebug.idle': 'Inativo',
|
||||
'intelligence.screenDebug.lastApp': 'Último App',
|
||||
'intelligence.screenDebug.mode': 'Modo',
|
||||
'intelligence.screenDebug.permAccessibility': 'Permissão de acessibilidade',
|
||||
'intelligence.screenDebug.permInput': 'Permissão de entrada',
|
||||
'intelligence.screenDebug.permScreen': 'Acessibilidade',
|
||||
'intelligence.screenDebug.permissions': 'Permissões',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Plataforma não suportada',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Resumos de visão recentes',
|
||||
'intelligence.screenDebug.session': 'Sessão',
|
||||
'intelligence.screenDebug.size': 'Tamanho',
|
||||
'intelligence.screenDebug.status': 'Status',
|
||||
'intelligence.screenDebug.testCapture': 'Testar captura',
|
||||
'intelligence.screenDebug.time': 'Tempo',
|
||||
'intelligence.screenDebug.title': 'Título',
|
||||
'intelligence.screenDebug.unknown': 'Desconhecido',
|
||||
'intelligence.screenDebug.visionQueue': 'Fila de Visão',
|
||||
'intelligence.screenDebug.visionState': 'Estado de Visão',
|
||||
'intelligence.tasks.activeBoardOne': '1 quadro ativo entre conversas',
|
||||
'intelligence.tasks.activeBoardOther': '{count} quadros ativos entre conversas',
|
||||
'intelligence.tasks.empty': 'Nenhum quadro de tarefas de agente ainda',
|
||||
@@ -4537,7 +4453,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Entrada de voz ativa',
|
||||
'overlay.companion.error': 'Erro',
|
||||
'overlay.companion.listening': 'Ouvindo…',
|
||||
'overlay.companion.pointing': 'Apontando…',
|
||||
'overlay.companion.speaking': 'Falando…',
|
||||
'overlay.companion.thinking': 'Pensando…',
|
||||
'overlay.orbTitle': 'Arraste para mover · Clique duplo para redefinir posição',
|
||||
@@ -4569,12 +4484,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Descrição dos canais de mensagens',
|
||||
'pages.settings.features.notifications': 'Notificações',
|
||||
'pages.settings.features.notificationsDesc': 'Descrição de notificações',
|
||||
'pages.settings.features.screenAwareness': 'Reconhecimento de tela',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Deixe o seu agente ver o seu ecrã para oferecer ajuda oportuna e contextual.',
|
||||
'pages.settings.features.tools': 'Ferramentas',
|
||||
'pages.settings.features.toolsDesc': 'Descrição de ferramentas',
|
||||
'pages.settings.featuresSection.description': 'Reconhecimento de tela, mensagens e ferramentas.',
|
||||
'pages.settings.featuresSection.title': 'Recursos',
|
||||
'privacy.dataKind.credentials': 'Credenciais',
|
||||
'privacy.dataKind.derived': 'Derivado',
|
||||
@@ -5339,9 +5250,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Configuração de IA',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Provedores em nuvem, modelos Ollama locais e roteamento por carga de trabalho',
|
||||
'settings.developerMenu.screenAwareness.title': 'Consciência de tela',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Permissões de captura de tela, política de monitoramento e controles de sessão',
|
||||
'settings.developerMenu.messagingChannels.title': 'Canais de mensagens',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Configure modos de autenticação Telegram/Discord e o roteamento de canal padrão',
|
||||
@@ -6004,20 +5912,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Aplicar Substituição',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Acessibilidade',
|
||||
'settings.screenIntel.permissions.grantHint': 'Dica de concessão',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Monitoramento de Entrada',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS aplica privacidade',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Atualizando…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Atualizando…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.requesting': 'Solicitando…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Reiniciando o core…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Reiniciando o core…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Gravação de Tela',
|
||||
'settings.screenIntel.permissions.title': 'Permissões',
|
||||
'skills.card.moreActions': 'Mais ações',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6279,36 +6173,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Modelos',
|
||||
'skills.resource.tree.empty': 'Sem recursos incluídos.',
|
||||
'skills.search.placeholder': 'Espaço reservado',
|
||||
'skills.setup.screenIntel.activeDesc': 'Descrição ativo',
|
||||
'skills.setup.screenIntel.activeTitle': 'Inteligência de Tela está Ativada',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Configurações avançadas',
|
||||
'skills.setup.screenIntel.allGranted': 'Todas as permissões concedidas',
|
||||
'skills.setup.screenIntel.captureMode': 'Modo de captura',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Todas as janelas (configurável depois)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'Após conceder permissões nas Configurações do Sistema, clique abaixo para reiniciar e aplicar as alterações.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Ativando...',
|
||||
'skills.setup.screenIntel.enableDesc': 's na sua tela e alimentar contexto útil no seu agente',
|
||||
'skills.setup.screenIntel.enableError': 'Falha ao ativar Inteligência de Tela',
|
||||
'skills.setup.screenIntel.enabling': 'Ativando...',
|
||||
'skills.setup.screenIntel.grant': 'Abrindo...',
|
||||
'skills.setup.screenIntel.granted': 'Concedido',
|
||||
'skills.setup.screenIntel.macosOnly': 'Apenas macOS',
|
||||
'skills.setup.screenIntel.opening': 'Abrindo...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Tecla de pânico',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Acessibilidade',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Monitoramento de Entrada',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Gravação de Tela',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Descrição de permissões',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS aplica privacidade a:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Atualizar status',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Reiniciando...',
|
||||
'skills.setup.screenIntel.restarting': 'Reiniciando...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Ativar a habilidade',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Conceder permissões',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Pronto para usar',
|
||||
'skills.setup.screenIntel.title': 'Inteligência de Tela',
|
||||
'skills.setup.screenIntel.visionModel': 'Modelo de visão',
|
||||
'skills.setup.voice.activation': 'Ativação',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6469,9 +6333,6 @@ const messages: TranslationMap = {
|
||||
'devOptions.menuAi': 'Configuração de IA',
|
||||
'devOptions.menuAiDesc':
|
||||
'Provedores de nuvem, modelos Ollama locais e roteamento por carga de trabalho',
|
||||
'devOptions.menuScreenAware': 'Reconhecimento de tela',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Permissões de captura de tela, política de monitoramento e controles de sessão',
|
||||
'devOptions.menuMessaging': 'Canais de mensagens',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Configurar modos de autenticação Telegram/Discord e roteamento de canal padrão',
|
||||
|
||||
@@ -9,8 +9,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanionDesc':
|
||||
'Режим постоянного компаньона с ярлыком в системном лотке',
|
||||
'pages.settings.features.desktopCompanion': 'Десктоп-спутник',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'Голосовой ассистент с распознаванием экрана: слушает, видит, говорит, указывает',
|
||||
'settings.companion.title': 'Помощник на рабочем столе',
|
||||
'settings.companion.session': 'Сеанс',
|
||||
'settings.companion.activeLabel': 'Активен',
|
||||
@@ -26,8 +24,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': 'Горячая клавиша',
|
||||
'settings.companion.activationMode': 'Режим активации',
|
||||
'settings.companion.sessionTtl': 'TTL сеанса',
|
||||
'settings.companion.screenCapture': 'Снимок экрана',
|
||||
'settings.companion.appContext': 'Контекст приложения',
|
||||
// Share cards (#5006).
|
||||
'share.button': 'Поделиться',
|
||||
'share.modalTitle': 'Поделитесь этим моментом',
|
||||
@@ -112,8 +108,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': 'Выберите модель эмбеддингов для поиска в памяти.',
|
||||
'connections.header.search': 'Настройте веб-поиск для вашего агента.',
|
||||
'connections.header.wallet': 'Баланс вашего локального кошелька и фраза восстановления.',
|
||||
'connections.header.screen':
|
||||
'Позвольте агенту видеть ваш экран, чтобы предлагать своевременную контекстную помощь.',
|
||||
'connections.header.composioKey':
|
||||
'Добавьте ключ API Composio, чтобы разблокировать сотни интеграций приложений.',
|
||||
'connections.llm.apiKeys': 'Ключи API и маршрутизация',
|
||||
@@ -767,8 +761,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.backgroundActivity': 'Подсознание',
|
||||
'settings.assistant.backgroundActivityDesc':
|
||||
'Управление тем, насколько активно ассистент работает в фоне',
|
||||
'settings.assistant.screenAwareness': 'Осведомлённость об экране',
|
||||
'settings.assistant.screenAwarenessDesc': 'Разрешить ассистенту видеть активное окно',
|
||||
'settings.assistant.permissions': 'Разрешения',
|
||||
'settings.assistant.permissionsDesc':
|
||||
'Выберите, что может делать помощник и где он может работать',
|
||||
@@ -851,7 +843,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': 'Настройки',
|
||||
'settings.notifications.tabs.routing': 'Маршрутизация',
|
||||
'settings.features': 'Функции',
|
||||
'settings.featuresDesc': 'Слежение за экраном, мессенджеры и инструменты',
|
||||
'settings.aiModels': 'AI и модели',
|
||||
'settings.aiModelsDesc': 'Настройка локальных AI-моделей, загрузки и LLM-провайдер',
|
||||
'settings.ai': 'Настройки AI',
|
||||
@@ -946,8 +937,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': 'Отключить все уведомления на заданный период',
|
||||
'settings.notifications.channelControls': 'По каналам',
|
||||
'settings.notifications.channelControlsDesc': 'Настройка уведомлений для каждого канала',
|
||||
'settings.features.screenAwareness': 'Слежение за экраном',
|
||||
'settings.features.screenAwarenessDesc': 'Разреши ассистенту видеть активное окно',
|
||||
'settings.features.messaging': 'Мессенджеры',
|
||||
'settings.features.messagingDesc': 'Настройки каналов и интеграции мессенджеров',
|
||||
'settings.features.tools': 'Инструменты',
|
||||
@@ -2226,21 +2215,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': 'Сохранить изменения',
|
||||
'settings.tools.preferencesSaved': 'Настройки сохранены',
|
||||
'settings.tools.saveFailed': 'Не удалось сохранить настройки. Попробуй ещё раз.',
|
||||
'settings.screenAwareness.mode': 'Режим',
|
||||
'settings.screenAwareness.allExceptBlacklist': 'Всё, кроме исключений',
|
||||
'settings.screenAwareness.whitelistOnly': 'Только разрешённые',
|
||||
'settings.screenAwareness.screenMonitoring': 'Мониторинг экрана',
|
||||
'settings.screenAwareness.saveSettings': 'Сохранить настройки',
|
||||
'settings.screenAwareness.session': 'Сессия',
|
||||
'settings.screenAwareness.status': 'Статус',
|
||||
'settings.screenAwareness.active': 'Активно',
|
||||
'settings.screenAwareness.stopped': 'Остановлено',
|
||||
'settings.screenAwareness.remaining': 'Осталось',
|
||||
'settings.screenAwareness.startSession': 'Начать сессию',
|
||||
'settings.screenAwareness.stopSession': 'Завершить сессию',
|
||||
'settings.screenAwareness.analyzeNow': 'Анализировать сейчас',
|
||||
'settings.screenAwareness.macosOnly':
|
||||
'Захват экрана и управление разрешениями поддерживаются только на macOS.',
|
||||
'connections.comingSoon': 'Скоро',
|
||||
'connections.setUp': 'Настроить',
|
||||
'connections.configured': 'Настроено',
|
||||
@@ -2601,37 +2575,6 @@ const messages: TranslationMap = {
|
||||
'Главный переключатель. По умолчанию выключен: Ollama простаивает. При включении суммаризатор деревьев, интеллект экрана и автодополнение всегда используют локальную модель.',
|
||||
'localModel.advancedSettings': 'Дополнительные настройки',
|
||||
'localModel.debugTitle': 'Отладка локальной модели',
|
||||
'screenAwareness.debugTitle': 'Отладка слежения за экраном',
|
||||
'screenAwareness.debug.debugAndDiagnostics': 'Отладка и диагностика',
|
||||
'screenAwareness.debug.collapse': 'Свернуть',
|
||||
'screenAwareness.debug.expand': 'Развернуть',
|
||||
'screenAwareness.debug.failedToSave': 'Не удалось сохранить интеллект экрана.',
|
||||
'screenAwareness.debug.policyTitle': 'Политика интеллекта экрана',
|
||||
'screenAwareness.debug.baselineFps': 'Базовый показатель FPS',
|
||||
'screenAwareness.debug.useVisionModel': 'Использовать модель концепции.',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'Отправлять снимки экрана в визуальную языковую модель для более богатого контекста. Если отключено, используется только OCR-текст с текстовой моделью: быстрее и без требований к визуальной модели.',
|
||||
'screenAwareness.debug.keepScreenshots': 'Сохранять снимки экрана.',
|
||||
'screenAwareness.debug.keepScreenshotsDesc':
|
||||
'Сохранять снимки экрана в рабочей области вместо удаления после обработки',
|
||||
'screenAwareness.debug.allowlist': 'Список разрешенных (одно правило в строке)',
|
||||
'screenAwareness.debug.denylist': 'Список запрещенных (одно правило в строке)',
|
||||
'screenAwareness.debug.saveSettings': 'Сохранить настройки интеллекта экрана',
|
||||
'screenAwareness.debug.sessionStats': 'Статистика сеанса',
|
||||
'screenAwareness.debug.framesEphemeral': 'Кадры (эфемерные)',
|
||||
'screenAwareness.debug.panicStop': 'Паника-стоп',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+.',
|
||||
'screenAwareness.debug.vision': 'Видение',
|
||||
'screenAwareness.debug.idle': 'бездействующее',
|
||||
'screenAwareness.debug.visionQueue': 'Очередь видений',
|
||||
'screenAwareness.debug.lastVision': 'Последнее видение',
|
||||
'screenAwareness.debug.notAvailable': 'н/д',
|
||||
'screenAwareness.debug.visionSummaries': 'Сводки видений',
|
||||
'screenAwareness.debug.refreshing': 'Обновление…',
|
||||
'screenAwareness.debug.noSummaries': 'Сводок пока нет.',
|
||||
'screenAwareness.debug.unknownApp': 'Неизвестное приложение.',
|
||||
'screenAwareness.debug.macosOnly':
|
||||
'Screen Intelligence V1 в настоящее время поддерживается только на macOS.',
|
||||
'memory.debugTitle': 'Отладка памяти',
|
||||
'memory.documents': 'Документы',
|
||||
'memory.filterByNamespace': 'Фильтровать по пространству имен...',
|
||||
@@ -3893,33 +3836,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': 'Последняя сгенерированная архитектурная диаграмма OpenHuman',
|
||||
'intelligence.diagram.refreshesEvery': 'Обновляется каждые {seconds} с',
|
||||
'intelligence.memoryText.entityTypePrefix': 'Тип сущности',
|
||||
'intelligence.screenDebug.active': 'Активно',
|
||||
'intelligence.screenDebug.app': 'Приложение',
|
||||
'intelligence.screenDebug.bounds': 'Границы',
|
||||
'intelligence.screenDebug.captureAlt': 'Результат тестового захвата',
|
||||
'intelligence.screenDebug.captureFailed': 'Ошибка',
|
||||
'intelligence.screenDebug.captureSuccess': 'Успех',
|
||||
'intelligence.screenDebug.captureTest': 'Тест захвата',
|
||||
'intelligence.screenDebug.capturing': 'Захват',
|
||||
'intelligence.screenDebug.frames': 'Кадры',
|
||||
'intelligence.screenDebug.idle': 'Ожидание',
|
||||
'intelligence.screenDebug.lastApp': 'Последнее приложение',
|
||||
'intelligence.screenDebug.mode': 'Режим',
|
||||
'intelligence.screenDebug.permAccessibility': 'Доступность',
|
||||
'intelligence.screenDebug.permInput': 'Ввод',
|
||||
'intelligence.screenDebug.permScreen': 'Доступность',
|
||||
'intelligence.screenDebug.permissions': 'Разрешения',
|
||||
'intelligence.screenDebug.platformNotSupported': 'Платформа не поддерживается',
|
||||
'intelligence.screenDebug.recentVisionSummaries': 'Недавние визуальные резюме',
|
||||
'intelligence.screenDebug.session': 'Сессия',
|
||||
'intelligence.screenDebug.size': 'Размер',
|
||||
'intelligence.screenDebug.status': 'Статус',
|
||||
'intelligence.screenDebug.testCapture': 'Тестовый захват',
|
||||
'intelligence.screenDebug.time': 'Время',
|
||||
'intelligence.screenDebug.title': 'Заголовок',
|
||||
'intelligence.screenDebug.unknown': 'Неизвестно',
|
||||
'intelligence.screenDebug.visionQueue': 'Очередь обработки изображений',
|
||||
'intelligence.screenDebug.visionState': 'Состояние обработки изображений',
|
||||
'intelligence.tasks.activeBoardOne': '1 активная доска в разговорах',
|
||||
'intelligence.tasks.activeBoardOther': '{count} активных досок в разговорах',
|
||||
'intelligence.tasks.empty': 'Досок задач агента пока нет',
|
||||
@@ -4516,7 +4432,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': 'Голосовой ввод активен',
|
||||
'overlay.companion.error': 'Ошибка',
|
||||
'overlay.companion.listening': 'Слушает…',
|
||||
'overlay.companion.pointing': 'Указывает…',
|
||||
'overlay.companion.speaking': 'Говорит…',
|
||||
'overlay.companion.thinking': 'Думает…',
|
||||
'overlay.orbTitle': 'Перетащи для перемещения · Двойной клик для сброса позиции',
|
||||
@@ -4547,12 +4462,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': 'Описание каналов сообщений',
|
||||
'pages.settings.features.notifications': 'Уведомления',
|
||||
'pages.settings.features.notificationsDesc': 'Описание уведомлений',
|
||||
'pages.settings.features.screenAwareness': 'Слежение за экраном',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'Позвольте агенту видеть ваш экран, чтобы предлагать своевременную контекстную помощь.',
|
||||
'pages.settings.features.tools': 'Инструменты',
|
||||
'pages.settings.features.toolsDesc': 'Описание инструментов',
|
||||
'pages.settings.featuresSection.description': 'Слежение за экраном, мессенджеры и инструменты.',
|
||||
'pages.settings.featuresSection.title': 'Функции',
|
||||
'privacy.dataKind.credentials': 'Учётные данные',
|
||||
'privacy.dataKind.derived': 'Производные данные',
|
||||
@@ -5314,9 +5225,6 @@ const messages: TranslationMap = {
|
||||
'settings.developerMenu.ai.title': 'Конфигурация ИИ',
|
||||
'settings.developerMenu.ai.desc':
|
||||
'Облачные провайдеры, локальные модели Ollama и маршрутизация по типам нагрузки',
|
||||
'settings.developerMenu.screenAwareness.title': 'Осведомленность об экране',
|
||||
'settings.developerMenu.screenAwareness.desc':
|
||||
'Разрешения на захват экрана, политика мониторинга и управление сессиями',
|
||||
'settings.developerMenu.messagingChannels.title': 'Каналы сообщений',
|
||||
'settings.developerMenu.messagingChannels.desc':
|
||||
'Настройка режимов аутентификации Telegram/Discord и маршрутизации канала по умолчанию',
|
||||
@@ -5971,20 +5879,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': 'Применить замену',
|
||||
'settings.modelHealth.tag.cheaper': 'CHEAPER',
|
||||
'settings.modelHealth.tag.better': 'BETTER',
|
||||
'settings.screenIntel.permissions.accessibility': 'Доступность',
|
||||
'settings.screenIntel.permissions.grantHint': 'Подсказка по предоставлению',
|
||||
'settings.screenIntel.permissions.inputMonitoring': 'Мониторинг ввода',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS применяет конфиденциальность',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': 'Запрос…',
|
||||
'settings.screenIntel.permissions.refreshStatus': 'Обновление…',
|
||||
'settings.screenIntel.permissions.refreshing': 'Обновление…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': 'Запрос…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': 'Запрос…',
|
||||
'settings.screenIntel.permissions.requesting': 'Запрос…',
|
||||
'settings.screenIntel.permissions.restartRefresh': 'Перезапуск ядра…',
|
||||
'settings.screenIntel.permissions.restartingCore': 'Перезапуск ядра…',
|
||||
'settings.screenIntel.permissions.screenRecording': 'Запись экрана',
|
||||
'settings.screenIntel.permissions.title': 'Разрешения',
|
||||
'skills.card.moreActions': 'Ещё действия',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -6243,37 +6137,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': 'Шаблоны',
|
||||
'skills.resource.tree.empty': 'Встроенных ресурсов нет.',
|
||||
'skills.search.placeholder': 'Поиск',
|
||||
'skills.setup.screenIntel.activeDesc': 'Активно',
|
||||
'skills.setup.screenIntel.activeTitle': 'Интеллект экрана включён',
|
||||
'skills.setup.screenIntel.advancedSettings': 'Дополнительные настройки',
|
||||
'skills.setup.screenIntel.allGranted': 'Все разрешения предоставлены',
|
||||
'skills.setup.screenIntel.captureMode': 'Режим захвата',
|
||||
'skills.setup.screenIntel.captureModeValue': 'Все окна (можно изменить позже)',
|
||||
'skills.setup.screenIntel.deniedHint':
|
||||
'После предоставления разрешений в Системных настройках нажми ниже для перезапуска и применения изменений.',
|
||||
'skills.setup.screenIntel.enableBtn': 'Включение...',
|
||||
'skills.setup.screenIntel.enableDesc':
|
||||
'Видеть, что происходит на вашем экране, и передавать полезный контекст вашему агенту',
|
||||
'skills.setup.screenIntel.enableError': 'Не удалось включить интеллект экрана',
|
||||
'skills.setup.screenIntel.enabling': 'Включение...',
|
||||
'skills.setup.screenIntel.grant': 'Открытие...',
|
||||
'skills.setup.screenIntel.granted': 'Предоставлено',
|
||||
'skills.setup.screenIntel.macosOnly': 'Только macOS',
|
||||
'skills.setup.screenIntel.opening': 'Открытие...',
|
||||
'skills.setup.screenIntel.panicHotkey': 'Экстренная горячая клавиша',
|
||||
'skills.setup.screenIntel.permAccessibility': 'Доступность',
|
||||
'skills.setup.screenIntel.permInputMonitoring': 'Мониторинг ввода',
|
||||
'skills.setup.screenIntel.permScreenRecording': 'Запись экрана',
|
||||
'skills.setup.screenIntel.permissionsDesc': 'Описание разрешений',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS применяет конфиденциальность к:',
|
||||
'skills.setup.screenIntel.refreshStatus': 'Обновить статус',
|
||||
'skills.setup.screenIntel.restartRefresh': 'Перезапуск...',
|
||||
'skills.setup.screenIntel.restarting': 'Перезапуск...',
|
||||
'skills.setup.screenIntel.stepEnable': 'Включить навык',
|
||||
'skills.setup.screenIntel.stepPermissions': 'Предоставить разрешения',
|
||||
'skills.setup.screenIntel.stepSuccess': 'Готово к работе',
|
||||
'skills.setup.screenIntel.title': 'Интеллект экрана',
|
||||
'skills.setup.screenIntel.visionModel': 'Визуальная модель',
|
||||
'skills.setup.voice.activation': 'Активация',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6435,9 +6298,6 @@ const messages: TranslationMap = {
|
||||
'devOptions.menuAi': 'Конфигурация AI',
|
||||
'devOptions.menuAiDesc':
|
||||
'Облачные поставщики, локальные модели Ollama и маршрутизация для каждой рабочей нагрузки',
|
||||
'devOptions.menuScreenAware': 'Функция Screen Aware',
|
||||
'devOptions.menuScreenAwareDesc':
|
||||
'Разрешения на захват экрана, мониторинг политика и элементы управления сеансом',
|
||||
'devOptions.menuMessaging': 'Каналы обмена сообщениями',
|
||||
'devOptions.menuMessagingDesc':
|
||||
'Настройка режимов аутентификации Telegram/Discord и маршрутизации каналов по умолчанию',
|
||||
|
||||
@@ -7,8 +7,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.desktopCompanion': '桌面伴侣',
|
||||
'settings.assistant.desktopCompanionDesc': '带有系统托盘快捷方式的常驻伴侣模式',
|
||||
'pages.settings.features.desktopCompanion': '桌面伴侣',
|
||||
'pages.settings.features.desktopCompanionDesc':
|
||||
'具有屏幕感知能力的语音助手:倾听、观看、说话、指向',
|
||||
'settings.companion.title': '桌面伴侣',
|
||||
'settings.companion.session': '会话',
|
||||
'settings.companion.activeLabel': '活跃',
|
||||
@@ -24,8 +22,6 @@ const messages: TranslationMap = {
|
||||
'settings.companion.hotkey': '热键',
|
||||
'settings.companion.activationMode': '激活模式',
|
||||
'settings.companion.sessionTtl': '会话有效期',
|
||||
'settings.companion.screenCapture': '屏幕截图',
|
||||
'settings.companion.appContext': '应用程序上下文',
|
||||
// Share cards (#5006).
|
||||
'share.button': '分享',
|
||||
'share.modalTitle': '分享这一刻',
|
||||
@@ -108,7 +104,6 @@ const messages: TranslationMap = {
|
||||
'connections.header.embeddings': '选择用于记忆搜索的嵌入模型。',
|
||||
'connections.header.search': '为你的智能体配置网络搜索。',
|
||||
'connections.header.wallet': '你的本地钱包余额和恢复短语。',
|
||||
'connections.header.screen': '让你的智能体查看你的屏幕,以便提供及时、贴合上下文的帮助。',
|
||||
'connections.header.composioKey': '添加 Composio API 密钥,解锁数百个应用集成。',
|
||||
'connections.llm.apiKeys': 'API 密钥与路由',
|
||||
'connections.welcome.nav': '欢迎',
|
||||
@@ -723,8 +718,6 @@ const messages: TranslationMap = {
|
||||
'settings.assistant.faceMascot': '面孔 / 吉祥物',
|
||||
'settings.assistant.backgroundActivity': '潜意识',
|
||||
'settings.assistant.backgroundActivityDesc': '控制助手在后台工作的活跃程度',
|
||||
'settings.assistant.screenAwareness': '屏幕感知',
|
||||
'settings.assistant.screenAwarenessDesc': '允许助手查看您的活动窗口',
|
||||
'settings.assistant.permissions': '权限',
|
||||
'settings.assistant.permissionsDesc': '选择助手可以做什么以及可以在哪里工作',
|
||||
'settings.privacySecurity.privacy': '隐私',
|
||||
@@ -798,7 +791,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.tabs.preferences': '偏好设置',
|
||||
'settings.notifications.tabs.routing': '路由',
|
||||
'settings.features': '功能',
|
||||
'settings.featuresDesc': '屏幕感知、消息与工具',
|
||||
'settings.aiModels': 'AI 与模型',
|
||||
'settings.aiModelsDesc': '本地 AI 模型设置、下载与 LLM 提供商',
|
||||
'settings.ai': 'AI',
|
||||
@@ -886,8 +878,6 @@ const messages: TranslationMap = {
|
||||
'settings.notifications.doNotDisturbDesc': '在一定时间内暂停所有通知',
|
||||
'settings.notifications.channelControls': '各渠道控制',
|
||||
'settings.notifications.channelControlsDesc': '为每个渠道配置通知偏好',
|
||||
'settings.features.screenAwareness': '屏幕感知',
|
||||
'settings.features.screenAwarenessDesc': '让助手看到你的活动窗口',
|
||||
'settings.features.messaging': '消息',
|
||||
'settings.features.messagingDesc': '渠道与消息集成设置',
|
||||
'settings.features.tools': '工具',
|
||||
@@ -2085,20 +2075,6 @@ const messages: TranslationMap = {
|
||||
'settings.tools.saveChanges': '保存更改',
|
||||
'settings.tools.preferencesSaved': '偏好设置已保存',
|
||||
'settings.tools.saveFailed': '保存偏好失败,请重试。',
|
||||
'settings.screenAwareness.mode': '模式',
|
||||
'settings.screenAwareness.allExceptBlacklist': '除黑名单外全部',
|
||||
'settings.screenAwareness.whitelistOnly': '仅白名单',
|
||||
'settings.screenAwareness.screenMonitoring': '屏幕监控',
|
||||
'settings.screenAwareness.saveSettings': '保存设置',
|
||||
'settings.screenAwareness.session': '会话',
|
||||
'settings.screenAwareness.status': '状态',
|
||||
'settings.screenAwareness.active': '活跃',
|
||||
'settings.screenAwareness.stopped': '已停止',
|
||||
'settings.screenAwareness.remaining': '剩余',
|
||||
'settings.screenAwareness.startSession': '开始会话',
|
||||
'settings.screenAwareness.stopSession': '停止会话',
|
||||
'settings.screenAwareness.analyzeNow': '立即分析',
|
||||
'settings.screenAwareness.macosOnly': '屏幕感知桌面捕获和权限控制目前仅在 macOS 上受支持。',
|
||||
'connections.comingSoon': '即将推出',
|
||||
'connections.setUp': '设置',
|
||||
'connections.configured': '已配置',
|
||||
@@ -2434,35 +2410,6 @@ const messages: TranslationMap = {
|
||||
'总开关。默认关闭:Ollama 保持空闲。启用后,树摘要器、屏幕智能和自动补全始终使用本地模型。',
|
||||
'localModel.advancedSettings': '高级设置',
|
||||
'localModel.debugTitle': '本地模型调试',
|
||||
'screenAwareness.debugTitle': '屏幕感知调试',
|
||||
'screenAwareness.debug.debugAndDiagnostics': '调试与诊断',
|
||||
'screenAwareness.debug.collapse': '崩溃',
|
||||
'screenAwareness.debug.expand': '展开',
|
||||
'screenAwareness.debug.failedToSave': '保存屏幕情报失败',
|
||||
'screenAwareness.debug.policyTitle': '屏幕情报政策',
|
||||
'screenAwareness.debug.baselineFps': '基线 FPS',
|
||||
'screenAwareness.debug.useVisionModel': '使用视觉模型',
|
||||
'screenAwareness.debug.useVisionModelDesc':
|
||||
'将截图发送给视觉 LLM 以获取更丰富的上下文。关闭时,仅使用 OCR 文本配合纯文本 LLM:速度更快且无需视觉模型。',
|
||||
'screenAwareness.debug.keepScreenshots': '保留截图',
|
||||
'screenAwareness.debug.keepScreenshotsDesc': '将捕获的截图保存到工作区,而非处理后删除',
|
||||
'screenAwareness.debug.allowlist': '允许列表(每行一条规则)',
|
||||
'screenAwareness.debug.denylist': '拒绝名单(每行一条规则)',
|
||||
'screenAwareness.debug.saveSettings': '保存屏幕智能设置',
|
||||
'screenAwareness.debug.sessionStats': '会话统计',
|
||||
'screenAwareness.debug.framesEphemeral': '框架(临时)',
|
||||
'screenAwareness.debug.panicStop': '紧急停止',
|
||||
'screenAwareness.debug.defaultPanicHotkey': 'Cmd+Shift+。',
|
||||
'screenAwareness.debug.vision': '愿景',
|
||||
'screenAwareness.debug.idle': '空闲',
|
||||
'screenAwareness.debug.visionQueue': '视觉队列',
|
||||
'screenAwareness.debug.lastVision': '最后的愿景',
|
||||
'screenAwareness.debug.notAvailable': '不适用',
|
||||
'screenAwareness.debug.visionSummaries': '愿景摘要',
|
||||
'screenAwareness.debug.refreshing': '清爽…',
|
||||
'screenAwareness.debug.noSummaries': '还没有总结。',
|
||||
'screenAwareness.debug.unknownApp': '未知应用程序',
|
||||
'screenAwareness.debug.macosOnly': 'Screen Intelligence V1 目前仅在 macOS 上受支持。',
|
||||
'memory.debugTitle': '记忆调试',
|
||||
'memory.documents': '文件',
|
||||
'memory.filterByNamespace': '按名称空间过滤...',
|
||||
@@ -3662,33 +3609,6 @@ const messages: TranslationMap = {
|
||||
'intelligence.diagram.imageAlt': '最新生成的 OpenHuman 架构图',
|
||||
'intelligence.diagram.refreshesEvery': '每 {seconds} 秒刷新',
|
||||
'intelligence.memoryText.entityTypePrefix': '实体类型',
|
||||
'intelligence.screenDebug.active': '活跃',
|
||||
'intelligence.screenDebug.app': '应用',
|
||||
'intelligence.screenDebug.bounds': '边界',
|
||||
'intelligence.screenDebug.captureAlt': '捕获测试结果',
|
||||
'intelligence.screenDebug.captureFailed': '失败',
|
||||
'intelligence.screenDebug.captureSuccess': '成功',
|
||||
'intelligence.screenDebug.captureTest': '捕获测试',
|
||||
'intelligence.screenDebug.capturing': '捕获中',
|
||||
'intelligence.screenDebug.frames': '帧数',
|
||||
'intelligence.screenDebug.idle': '空闲',
|
||||
'intelligence.screenDebug.lastApp': '上一个应用',
|
||||
'intelligence.screenDebug.mode': '模式',
|
||||
'intelligence.screenDebug.permAccessibility': '辅助功能权限',
|
||||
'intelligence.screenDebug.permInput': '输入监控权限',
|
||||
'intelligence.screenDebug.permScreen': '辅助功能',
|
||||
'intelligence.screenDebug.permissions': '权限',
|
||||
'intelligence.screenDebug.platformNotSupported': '平台不支持',
|
||||
'intelligence.screenDebug.recentVisionSummaries': '最近视觉摘要',
|
||||
'intelligence.screenDebug.session': '会话',
|
||||
'intelligence.screenDebug.size': '大小',
|
||||
'intelligence.screenDebug.status': '状态',
|
||||
'intelligence.screenDebug.testCapture': '测试捕获',
|
||||
'intelligence.screenDebug.time': '时间',
|
||||
'intelligence.screenDebug.title': '标题',
|
||||
'intelligence.screenDebug.unknown': '未知',
|
||||
'intelligence.screenDebug.visionQueue': '视觉队列',
|
||||
'intelligence.screenDebug.visionState': '视觉状态',
|
||||
'intelligence.tasks.activeBoardOne': '1 个跨对话的活跃看板',
|
||||
'intelligence.tasks.activeBoardOther': '{count} 个跨对话的活跃看板',
|
||||
'intelligence.tasks.empty': '暂无智能体任务看板',
|
||||
@@ -4231,7 +4151,6 @@ const messages: TranslationMap = {
|
||||
'overlay.ariaVoiceActive': '语音输入已激活',
|
||||
'overlay.companion.error': '错误',
|
||||
'overlay.companion.listening': '正在聆听…',
|
||||
'overlay.companion.pointing': '正在指向…',
|
||||
'overlay.companion.speaking': '正在说话…',
|
||||
'overlay.companion.thinking': '正在思考…',
|
||||
'overlay.orbTitle': '拖动以移动 · 双击重置位置',
|
||||
@@ -4260,12 +4179,8 @@ const messages: TranslationMap = {
|
||||
'pages.settings.features.messagingChannelsDesc': '配置消息渠道和集成',
|
||||
'pages.settings.features.notifications': '通知',
|
||||
'pages.settings.features.notificationsDesc': '管理通知偏好',
|
||||
'pages.settings.features.screenAwareness': '屏幕感知',
|
||||
'pages.settings.features.screenAwarenessDesc':
|
||||
'让你的智能体查看你的屏幕,以便提供及时、贴合上下文的帮助。',
|
||||
'pages.settings.features.tools': '工具',
|
||||
'pages.settings.features.toolsDesc': '管理已连接的工具和集成',
|
||||
'pages.settings.featuresSection.description': '屏幕感知、消息和工具。',
|
||||
'pages.settings.featuresSection.title': '功能',
|
||||
'privacy.dataKind.credentials': '凭据',
|
||||
'privacy.dataKind.derived': '派生数据',
|
||||
@@ -4988,8 +4903,6 @@ const messages: TranslationMap = {
|
||||
'settings.localModel.status.working': '处理中...',
|
||||
'settings.developerMenu.ai.title': 'AI 配置',
|
||||
'settings.developerMenu.ai.desc': '云端提供商、本地 Ollama 模型和按工作负载路由',
|
||||
'settings.developerMenu.screenAwareness.title': '屏幕感知',
|
||||
'settings.developerMenu.screenAwareness.desc': '屏幕捕获权限、监控策略和会话控制',
|
||||
'settings.developerMenu.messagingChannels.title': '消息渠道',
|
||||
'settings.developerMenu.messagingChannels.desc': '配置 Telegram/Discord 认证模式和默认渠道路由',
|
||||
'settings.developerMenu.tools.title': '工具',
|
||||
@@ -5591,20 +5504,6 @@ const messages: TranslationMap = {
|
||||
'settings.modelHealth.modal.apply': '应用替换',
|
||||
'settings.modelHealth.tag.cheaper': '更便宜',
|
||||
'settings.modelHealth.tag.better': '更好',
|
||||
'settings.screenIntel.permissions.accessibility': '辅助功能',
|
||||
'settings.screenIntel.permissions.grantHint': '在系统设置中授权后,点击下方刷新',
|
||||
'settings.screenIntel.permissions.inputMonitoring': '输入监控',
|
||||
'settings.screenIntel.permissions.macosAppliesPrivacy': 'macOS 会在所有窗口中应用隐私保护',
|
||||
'settings.screenIntel.permissions.openInputMonitoring': '请求中…',
|
||||
'settings.screenIntel.permissions.refreshStatus': '刷新中…',
|
||||
'settings.screenIntel.permissions.refreshing': '刷新中…',
|
||||
'settings.screenIntel.permissions.requestAccessibility': '请求中…',
|
||||
'settings.screenIntel.permissions.requestScreenRecording': '请求中…',
|
||||
'settings.screenIntel.permissions.requesting': '请求中…',
|
||||
'settings.screenIntel.permissions.restartRefresh': '正在重启核心…',
|
||||
'settings.screenIntel.permissions.restartingCore': '正在重启核心…',
|
||||
'settings.screenIntel.permissions.screenRecording': '屏幕录制',
|
||||
'settings.screenIntel.permissions.title': '权限',
|
||||
'skills.card.moreActions': '更多操作',
|
||||
'skills.channelIcon.discord': 'Discord',
|
||||
'skills.channelIcon.imessage': 'iMessage',
|
||||
@@ -5853,35 +5752,6 @@ const messages: TranslationMap = {
|
||||
'skills.resource.tree.templates': '模板',
|
||||
'skills.resource.tree.empty': '无捆绑资源。',
|
||||
'skills.search.placeholder': '搜索技能...',
|
||||
'skills.setup.screenIntel.activeDesc': '屏幕智能已激活,正在感知你的屏幕',
|
||||
'skills.setup.screenIntel.activeTitle': '屏幕智能已启用',
|
||||
'skills.setup.screenIntel.advancedSettings': '高级设置',
|
||||
'skills.setup.screenIntel.allGranted': '所有权限已授予',
|
||||
'skills.setup.screenIntel.captureMode': '捕获模式',
|
||||
'skills.setup.screenIntel.captureModeValue': '所有窗口(稍后可配置)',
|
||||
'skills.setup.screenIntel.deniedHint': '在系统设置中授权后,点击下方重启以应用更改。',
|
||||
'skills.setup.screenIntel.enableBtn': '启用中...',
|
||||
'skills.setup.screenIntel.enableDesc': '检测屏幕上的内容并将有用的上下文提供给助手',
|
||||
'skills.setup.screenIntel.enableError': '启用屏幕智能失败',
|
||||
'skills.setup.screenIntel.enabling': '启用中...',
|
||||
'skills.setup.screenIntel.grant': '打开中...',
|
||||
'skills.setup.screenIntel.granted': '已授予',
|
||||
'skills.setup.screenIntel.macosOnly': '仅支持 macOS',
|
||||
'skills.setup.screenIntel.opening': '打开中...',
|
||||
'skills.setup.screenIntel.panicHotkey': '紧急热键',
|
||||
'skills.setup.screenIntel.permAccessibility': '辅助功能',
|
||||
'skills.setup.screenIntel.permInputMonitoring': '输入监控',
|
||||
'skills.setup.screenIntel.permScreenRecording': '屏幕录制',
|
||||
'skills.setup.screenIntel.permissionsDesc': '屏幕智能需要以下权限才能正常工作',
|
||||
'skills.setup.screenIntel.permissionPathLabel': 'macOS 将隐私应用于:',
|
||||
'skills.setup.screenIntel.refreshStatus': '刷新状态',
|
||||
'skills.setup.screenIntel.restartRefresh': '重启中...',
|
||||
'skills.setup.screenIntel.restarting': '重启中...',
|
||||
'skills.setup.screenIntel.stepEnable': '启用技能',
|
||||
'skills.setup.screenIntel.stepPermissions': '授予权限',
|
||||
'skills.setup.screenIntel.stepSuccess': '准备就绪',
|
||||
'skills.setup.screenIntel.title': '屏幕智能',
|
||||
'skills.setup.screenIntel.visionModel': '视觉模型',
|
||||
'skills.setup.voice.activation': '激活方式',
|
||||
'skills.setup.voice.activeDescPrefix': 'Fn',
|
||||
'skills.setup.voice.activeDescSuffix': 'Fn',
|
||||
@@ -6031,8 +5901,6 @@ const messages: TranslationMap = {
|
||||
'settings.localModel.status.thenRetry': '获取设置说明,待你的运行时可达后再重试。',
|
||||
'devOptions.menuAi': 'AI 配置',
|
||||
'devOptions.menuAiDesc': '云端提供商、本地 Ollama 模型与按工作负载路由',
|
||||
'devOptions.menuScreenAware': '屏幕感知',
|
||||
'devOptions.menuScreenAwareDesc': '屏幕捕获权限、监控策略与会话控制',
|
||||
'devOptions.menuMessaging': '消息通道',
|
||||
'devOptions.menuMessagingDesc': '配置 Telegram/Discord 认证模式与默认通道路由',
|
||||
'devOptions.menuTools': '工具',
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* CompanionPointer — renders point-target labels inside the overlay bubble area.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export interface PointTarget {
|
||||
absolute_x: number;
|
||||
absolute_y: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface CompanionPointerProps {
|
||||
targets: PointTarget[];
|
||||
/** Auto-dismiss each target after this many ms. Default: 2000 */
|
||||
dismissMs?: number;
|
||||
}
|
||||
|
||||
export default function CompanionPointer({ targets, dismissMs = 2000 }: CompanionPointerProps) {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (targets.length === 0) return;
|
||||
const showFrame = window.requestAnimationFrame(() => setVisible(true));
|
||||
const dismissTimer = window.setTimeout(() => setVisible(false), dismissMs);
|
||||
return () => {
|
||||
window.cancelAnimationFrame(showFrame);
|
||||
window.clearTimeout(dismissTimer);
|
||||
};
|
||||
}, [targets, dismissMs]);
|
||||
|
||||
if (!visible || targets.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
{targets.map((t, i) => (
|
||||
<div
|
||||
key={`${t.label}-${i}`}
|
||||
className="animate-[overlay-bubble-in_220ms_ease-out] rounded-lg bg-blue-600 px-2 py-1 text-xs text-white shadow-md">
|
||||
<span className="mr-1 inline-block h-2 w-2 rounded-full bg-blue-300 animate-pulse" />
|
||||
{t.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -119,8 +119,6 @@ export function companionStateLabel(state: string, t: (key: string) => string):
|
||||
return t('overlay.companion.thinking');
|
||||
case 'speaking':
|
||||
return t('overlay.companion.speaking');
|
||||
case 'pointing':
|
||||
return t('overlay.companion.pointing');
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import CompanionPointer, { type PointTarget } from '../CompanionPointer';
|
||||
|
||||
describe('CompanionPointer', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
// Make rAF fire synchronously so we can observe the visible→true transition
|
||||
// without juggling async/await boundaries in every case.
|
||||
vi.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
|
||||
cb(0);
|
||||
return 1;
|
||||
});
|
||||
vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders nothing when targets is empty', () => {
|
||||
const { container } = render(<CompanionPointer targets={[]} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('renders one label per target with the label text', () => {
|
||||
const targets: PointTarget[] = [
|
||||
{ absolute_x: 10, absolute_y: 20, label: 'Click here' },
|
||||
{ absolute_x: 30, absolute_y: 40, label: 'Then this' },
|
||||
];
|
||||
render(<CompanionPointer targets={targets} />);
|
||||
expect(screen.getByText('Click here')).toBeInTheDocument();
|
||||
expect(screen.getByText('Then this')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('auto-dismisses after dismissMs elapses', () => {
|
||||
const { container } = render(
|
||||
<CompanionPointer
|
||||
targets={[{ absolute_x: 0, absolute_y: 0, label: 'Vanish' }]}
|
||||
dismissMs={500}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('Vanish')).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('uses default dismissMs of 2000 when not provided', () => {
|
||||
const { container } = render(
|
||||
<CompanionPointer targets={[{ absolute_x: 0, absolute_y: 0, label: 'Default' }]} />
|
||||
);
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(1999);
|
||||
});
|
||||
expect(screen.getByText('Default')).toBeInTheDocument();
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(1);
|
||||
});
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('cancels rAF and timer on unmount', () => {
|
||||
const cancelTimerSpy = vi.spyOn(window, 'clearTimeout');
|
||||
const cancelRafSpy = vi.spyOn(window, 'cancelAnimationFrame');
|
||||
|
||||
const { unmount } = render(
|
||||
<CompanionPointer targets={[{ absolute_x: 0, absolute_y: 0, label: 'A' }]} />
|
||||
);
|
||||
unmount();
|
||||
|
||||
expect(cancelRafSpy).toHaveBeenCalled();
|
||||
expect(cancelTimerSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,6 @@ describe('companionStateLabel', () => {
|
||||
['listening', '“[overlay.companion.listening]”'],
|
||||
['thinking', '“[overlay.companion.thinking]”'],
|
||||
['speaking', '“[overlay.companion.speaking]”'],
|
||||
['pointing', '“[overlay.companion.pointing]”'],
|
||||
])('routes %s through the matching i18n key', (state, expected) => {
|
||||
expect(companionStateLabel(state, t)).toBe(expected);
|
||||
});
|
||||
|
||||
@@ -23,12 +23,10 @@ import ComposioPanel from '../components/settings/panels/ComposioPanel';
|
||||
import EmbeddingsPanel from '../components/settings/panels/EmbeddingsPanel';
|
||||
import LlmConnectionsPanel from '../components/settings/panels/LlmConnectionsPanel';
|
||||
import MeetingSettingsPanel from '../components/settings/panels/MeetingSettingsPanel';
|
||||
import ScreenIntelligencePanel from '../components/settings/panels/ScreenIntelligencePanel';
|
||||
import SearchPanel from '../components/settings/panels/SearchPanel';
|
||||
import UsagePanel from '../components/settings/panels/UsagePanel';
|
||||
import VoicePanel from '../components/settings/panels/VoicePanel';
|
||||
import WalletPanel from '../components/settings/panels/WalletPanel';
|
||||
import ScreenIntelligenceSetupModal from '../components/skills/ScreenIntelligenceSetupModal';
|
||||
import UnifiedSkillCard from '../components/skills/SkillCard';
|
||||
import { SKILL_CATEGORY_ORDER, type SkillCategory } from '../components/skills/skillCategories';
|
||||
import SkillCategoryFilter from '../components/skills/SkillCategoryFilter';
|
||||
@@ -41,7 +39,6 @@ import SkillSearchBar from '../components/skills/SkillSearchBar';
|
||||
import SkillsExplorerTab from '../components/skills/SkillsExplorerTab';
|
||||
import VoiceSetupModal from '../components/skills/VoiceSetupModal';
|
||||
import BetaBanner from '../components/ui/BetaBanner';
|
||||
import { useScreenIntelligenceSkillStatus } from '../features/screen-intelligence/useScreenIntelligenceSkillStatus';
|
||||
import { useVoiceSkillStatus } from '../features/voice/useVoiceSkillStatus';
|
||||
import { useChannelDefinitions } from '../hooks/useChannelDefinitions';
|
||||
import { useAgentReadyComposioToolkits, useComposioIntegrations } from '../lib/composio/hooks';
|
||||
@@ -398,18 +395,7 @@ const BUILT_IN_SKILLS: Array<{
|
||||
description: string;
|
||||
route: string;
|
||||
icon: React.ReactNode;
|
||||
}> = [
|
||||
// Hidden — not active yet. Uncomment to re-enable.
|
||||
// {
|
||||
// id: 'screen-intelligence',
|
||||
// title: 'Screen Intelligence',
|
||||
// description:
|
||||
// 'Capture windows, summarize what is on screen, and feed useful context into memory.',
|
||||
// route: '/settings/screen-intelligence',
|
||||
// icon: BUILT_IN_SKILL_ICONS.screenIntelligence,
|
||||
// },
|
||||
// text-autocomplete + voice-stt hidden per #717 (modals/status hooks retained for re-enable).
|
||||
];
|
||||
}> = [];
|
||||
|
||||
// ─── Item type for unified list ────────────────────────────────────────────────
|
||||
|
||||
@@ -455,7 +441,6 @@ type ConnectionsTab =
|
||||
| 'usage'
|
||||
| 'composio-key'
|
||||
| 'wallet'
|
||||
| 'screen-intelligence'
|
||||
| 'companion';
|
||||
|
||||
/**
|
||||
@@ -487,10 +472,6 @@ const INTELLIGENCE_HEADERS: Partial<Record<ConnectionsTab, { titleKey: string; d
|
||||
},
|
||||
search: { titleKey: 'settings.search.title', descKey: 'connections.header.search' },
|
||||
usage: { titleKey: 'settings.usage.title', descKey: 'settings.usage.menuDesc' },
|
||||
'screen-intelligence': {
|
||||
titleKey: 'pages.settings.features.screenAwareness',
|
||||
descKey: 'connections.header.screen',
|
||||
},
|
||||
companion: {
|
||||
titleKey: 'pages.settings.features.desktopCompanion',
|
||||
descKey: 'connections.header.companion',
|
||||
@@ -509,7 +490,6 @@ const INTELLIGENCE_TABS: ReadonlySet<ConnectionsTab> = new Set<ConnectionsTab>([
|
||||
'usage',
|
||||
'composio-key',
|
||||
'wallet',
|
||||
'screen-intelligence',
|
||||
'companion',
|
||||
]);
|
||||
|
||||
@@ -540,7 +520,6 @@ export default function Skills() {
|
||||
raw === 'usage' ||
|
||||
raw === 'composio-key' ||
|
||||
raw === 'wallet' ||
|
||||
raw === 'screen-intelligence' ||
|
||||
raw === 'companion'
|
||||
)
|
||||
return raw;
|
||||
@@ -609,9 +588,7 @@ export default function Skills() {
|
||||
const [composioModalToolkit, setComposioModalToolkit] = useState<ComposioToolkitMeta | null>(
|
||||
null
|
||||
);
|
||||
const [screenIntelligenceModalOpen, setScreenIntelligenceModalOpen] = useState(false);
|
||||
const [voiceModalOpen, setVoiceModalOpen] = useState(false);
|
||||
const screenIntelligenceStatus = useScreenIntelligenceSkillStatus();
|
||||
const voiceStatus = useVoiceSkillStatus();
|
||||
|
||||
const [toasts, setToasts] = useState<ToastNotification[]>([]);
|
||||
@@ -872,39 +849,8 @@ export default function Skills() {
|
||||
{items.map(item => {
|
||||
if (item.kind === 'builtin') {
|
||||
/* v8 ignore start -- BUILT_IN_SKILLS list is empty today; the per-id
|
||||
branches below are kept for re-enabling screen-intelligence /
|
||||
text-autocomplete / voice-stt and shouldn't drag the diff-coverage
|
||||
gate down while they're unreachable. */
|
||||
if (item.id === 'screen-intelligence') {
|
||||
return (
|
||||
<UnifiedSkillCard
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
title={item.name}
|
||||
description={item.description}
|
||||
statusLabel={screenIntelligenceStatus.statusLabel}
|
||||
statusColor={screenIntelligenceStatus.statusColor}
|
||||
ctaLabel={screenIntelligenceStatus.ctaLabel}
|
||||
ctaVariant={screenIntelligenceStatus.ctaVariant}
|
||||
testId={`skill-row-${item.id}`}
|
||||
ctaTestId={`skill-install-${item.id}`}
|
||||
onCtaClick={() => {
|
||||
if (screenIntelligenceStatus.platformUnsupported) {
|
||||
navigate(item.route!);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
screenIntelligenceStatus.connectionStatus === 'connected' ||
|
||||
screenIntelligenceStatus.connectionStatus === 'disconnected'
|
||||
) {
|
||||
navigate(item.route!);
|
||||
return;
|
||||
}
|
||||
setScreenIntelligenceModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
branches below are retained for the hidden voice skill without
|
||||
dragging the diff-coverage gate down while unreachable. */
|
||||
if (item.id === 'voice-stt') {
|
||||
return (
|
||||
<UnifiedSkillCard
|
||||
@@ -1020,11 +966,6 @@ export default function Skills() {
|
||||
// Desktop capabilities relocated from Settings → Connections.
|
||||
label: t('connections.groups.desktop'),
|
||||
items: [
|
||||
{
|
||||
value: 'screen-intelligence',
|
||||
label: t('pages.settings.features.screenAwareness'),
|
||||
icon: navIcon('M3 5h18v12H3zM8 21h8m-4-4v4'),
|
||||
},
|
||||
{
|
||||
value: 'companion',
|
||||
label: t('pages.settings.features.desktopCompanion'),
|
||||
@@ -1166,7 +1107,6 @@ export default function Skills() {
|
||||
{activeTab === 'search' && <SearchPanel />}
|
||||
{activeTab === 'usage' && <UsagePanel />}
|
||||
{activeTab === 'composio-key' && <ComposioPanel />}
|
||||
{activeTab === 'screen-intelligence' && <ScreenIntelligencePanel />}
|
||||
{activeTab === 'companion' && <CompanionPanel />}
|
||||
</SettingsLayoutProvider>
|
||||
</div>
|
||||
@@ -1433,13 +1373,6 @@ export default function Skills() {
|
||||
<ChannelSetupModal definition={channelModalDef} onClose={() => setChannelModalDef(null)} />
|
||||
)}
|
||||
|
||||
{screenIntelligenceModalOpen && (
|
||||
<ScreenIntelligenceSetupModal
|
||||
onClose={() => setScreenIntelligenceModalOpen(false)}
|
||||
initialStep={screenIntelligenceStatus.allPermissionsGranted ? 'enable' : 'permissions'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{voiceModalOpen && (
|
||||
<VoiceSetupModal onClose={() => setVoiceModalOpen(false)} skillStatus={voiceStatus} />
|
||||
)}
|
||||
|
||||
@@ -121,7 +121,7 @@ async function setupLayout(onboardingTasks: unknown = null) {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
isBootstrapping: false,
|
||||
isReady: true,
|
||||
|
||||
@@ -240,11 +240,7 @@ function normalizeSnapshot(
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: {
|
||||
screenIntelligence: result.runtime?.screenIntelligence ?? null,
|
||||
localAi: result.runtime?.localAi ?? null,
|
||||
service: result.runtime?.service ?? null,
|
||||
},
|
||||
runtime: { localAi: result.runtime?.localAi ?? null, service: result.runtime?.service ?? null },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ function makeSnapshot(overrides: {
|
||||
chatOnboardingCompleted: false,
|
||||
analyticsEnabled: false,
|
||||
localState: {},
|
||||
runtime: { screenIntelligence: null as never, localAi: null as never, service: null as never },
|
||||
runtime: { localAi: null as never, service: null as never },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ function resetCoreStateStore() {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
|
||||
@@ -37,7 +37,7 @@ function makeSnapshot(overrides: {
|
||||
chatOnboardingCompleted: false,
|
||||
analyticsEnabled: false,
|
||||
localState: {},
|
||||
runtime: { screenIntelligence: null as never, localAi: null as never, service: null as never },
|
||||
runtime: { localAi: null as never, service: null as never },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ function resetCoreStateStore() {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('parseCompanionStateChangedEvent', () => {
|
||||
});
|
||||
|
||||
it('accepts every valid state value', () => {
|
||||
for (const state of ['idle', 'listening', 'thinking', 'speaking', 'pointing', 'error']) {
|
||||
for (const state of ['idle', 'listening', 'thinking', 'speaking', 'error']) {
|
||||
const event = parseCompanionStateChangedEvent({ sessionId: 's', state });
|
||||
expect(event?.state).toBe(state);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { dispatchLocalAiMethod } from '../../lib/ai/localCoreAiMemory';
|
||||
import { CORE_RPC_TIMEOUT_MS } from '../../utils/config';
|
||||
import type { AccessibilityStatus, CommandResponse } from '../../utils/tauriCommands';
|
||||
import {
|
||||
callCoreRpc,
|
||||
classifyAuthExpiredReason,
|
||||
@@ -12,56 +11,6 @@ import {
|
||||
isThreadNotFoundCoreRpcError,
|
||||
} from '../coreRpcClient';
|
||||
|
||||
function sampleAccessibilityStatus(
|
||||
overrides: Partial<AccessibilityStatus> = {}
|
||||
): AccessibilityStatus {
|
||||
return {
|
||||
platform_supported: true,
|
||||
core_process: { pid: 4242, started_at_ms: 1712700000000 },
|
||||
permissions: {
|
||||
screen_recording: 'denied',
|
||||
accessibility: 'granted',
|
||||
input_monitoring: 'unknown',
|
||||
},
|
||||
features: { screen_monitoring: true },
|
||||
session: {
|
||||
active: false,
|
||||
started_at_ms: null,
|
||||
expires_at_ms: null,
|
||||
remaining_ms: null,
|
||||
ttl_secs: 300,
|
||||
panic_hotkey: 'Cmd+Shift+.',
|
||||
stop_reason: null,
|
||||
frames_in_memory: 0,
|
||||
last_capture_at_ms: null,
|
||||
last_context: null,
|
||||
vision_enabled: true,
|
||||
vision_state: 'idle',
|
||||
vision_queue_depth: 0,
|
||||
last_vision_at_ms: null,
|
||||
last_vision_summary: null,
|
||||
},
|
||||
config: {
|
||||
enabled: true,
|
||||
capture_policy: 'hybrid',
|
||||
policy_mode: 'all_except_blacklist',
|
||||
baseline_fps: 1,
|
||||
vision_enabled: true,
|
||||
session_ttl_secs: 300,
|
||||
panic_stop_hotkey: 'Cmd+Shift+.',
|
||||
autocomplete_enabled: true,
|
||||
use_vision_model: true,
|
||||
keep_screenshots: false,
|
||||
allowlist: [],
|
||||
denylist: [],
|
||||
},
|
||||
denylist: [],
|
||||
is_context_blocked: false,
|
||||
permission_check_process_path: '/tmp/openhuman-core-aarch64-apple-darwin',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(), isTauri: vi.fn(() => false) }));
|
||||
vi.mock('../../lib/ai/localCoreAiMemory', () => ({
|
||||
dispatchLocalAiMethod: vi.fn(async (_method: string) => ({ source: 'local-ai' })),
|
||||
@@ -88,50 +37,6 @@ describe('coreRpcClient', () => {
|
||||
expect(body.method).toBe('openhuman.auth_get_state');
|
||||
});
|
||||
|
||||
test('maps accessibility prefix to screen intelligence prefix', async () => {
|
||||
const fetchMock = vi.mocked(fetch);
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ jsonrpc: '2.0', id: 2, result: { accepted: true } }),
|
||||
} as Response);
|
||||
|
||||
await callCoreRpc({ method: 'openhuman.accessibility_status' });
|
||||
|
||||
const requestInit = fetchMock.mock.calls[0][1] as RequestInit;
|
||||
const body = JSON.parse(String(requestInit.body));
|
||||
expect(body.method).toBe('openhuman.screen_intelligence_status');
|
||||
});
|
||||
|
||||
test('fetches accessibility_status CommandResponse with permissions and process path', async () => {
|
||||
const fetchMock = vi.mocked(fetch);
|
||||
const status = sampleAccessibilityStatus({
|
||||
permission_check_process_path:
|
||||
'/Users/dev/openhuman/app/src-tauri/binaries/openhuman-core-aarch64-apple-darwin',
|
||||
});
|
||||
const envelope: CommandResponse<AccessibilityStatus> = {
|
||||
result: status,
|
||||
logs: ['screen intelligence status fetched'],
|
||||
};
|
||||
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ jsonrpc: '2.0', id: 99, result: envelope }),
|
||||
} as Response);
|
||||
|
||||
const out = await callCoreRpc<CommandResponse<AccessibilityStatus>>({
|
||||
method: 'openhuman.accessibility_status',
|
||||
});
|
||||
|
||||
expect(out.logs).toContain('screen intelligence status fetched');
|
||||
expect(out.result.permissions.screen_recording).toBe('denied');
|
||||
expect(out.result.permissions.accessibility).toBe('granted');
|
||||
expect(out.result.permissions.input_monitoring).toBe('unknown');
|
||||
expect(out.result.core_process?.pid).toBe(4242);
|
||||
expect(out.result.permission_check_process_path).toBe(
|
||||
'/Users/dev/openhuman/app/src-tauri/binaries/openhuman-core-aarch64-apple-darwin'
|
||||
);
|
||||
});
|
||||
|
||||
test('throws clean error when JSON-RPC error payload is returned', async () => {
|
||||
const fetchMock = vi.mocked(fetch);
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
@@ -241,10 +146,6 @@ describe('coreRpcClient', () => {
|
||||
['openhuman.update_memory_settings', 'openhuman.config_update_memory_settings'],
|
||||
['openhuman.update_model_settings', 'openhuman.inference_update_model_settings'],
|
||||
['openhuman.update_runtime_settings', 'openhuman.config_update_runtime_settings'],
|
||||
[
|
||||
'openhuman.update_screen_intelligence_settings',
|
||||
'openhuman.config_update_screen_intelligence_settings',
|
||||
],
|
||||
[
|
||||
'openhuman.workspace_onboarding_flag_exists',
|
||||
'openhuman.config_workspace_onboarding_flag_exists',
|
||||
|
||||
@@ -18,15 +18,6 @@ describe('rpcMethods catalog', () => {
|
||||
expect(normalizeRpcMethod('openhuman.auth.a.b.c')).toBe('openhuman.auth_a_b_c');
|
||||
});
|
||||
|
||||
test('transforms accessibility prefix to screen_intelligence prefix', () => {
|
||||
expect(normalizeRpcMethod('openhuman.accessibility_status')).toBe(
|
||||
'openhuman.screen_intelligence_status'
|
||||
);
|
||||
expect(normalizeRpcMethod('openhuman.accessibility_enable')).toBe(
|
||||
'openhuman.screen_intelligence_enable'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns unmapped or unrecognized methods unchanged', () => {
|
||||
expect(normalizeRpcMethod('openhuman.threads_list')).toBe('openhuman.threads_list');
|
||||
expect(normalizeRpcMethod('openhuman.unknown_method')).toBe('openhuman.unknown_method');
|
||||
@@ -37,8 +28,8 @@ describe('rpcMethods catalog', () => {
|
||||
test('trims whitespace and converts to lower case', () => {
|
||||
expect(normalizeRpcMethod(' OpenHuman.Auth.Login ')).toBe('openhuman.auth_login');
|
||||
expect(normalizeRpcMethod(' OPENHUMAN.GET_CONFIG ')).toBe(CORE_RPC_METHODS.configGet);
|
||||
expect(normalizeRpcMethod('OpenHuman.Accessibility_Status ')).toBe(
|
||||
'openhuman.screen_intelligence_status'
|
||||
expect(normalizeRpcMethod('OpenHuman.Unrecognized_Status ')).toBe(
|
||||
'openhuman.unrecognized_status'
|
||||
);
|
||||
expect(normalizeRpcMethod(' some_RANDOM_method ')).toBe('some_random_method');
|
||||
});
|
||||
@@ -137,10 +128,6 @@ describe('rpcMethods catalog', () => {
|
||||
path.resolve(__dirname, '../../../../src/openhuman/config/schemas/schema_defs.rs'),
|
||||
'utf8'
|
||||
),
|
||||
fs.readFileSync(
|
||||
path.resolve(__dirname, '../../../../src/openhuman/screen_intelligence/schemas.rs'),
|
||||
'utf8'
|
||||
),
|
||||
fs.readFileSync(
|
||||
path.resolve(__dirname, '../../../../src/openhuman/inference/provider/schemas.rs'),
|
||||
'utf8'
|
||||
@@ -188,23 +175,21 @@ describe('rpcMethods catalog', () => {
|
||||
// core.* methods (e.g. core.ping) are special dispatch methods, not in the schema catalog.
|
||||
if (!method.startsWith('openhuman.')) continue;
|
||||
const methodRoot = method.slice('openhuman.'.length);
|
||||
const namespace = methodRoot.startsWith('screen_intelligence_')
|
||||
? 'screen_intelligence'
|
||||
: methodRoot.startsWith('inference_')
|
||||
? 'inference'
|
||||
: methodRoot.startsWith('embeddings_')
|
||||
? 'embeddings'
|
||||
: methodRoot.startsWith('providers_')
|
||||
? 'providers'
|
||||
: methodRoot.startsWith('mcp_clients_')
|
||||
? 'mcp_clients'
|
||||
: methodRoot.startsWith('health_')
|
||||
? 'health'
|
||||
: methodRoot.startsWith('channels_')
|
||||
? 'channels'
|
||||
: methodRoot.startsWith('tool_registry_')
|
||||
? 'tool_registry'
|
||||
: 'config';
|
||||
const namespace = methodRoot.startsWith('inference_')
|
||||
? 'inference'
|
||||
: methodRoot.startsWith('embeddings_')
|
||||
? 'embeddings'
|
||||
: methodRoot.startsWith('providers_')
|
||||
? 'providers'
|
||||
: methodRoot.startsWith('mcp_clients_')
|
||||
? 'mcp_clients'
|
||||
: methodRoot.startsWith('health_')
|
||||
? 'health'
|
||||
: methodRoot.startsWith('channels_')
|
||||
? 'channels'
|
||||
: methodRoot.startsWith('tool_registry_')
|
||||
? 'tool_registry'
|
||||
: 'config';
|
||||
const fnName = methodRoot.slice(`${namespace}_`.length);
|
||||
expect(schemaSources).toContain(`namespace: "${namespace}"`);
|
||||
expect(schemaSources).toContain(`function: "${fnName}"`);
|
||||
|
||||
@@ -23,7 +23,6 @@ const COMPANION_STATES: ReadonlySet<string> = new Set([
|
||||
'listening',
|
||||
'thinking',
|
||||
'speaking',
|
||||
'pointing',
|
||||
'error',
|
||||
]);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function makeSnapshotResult(overrides: Record<string, unknown> = {}) {
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: {}, localAi: {}, service: {} },
|
||||
runtime: { localAi: {}, service: {} },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { User } from '../types/api';
|
||||
import type { TeamInvite, TeamMember, TeamWithRole } from '../types/team';
|
||||
import type { AccessibilityStatus } from '../utils/tauriCommands/accessibility';
|
||||
import type { LocalAiStatus } from '../utils/tauriCommands/localAi';
|
||||
import type { ServiceStatus } from '../utils/tauriCommands/service';
|
||||
import { callCoreRpc } from './coreRpcClient';
|
||||
@@ -57,11 +56,7 @@ interface AppStateSnapshotResult {
|
||||
keyringConsent?: KeyringConsentPreference | null;
|
||||
};
|
||||
keyringStatus?: KeyringStatus;
|
||||
runtime: {
|
||||
screenIntelligence: AccessibilityStatus;
|
||||
localAi: LocalAiStatus;
|
||||
service: ServiceStatus;
|
||||
};
|
||||
runtime: { localAi: LocalAiStatus; service: ServiceStatus };
|
||||
/**
|
||||
* Process + component health, folded into this snapshot (#daemon-poll-fold)
|
||||
* so the daemon-health store hydrates from the same poll instead of a second
|
||||
|
||||
@@ -28,7 +28,6 @@ export const CORE_RPC_METHODS = {
|
||||
configUpdateModelSettings: 'openhuman.config_update_model_settings',
|
||||
configUpdateRuntimeSettings: 'openhuman.config_update_runtime_settings',
|
||||
configUpdateSandboxSettings: 'openhuman.config_update_sandbox_settings',
|
||||
configUpdateScreenIntelligenceSettings: 'openhuman.config_update_screen_intelligence_settings',
|
||||
configWorkspaceOnboardingFlagExists: 'openhuman.config_workspace_onboarding_flag_exists',
|
||||
configWorkspaceOnboardingFlagSet: 'openhuman.config_workspace_onboarding_flag_set',
|
||||
corePing: 'core.ping',
|
||||
@@ -54,7 +53,6 @@ export const CORE_RPC_METHODS = {
|
||||
inferenceUpdateModelSettings: 'openhuman.inference_update_model_settings',
|
||||
inferenceWhisperInstallStatus: 'openhuman.inference_whisper_install_status',
|
||||
providersListModels: 'openhuman.inference_list_models',
|
||||
screenIntelligenceStatus: 'openhuman.screen_intelligence_status',
|
||||
embeddingsGetSettings: 'openhuman.embeddings_get_settings',
|
||||
embeddingsUpdateSettings: 'openhuman.embeddings_update_settings',
|
||||
embeddingsSetApiKey: 'openhuman.embeddings_set_api_key',
|
||||
@@ -104,8 +102,6 @@ export const LEGACY_METHOD_ALIASES: Record<string, CoreRpcMethod> = {
|
||||
'openhuman.update_memory_settings': CORE_RPC_METHODS.configUpdateMemorySettings,
|
||||
'openhuman.update_model_settings': CORE_RPC_METHODS.inferenceUpdateModelSettings,
|
||||
'openhuman.update_runtime_settings': CORE_RPC_METHODS.configUpdateRuntimeSettings,
|
||||
'openhuman.update_screen_intelligence_settings':
|
||||
CORE_RPC_METHODS.configUpdateScreenIntelligenceSettings,
|
||||
'openhuman.workspace_onboarding_flag_exists':
|
||||
CORE_RPC_METHODS.configWorkspaceOnboardingFlagExists,
|
||||
'openhuman.workspace_onboarding_flag_set': CORE_RPC_METHODS.configWorkspaceOnboardingFlagSet,
|
||||
@@ -154,9 +150,5 @@ export function normalizeRpcMethod(method: string): string {
|
||||
return `openhuman.auth_${normalized.slice('openhuman.auth.'.length).split('.').join('_')}`;
|
||||
}
|
||||
|
||||
if (normalized.startsWith('openhuman.accessibility_')) {
|
||||
return normalized.replace('openhuman.accessibility_', 'openhuman.screen_intelligence_');
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function makeCoreState(token: string | null, userId: string | null = null): Core
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, service: null },
|
||||
runtime: { localAi: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
|
||||
@@ -94,13 +94,7 @@ describe('companionSlice', () => {
|
||||
});
|
||||
|
||||
it('setConfig stores config object', () => {
|
||||
const config = {
|
||||
hotkey: 'ctrl+space',
|
||||
activation_mode: 'push',
|
||||
ttl_secs: 3600,
|
||||
capture_screen: true,
|
||||
include_app_context: true,
|
||||
};
|
||||
const config = { hotkey: 'ctrl+space', activation_mode: 'push', ttl_secs: 3600 };
|
||||
const state = companionReducer(initialState, setConfig(config));
|
||||
expect(state.config).toEqual(config);
|
||||
});
|
||||
@@ -121,13 +115,7 @@ describe('companionSlice', () => {
|
||||
sessionActive: true,
|
||||
state: 'speaking',
|
||||
sessionId: 'sess-1',
|
||||
config: {
|
||||
hotkey: 'ctrl+space',
|
||||
activation_mode: 'push',
|
||||
ttl_secs: 3600,
|
||||
capture_screen: true,
|
||||
include_app_context: true,
|
||||
},
|
||||
config: { hotkey: 'ctrl+space', activation_mode: 'push', ttl_secs: 3600 },
|
||||
lastError: 'some error',
|
||||
};
|
||||
const state = companionReducer(active, clearSession());
|
||||
@@ -151,13 +139,7 @@ describe('companion selectors', () => {
|
||||
sessionActive: true,
|
||||
state: 'speaking' as const,
|
||||
sessionId: 'sess-1',
|
||||
config: {
|
||||
hotkey: 'ctrl+space',
|
||||
activation_mode: 'push',
|
||||
ttl_secs: 3600,
|
||||
capture_screen: true,
|
||||
include_app_context: true,
|
||||
},
|
||||
config: { hotkey: 'ctrl+space', activation_mode: 'push', ttl_secs: 3600 },
|
||||
lastError: 'err',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,15 +5,13 @@ import { resetUserScopedState } from './resetActions';
|
||||
// ── Types matching the Tauri-side companion module ───────────────────
|
||||
|
||||
/** Matches `CompanionState` enum (serde rename_all = "snake_case"). */
|
||||
export type CompanionState = 'idle' | 'listening' | 'thinking' | 'speaking' | 'pointing' | 'error';
|
||||
export type CompanionState = 'idle' | 'listening' | 'thinking' | 'speaking' | 'error';
|
||||
|
||||
/** Matches `CompanionConfig` struct. */
|
||||
export interface CompanionConfig {
|
||||
hotkey: string;
|
||||
activation_mode: string;
|
||||
ttl_secs: number;
|
||||
capture_screen: boolean;
|
||||
include_app_context: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Shared Vitest mocks for screen-intelligence / voice status hooks.
|
||||
* Shared Vitest mocks for voice status hooks.
|
||||
* Import this module first in Skills page tests so `Skills` does not require `CoreStateProvider`.
|
||||
*/
|
||||
import { vi } from 'vitest';
|
||||
@@ -14,14 +14,6 @@ const offlineStatusBase = {
|
||||
ctaVariant: 'sage' as const,
|
||||
};
|
||||
|
||||
vi.mock('../features/screen-intelligence/useScreenIntelligenceSkillStatus', () => ({
|
||||
useScreenIntelligenceSkillStatus: () => ({
|
||||
...offlineStatusBase,
|
||||
allPermissionsGranted: false,
|
||||
platformUnsupported: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../features/voice/useVoiceSkillStatus', () => ({
|
||||
useVoiceSkillStatus: () => ({
|
||||
...offlineStatusBase,
|
||||
|
||||
@@ -205,12 +205,6 @@ describe('formatTimelineEntry', () => {
|
||||
).toEqual({ title: 'Git diff', detail: 'diff --stat' });
|
||||
});
|
||||
|
||||
it('formats screenshot as a simple label', () => {
|
||||
expect(formatTimelineEntry(entry({ name: 'screenshot' }))).toEqual({
|
||||
title: 'Taking screenshot',
|
||||
});
|
||||
});
|
||||
|
||||
it('formats glob with pattern detail', () => {
|
||||
expect(
|
||||
formatTimelineEntry(
|
||||
@@ -247,7 +241,6 @@ describe('formatToolName', () => {
|
||||
expect(formatToolName('edit')).toBe('Editing file');
|
||||
expect(formatToolName('grep')).toBe('Searching code');
|
||||
expect(formatToolName('git_operations')).toBe('Git operation');
|
||||
expect(formatToolName('screenshot')).toBe('Taking screenshot');
|
||||
expect(formatToolName('lsp')).toBe('Code intelligence');
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ export type CapabilityCategory =
|
||||
| 'team'
|
||||
| 'settings'
|
||||
| 'auth'
|
||||
| 'screen_intelligence'
|
||||
| 'channels'
|
||||
| 'automation';
|
||||
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
/**
|
||||
* Accessibility and Screen Intelligence commands.
|
||||
*/
|
||||
import { callCoreRpc } from '../../services/coreRpcClient';
|
||||
import { CommandResponse, isTauri } from './common';
|
||||
|
||||
export type AccessibilityPermissionState = 'granted' | 'denied' | 'unknown' | 'unsupported';
|
||||
export type AccessibilityPermissionKind =
|
||||
| 'screen_recording'
|
||||
| 'accessibility'
|
||||
| 'input_monitoring'
|
||||
| 'microphone';
|
||||
|
||||
export interface AccessibilityPermissionStatus {
|
||||
screen_recording: AccessibilityPermissionState;
|
||||
accessibility: AccessibilityPermissionState;
|
||||
input_monitoring: AccessibilityPermissionState;
|
||||
/**
|
||||
* Microphone permission. The Rust `PermissionStatus` always serializes this,
|
||||
* but it's optional here so existing object literals (e.g. screen-intelligence
|
||||
* status fixtures) that predate the desktop-agent panel still typecheck.
|
||||
*/
|
||||
microphone?: AccessibilityPermissionState;
|
||||
}
|
||||
|
||||
export interface AccessibilityFeatures {
|
||||
screen_monitoring: boolean;
|
||||
}
|
||||
|
||||
export interface AccessibilitySessionStatus {
|
||||
active: boolean;
|
||||
started_at_ms: number | null;
|
||||
expires_at_ms: number | null;
|
||||
remaining_ms: number | null;
|
||||
ttl_secs: number;
|
||||
panic_hotkey: string;
|
||||
stop_reason: string | null;
|
||||
frames_in_memory: number;
|
||||
last_capture_at_ms: number | null;
|
||||
last_context: string | null;
|
||||
vision_enabled: boolean;
|
||||
vision_state: string;
|
||||
vision_queue_depth: number;
|
||||
last_vision_at_ms: number | null;
|
||||
last_vision_summary: string | null;
|
||||
}
|
||||
|
||||
export interface AccessibilityConfig {
|
||||
enabled: boolean;
|
||||
capture_policy: string;
|
||||
policy_mode: 'all_except_blacklist' | 'whitelist_only' | string;
|
||||
baseline_fps: number;
|
||||
vision_enabled: boolean;
|
||||
session_ttl_secs: number;
|
||||
panic_stop_hotkey: string;
|
||||
autocomplete_enabled: boolean;
|
||||
use_vision_model: boolean;
|
||||
keep_screenshots: boolean;
|
||||
allowlist: string[];
|
||||
denylist: string[];
|
||||
}
|
||||
|
||||
export interface AccessibilityCoreProcessStatus {
|
||||
pid: number;
|
||||
started_at_ms: number;
|
||||
}
|
||||
|
||||
export interface AccessibilityStatus {
|
||||
platform_supported: boolean;
|
||||
permissions: AccessibilityPermissionStatus;
|
||||
features: AccessibilityFeatures;
|
||||
session: AccessibilitySessionStatus;
|
||||
config: AccessibilityConfig;
|
||||
denylist: string[];
|
||||
is_context_blocked: boolean;
|
||||
/** Absolute path of the core binary; macOS TCC applies to this executable. */
|
||||
permission_check_process_path?: string | null;
|
||||
/** Identity of the core process currently serving RPC requests. */
|
||||
core_process?: AccessibilityCoreProcessStatus | null;
|
||||
}
|
||||
|
||||
export interface AccessibilityStartSessionParams {
|
||||
consent: boolean;
|
||||
ttl_secs?: number;
|
||||
screen_monitoring?: boolean;
|
||||
}
|
||||
|
||||
export interface AccessibilityStopSessionParams {
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface AccessibilityCaptureFrame {
|
||||
captured_at_ms: number;
|
||||
reason: string;
|
||||
app_name: string | null;
|
||||
window_title: string | null;
|
||||
image_ref?: string | null;
|
||||
}
|
||||
|
||||
export interface AccessibilityCaptureNowResult {
|
||||
accepted: boolean;
|
||||
frame: AccessibilityCaptureFrame | null;
|
||||
}
|
||||
|
||||
export interface AccessibilityInputActionParams {
|
||||
action: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
button?: string;
|
||||
text?: string;
|
||||
key?: string;
|
||||
modifiers?: string[];
|
||||
}
|
||||
|
||||
export interface AccessibilityInputActionResult {
|
||||
accepted: boolean;
|
||||
blocked: boolean;
|
||||
reason: string | null;
|
||||
}
|
||||
|
||||
export interface AccessibilityAutocompleteSuggestion {
|
||||
value: string;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface AccessibilityAutocompleteSuggestParams {
|
||||
context?: string;
|
||||
max_results?: number;
|
||||
}
|
||||
|
||||
export interface AccessibilityAutocompleteSuggestResult {
|
||||
suggestions: AccessibilityAutocompleteSuggestion[];
|
||||
}
|
||||
|
||||
export interface AccessibilityAutocompleteCommitParams {
|
||||
suggestion: string;
|
||||
}
|
||||
|
||||
export interface AccessibilityAutocompleteCommitResult {
|
||||
committed: boolean;
|
||||
}
|
||||
|
||||
export interface AccessibilityVisionSummary {
|
||||
id: string;
|
||||
captured_at_ms: number;
|
||||
app_name: string | null;
|
||||
window_title: string | null;
|
||||
ui_state: string;
|
||||
key_text: string;
|
||||
actionable_notes: string;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface AccessibilityVisionRecentResult {
|
||||
summaries: AccessibilityVisionSummary[];
|
||||
}
|
||||
|
||||
export interface AccessibilityVisionFlushResult {
|
||||
accepted: boolean;
|
||||
summary: AccessibilityVisionSummary | null;
|
||||
}
|
||||
|
||||
export interface CaptureTestContextInfo {
|
||||
app_name: string | null;
|
||||
window_title: string | null;
|
||||
bounds_x: number | null;
|
||||
bounds_y: number | null;
|
||||
bounds_width: number | null;
|
||||
bounds_height: number | null;
|
||||
}
|
||||
|
||||
export interface CaptureTestResult {
|
||||
ok: boolean;
|
||||
capture_mode: string;
|
||||
context: CaptureTestContextInfo | null;
|
||||
image_ref: string | null;
|
||||
bytes_estimate: number | null;
|
||||
error: string | null;
|
||||
timing_ms: number;
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityStatus(): Promise<
|
||||
CommandResponse<AccessibilityStatus>
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityStatus>>({
|
||||
method: 'openhuman.accessibility_status',
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityRequestPermissions(): Promise<
|
||||
CommandResponse<AccessibilityPermissionStatus>
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityPermissionStatus>>({
|
||||
method: 'openhuman.accessibility_request_permissions',
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityRequestPermission(
|
||||
permission: AccessibilityPermissionKind
|
||||
): Promise<CommandResponse<AccessibilityPermissionStatus>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityPermissionStatus>>({
|
||||
method: 'openhuman.accessibility_request_permission',
|
||||
params: { permission },
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityStartSession(
|
||||
params: AccessibilityStartSessionParams
|
||||
): Promise<CommandResponse<AccessibilitySessionStatus>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilitySessionStatus>>({
|
||||
method: 'openhuman.accessibility_start_session',
|
||||
params,
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityStopSession(
|
||||
params?: AccessibilityStopSessionParams
|
||||
): Promise<CommandResponse<AccessibilitySessionStatus>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilitySessionStatus>>({
|
||||
method: 'openhuman.accessibility_stop_session',
|
||||
params: params ?? {},
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityCaptureNow(): Promise<
|
||||
CommandResponse<AccessibilityCaptureNowResult>
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityCaptureNowResult>>({
|
||||
method: 'openhuman.accessibility_capture_now',
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityInputAction(
|
||||
params: AccessibilityInputActionParams
|
||||
): Promise<CommandResponse<AccessibilityInputActionResult>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityInputActionResult>>({
|
||||
method: 'openhuman.accessibility_input_action',
|
||||
params,
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityAutocompleteSuggest(
|
||||
params?: AccessibilityAutocompleteSuggestParams
|
||||
): Promise<CommandResponse<AccessibilityAutocompleteSuggestResult>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityAutocompleteSuggestResult>>({
|
||||
method: 'openhuman.accessibility_autocomplete_suggest',
|
||||
params: params ?? {},
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityAutocompleteCommit(
|
||||
params: AccessibilityAutocompleteCommitParams
|
||||
): Promise<CommandResponse<AccessibilityAutocompleteCommitResult>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityAutocompleteCommitResult>>({
|
||||
method: 'openhuman.accessibility_autocomplete_commit',
|
||||
params,
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityVisionRecent(
|
||||
limit?: number
|
||||
): Promise<CommandResponse<AccessibilityVisionRecentResult>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityVisionRecentResult>>({
|
||||
method: 'openhuman.accessibility_vision_recent',
|
||||
params: { limit },
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanAccessibilityVisionFlush(): Promise<
|
||||
CommandResponse<AccessibilityVisionFlushResult>
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<AccessibilityVisionFlushResult>>({
|
||||
method: 'openhuman.accessibility_vision_flush',
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanScreenIntelligenceCaptureTest(): Promise<
|
||||
CommandResponse<CaptureTestResult>
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<CaptureTestResult>>({
|
||||
method: 'openhuman.screen_intelligence_capture_test',
|
||||
serviceManaged: true,
|
||||
});
|
||||
}
|
||||
@@ -153,19 +153,6 @@ export interface BrowserSettingsUpdate {
|
||||
backend?: 'agent_browser' | 'playwright' | 'rust_native' | 'computer_use' | 'auto' | null;
|
||||
}
|
||||
|
||||
export interface ScreenIntelligenceSettingsUpdate {
|
||||
enabled?: boolean | null;
|
||||
capture_policy?: string | null;
|
||||
policy_mode?: 'all_except_blacklist' | 'whitelist_only' | null;
|
||||
baseline_fps?: number | null;
|
||||
vision_enabled?: boolean | null;
|
||||
autocomplete_enabled?: boolean | null;
|
||||
use_vision_model?: boolean | null;
|
||||
keep_screenshots?: boolean | null;
|
||||
allowlist?: string[] | null;
|
||||
denylist?: string[] | null;
|
||||
}
|
||||
|
||||
export interface LocalAiSettingsUpdate {
|
||||
runtime_enabled?: boolean | null;
|
||||
/**
|
||||
@@ -454,18 +441,6 @@ export async function openhumanUpdateBrowserSettings(
|
||||
});
|
||||
}
|
||||
|
||||
export async function openhumanUpdateScreenIntelligenceSettings(
|
||||
update: ScreenIntelligenceSettingsUpdate
|
||||
): Promise<CommandResponse<ConfigSnapshot>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await callCoreRpc<CommandResponse<ConfigSnapshot>>({
|
||||
method: CORE_RPC_METHODS.configUpdateScreenIntelligenceSettings,
|
||||
params: update,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent access mode (autonomy / filesystem permissions) ───────────────────
|
||||
|
||||
export type AutonomyLevel = 'readonly' | 'supervised' | 'full';
|
||||
|
||||
@@ -16,7 +16,6 @@ export * from './cron';
|
||||
export * from './taskSources';
|
||||
export * from './heartbeat';
|
||||
export * from './service';
|
||||
export * from './accessibility';
|
||||
export * from './voice';
|
||||
export * from './aboutApp';
|
||||
export * from './workspacePaths';
|
||||
|
||||
@@ -56,14 +56,6 @@ export const TOOL_CATALOG: ToolDefinition[] = [
|
||||
},
|
||||
|
||||
// Vision
|
||||
{
|
||||
id: 'screenshot',
|
||||
displayName: 'Screenshot',
|
||||
description: 'Capture screenshots of your screen.',
|
||||
category: 'Vision',
|
||||
defaultEnabled: true,
|
||||
rustToolNames: ['screenshot'],
|
||||
},
|
||||
{
|
||||
id: 'image_info',
|
||||
displayName: 'Image Analysis',
|
||||
@@ -155,7 +147,7 @@ export const TOOL_CATALOG: ToolDefinition[] = [
|
||||
export const CATEGORY_DESCRIPTIONS: Record<ToolCategory, string> = {
|
||||
System: 'Shell access and version control',
|
||||
Files: 'Read and write files on disk',
|
||||
Vision: 'Screen capture and image analysis',
|
||||
Vision: 'Image analysis',
|
||||
Web: 'Browser, HTTP, and web search',
|
||||
Memory: 'Persistent recall for the AI',
|
||||
Automation: 'Cron jobs and scheduled tasks',
|
||||
|
||||
@@ -35,7 +35,6 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
||||
git_operations: 'Git operation',
|
||||
browser: 'Browsing',
|
||||
browser_open: 'Opening browser',
|
||||
screenshot: 'Taking screenshot',
|
||||
image_info: 'Analyzing image',
|
||||
install_tool: 'Installing tool',
|
||||
lsp: 'Code intelligence',
|
||||
@@ -115,7 +114,6 @@ const CLIENT_KNOWN_TOOLS = new Set<string>([
|
||||
'git_operations',
|
||||
'browser',
|
||||
'browser_open',
|
||||
'screenshot',
|
||||
'image_info',
|
||||
'install_tool',
|
||||
'lsp',
|
||||
@@ -189,7 +187,6 @@ const TOOL_CATEGORIES: Record<string, ToolCategory> = {
|
||||
curl: 'fetch',
|
||||
browser: 'browse',
|
||||
browser_open: 'browse',
|
||||
screenshot: 'browse',
|
||||
};
|
||||
|
||||
/** Categorize a (possibly `subagent:`-prefixed) tool name for grouping/icons. */
|
||||
@@ -545,9 +542,6 @@ function formatToolDetail(
|
||||
return { title: host ? `Browsing ${host}` : 'Browsing' };
|
||||
}
|
||||
|
||||
case 'screenshot':
|
||||
return { title: 'Taking screenshot' };
|
||||
|
||||
case 'image_info':
|
||||
return { title: 'Analyzing image' };
|
||||
|
||||
|
||||
@@ -467,14 +467,6 @@ describe('Mega flow — login + Gmail OAuth + Composio in one session', () => {
|
||||
// can reliably emit nested tool-call chains — neither is present.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Scenario 9 — Accessibility permission flow.
|
||||
// SKIPPED: No `openhuman.accessibility_*` RPC surface exists in the Rust
|
||||
// controller registry. The `accessibility` domain name appears only in
|
||||
// directory listings; it has no `schemas.rs` with registered controllers.
|
||||
// If a future PR adds accessibility controllers, add scenarios here.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Scenario 10 — Account switch + restore.
|
||||
// Login as user A, create a thread, reset, login as user B, then re-login
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { browser, expect } from '@wdio/globals';
|
||||
|
||||
import { waitForApp } from '../helpers/app-helpers';
|
||||
import {
|
||||
clickButton,
|
||||
dumpAccessibilityTree,
|
||||
textExists,
|
||||
waitForText,
|
||||
} from '../helpers/element-helpers';
|
||||
import { isTauriDriver } from '../helpers/platform';
|
||||
import { resetApp } from '../helpers/reset-app';
|
||||
import { navigateViaHash } from '../helpers/shared-flows';
|
||||
import { clearRequestLog, startMockServer, stopMockServer } from '../mock-server';
|
||||
|
||||
function stepLog(message: string, context?: unknown): void {
|
||||
const stamp = new Date().toISOString();
|
||||
if (context === undefined) {
|
||||
console.log(`[ScreenIntelligenceE2E][${stamp}] ${message}`);
|
||||
return;
|
||||
}
|
||||
console.log(`[ScreenIntelligenceE2E][${stamp}] ${message}`, JSON.stringify(context, null, 2));
|
||||
}
|
||||
|
||||
async function waitForCaptureOutcome(timeoutMs = 20_000): Promise<'success' | 'failure'> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
if (
|
||||
(await textExists('Success')) &&
|
||||
((await textExists('windowed')) || (await textExists('fullscreen')))
|
||||
) {
|
||||
return 'success';
|
||||
}
|
||||
if (
|
||||
(await textExists('Failed')) ||
|
||||
(await textExists('screen recording permission is not granted')) ||
|
||||
(await textExists('screen capture is unsupported on this platform')) ||
|
||||
(await textExists('screen capture failed'))
|
||||
) {
|
||||
return 'failure';
|
||||
}
|
||||
await browser.pause(500);
|
||||
}
|
||||
throw new Error('Timed out waiting for screen capture outcome');
|
||||
}
|
||||
|
||||
describe('Screen Intelligence', () => {
|
||||
before(async function () {
|
||||
stepLog('Starting Screen Intelligence E2E');
|
||||
await startMockServer();
|
||||
await waitForApp();
|
||||
await resetApp('e2e-screen-intelligence-user');
|
||||
clearRequestLog();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await stopMockServer();
|
||||
});
|
||||
|
||||
it('opens the Screen Intelligence settings route', async function () {
|
||||
if (!isTauriDriver()) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the settings shell first so nested routes are available.
|
||||
await browser.execute(() => {
|
||||
window.location.hash = '/settings';
|
||||
});
|
||||
await browser.pause(2_000);
|
||||
|
||||
// Now navigate to the nested screen-intelligence route.
|
||||
// Retry if the hash bounces (lazy component load may cause redirect).
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
await browser.execute(() => {
|
||||
window.location.hash = '/settings/screen-intelligence';
|
||||
});
|
||||
await browser.pause(3_000);
|
||||
const h = String(await browser.execute(() => window.location.hash));
|
||||
if (h.includes('/settings/screen-intelligence')) break;
|
||||
stepLog(`hash bounce attempt ${attempt}`, { hash: h });
|
||||
}
|
||||
|
||||
const currentHash = await browser.execute(() => window.location.hash);
|
||||
stepLog('Navigated to screen intelligence route', { currentHash });
|
||||
|
||||
// The panel renders the "Screen Awareness" title on every platform.
|
||||
// The "Permissions" sub-section is only rendered when
|
||||
// status.platform_supported is true (macOS) — on Linux/Windows the
|
||||
// section is gated out by ScreenIntelligencePanel, so don't assert
|
||||
// on it here.
|
||||
await waitForText('Screen Awareness', 15_000);
|
||||
});
|
||||
|
||||
it('triggers capture test and reaches a stable UI outcome', async function () {
|
||||
if (!isTauriDriver()) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// The capture test UI lives in the debug panel, not the main panel.
|
||||
await navigateViaHash('/settings/screen-awareness-debug');
|
||||
await waitForText('Screen Awareness', 10_000);
|
||||
|
||||
// The Expand button opens the Debug & Diagnostics section.
|
||||
// If not present, the debug panel may already be expanded.
|
||||
if (await textExists('Expand')) {
|
||||
await clickButton('Expand', 10_000);
|
||||
}
|
||||
await waitForText('Capture test', 10_000);
|
||||
await clickButton('Test capture', 10_000);
|
||||
|
||||
const outcome = await waitForCaptureOutcome();
|
||||
stepLog('Capture test outcome', { outcome });
|
||||
|
||||
if (outcome === 'success') {
|
||||
const hasPreviewImage = await browser.execute(() => {
|
||||
const img = document.querySelector('img[alt="Capture test result"]');
|
||||
return !!img && !!img.getAttribute('src');
|
||||
});
|
||||
expect(hasPreviewImage).toBe(true);
|
||||
expect((await textExists('windowed')) || (await textExists('fullscreen'))).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasFailureGuidance =
|
||||
(await textExists('Failed')) ||
|
||||
(await textExists('screen recording permission is not granted')) ||
|
||||
(await textExists('screen capture is unsupported on this platform')) ||
|
||||
(await textExists('screen capture failed'));
|
||||
if (!hasFailureGuidance) {
|
||||
const tree = await dumpAccessibilityTree();
|
||||
stepLog('Capture failure outcome missing expected guidance', { tree: tree.slice(0, 4000) });
|
||||
}
|
||||
expect(hasFailureGuidance).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -73,16 +73,25 @@ describe('Settings - Feature Preferences', function () {
|
||||
await stopMockServer();
|
||||
});
|
||||
|
||||
it('renders the screen-awareness settings route', async () => {
|
||||
// The combined "Features" hub was retired: screen-awareness, notifications,
|
||||
// and tools are now independent sidebar entries. The legacy /settings/features
|
||||
// slug redirects to /settings/screen-intelligence (see Settings.tsx), which
|
||||
// renders the Screen Awareness panel.
|
||||
await navigateViaHash('/settings/features');
|
||||
it('falls through removed feature routes to settings home', async () => {
|
||||
for (const route of [
|
||||
'/settings/features',
|
||||
'/settings/screen-intelligence',
|
||||
'/settings/screen-awareness-debug',
|
||||
]) {
|
||||
await navigateViaHash(route);
|
||||
await browser.waitUntil(
|
||||
async () => {
|
||||
const hash = await browser.execute(() => window.location.hash);
|
||||
return hash === '#/settings' || hash === '#/settings/account';
|
||||
},
|
||||
{ timeout: 15_000, timeoutMsg: `${route} did not normalize to the settings index` }
|
||||
);
|
||||
|
||||
// ScreenIntelligencePanel renders SettingsSection title
|
||||
// t('settings.features.screenAwareness') = 'Screen awareness'.
|
||||
await waitForText('Screen awareness', 15_000);
|
||||
const bodyText = await browser.$('body').getText();
|
||||
expect(bodyText).not.toContain('Screen Intelligence');
|
||||
expect(bodyText).not.toContain('Screen Awareness Debug');
|
||||
}
|
||||
});
|
||||
|
||||
it('persists the default messaging channel through redux state', async () => {
|
||||
|
||||
@@ -29,11 +29,10 @@ const USER_ID = 'e2e-tool-browser';
|
||||
* over JSON-RPC. Plus: the mock backend correctly records arbitrary HTTP
|
||||
* requests (proving the side-channel browser-automation flows would emit
|
||||
* against the mocked services is intact).
|
||||
* - 7.1.2 — `BrowserTool::parameters_schema` enumerates the automation
|
||||
* surface (open / snapshot / click / fill / type / get_text / etc.).
|
||||
* Asserting that `tools_agent`'s tool scope is wildcard (which would
|
||||
* surface `browser` to the LLM) ensures the schema-driven tool surface is
|
||||
* intact for the agent path.
|
||||
* - 7.1.2 — the live built-in tool catalog contains `browser` and advertises
|
||||
* its DOM/accessibility `snapshot` action without the removed screenshot
|
||||
* action. The exact action-enum schema is pinned by the BrowserTool Rust
|
||||
* unit test.
|
||||
*
|
||||
* Future: when the harness gains a deterministic mock-LLM that emits
|
||||
* structured `tool_calls`, the `it.skip` block below can flip into a real
|
||||
@@ -63,6 +62,15 @@ interface ListDefinitionsResult {
|
||||
definitions?: AgentDef[];
|
||||
}
|
||||
|
||||
interface AgentToolInfo {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface AvailableToolsResult {
|
||||
tools?: AgentToolInfo[];
|
||||
}
|
||||
|
||||
describe('System tools — Browser (open URL + automation registry)', () => {
|
||||
before(async function () {
|
||||
// resetApp bring-up can run ~25-30s and race the default 30s Mocha hook
|
||||
@@ -71,6 +79,14 @@ describe('System tools — Browser (open URL + automation registry)', () => {
|
||||
await startMockServer();
|
||||
await waitForApp();
|
||||
await resetApp(USER_ID);
|
||||
const browserSettings = await callOpenhumanRpc('openhuman.config_update_browser_settings', {
|
||||
enabled: true,
|
||||
});
|
||||
expect(browserSettings.ok).toBe(true);
|
||||
const toolPreferences = await callOpenhumanRpc('openhuman.app_state_update_local_state', {
|
||||
onboardingTasks: { enabledTools: ['browser'] },
|
||||
});
|
||||
expect(toolPreferences.ok).toBe(true);
|
||||
clearRequestLog();
|
||||
});
|
||||
|
||||
@@ -137,30 +153,24 @@ describe('System tools — Browser (open URL + automation registry)', () => {
|
||||
expect(Array.isArray(log)).toBe(true);
|
||||
});
|
||||
|
||||
it('7.1.2 browser-automation registry surface is reachable via the agent registry', async () => {
|
||||
// BrowserTool's parameters_schema enumerates 22 actions (open, snapshot,
|
||||
// click, fill, type, get_text, screenshot, …). Asserting tools_agent's
|
||||
// wildcard scope is present means the LLM-facing tool surface that
|
||||
// would expose this schema to a model is intact. The schema content
|
||||
// itself is unit-tested in `browser_tests.rs::browser_tool_schema_*`.
|
||||
const list = await callOpenhumanRpc<ListDefinitionsResult>(
|
||||
'openhuman.agent_list_definitions',
|
||||
it('7.1.2 live BrowserTool catalog advertises DOM snapshots without screenshots', async () => {
|
||||
// `agent_registry_available_tools` builds its response from the wildcard
|
||||
// tools-catalog agent's live `tool_specs()`, so this checks the actual
|
||||
// BrowserTool metadata exposed to agents rather than merely finding an
|
||||
// agent definition that might be able to resolve it. The exact
|
||||
// parameters-schema action enum is pinned by
|
||||
// `browser_tests.rs::browser_tool_schema_has_required_action`.
|
||||
const list = await callOpenhumanRpc<AvailableToolsResult>(
|
||||
'openhuman.agent_registry_available_tools',
|
||||
{}
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
const defs = list.result?.definitions ?? [];
|
||||
// The integrations_agent and tools_agent both bring browser surfaces
|
||||
// (the former via SaaS-specific scrapers, the latter via the generic
|
||||
// `browser` automation tool). Confirm at least one is present.
|
||||
const browserBearing = defs.filter(
|
||||
d =>
|
||||
d?.id === 'tools_agent' ||
|
||||
d?.id === 'integrations_agent' ||
|
||||
d?.id === 'researcher' ||
|
||||
d?.id === 'planner'
|
||||
);
|
||||
stepLog('browser-bearing agent definitions', { ids: browserBearing.map(d => d.id) });
|
||||
expect(browserBearing.length).toBeGreaterThan(0);
|
||||
const tools = list.result?.tools ?? [];
|
||||
const browserTool = tools.find(tool => tool?.name === 'browser');
|
||||
stepLog('live browser tool metadata', browserTool);
|
||||
expect(browserTool).toBeDefined();
|
||||
expect(browserTool?.description).toContain("'snapshot'");
|
||||
expect(browserTool?.description?.toLowerCase()).not.toContain('screenshot');
|
||||
});
|
||||
|
||||
it.skip('(future #68) chat tool_calls drive `browser_open https://example.com` via mock LLM', () => {
|
||||
|
||||
@@ -161,7 +161,6 @@ async function setupMockRpc(page: Page, state: MockState) {
|
||||
localState: {},
|
||||
keyringStatus: { isUnlocked: true, hasPassphrase: false },
|
||||
runtime: {
|
||||
screenIntelligence: { enabled: false },
|
||||
localAi: { enabled: false },
|
||||
autocomplete: { enabled: false },
|
||||
service: { running: false },
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { bootAuthenticatedPage, waitForAppReady } from '../helpers/core-rpc';
|
||||
|
||||
test.describe('Screen Intelligence', () => {
|
||||
test('opens the Screen Intelligence settings route', async ({ page }) => {
|
||||
await bootAuthenticatedPage(page, 'pw-screen-intelligence', '/settings/screen-intelligence');
|
||||
await waitForAppReady(page);
|
||||
|
||||
const text = await page.locator('#root').innerText();
|
||||
expect(text.includes('Screen Awareness')).toBe(true);
|
||||
});
|
||||
|
||||
test('debug route reaches a stable success or unsupported/failure state', async ({ page }) => {
|
||||
await bootAuthenticatedPage(
|
||||
page,
|
||||
'pw-screen-intelligence-debug',
|
||||
'/settings/screen-awareness-debug'
|
||||
);
|
||||
await waitForAppReady(page);
|
||||
|
||||
const text = await page.locator('#root').innerText();
|
||||
expect(
|
||||
[
|
||||
'Screen Awareness',
|
||||
'screen capture is unsupported on this platform',
|
||||
'screen recording permission is not granted',
|
||||
'Capture test',
|
||||
'Test capture',
|
||||
].some(marker => text.includes(marker))
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user