android-midscene-automation 0.2.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +159 -29
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +340 -121
- package/src/types.ts +1 -1
package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
.floating-nav-switcher {
|
|
18
|
+
position: fixed;
|
|
19
|
+
top: 12px;
|
|
20
|
+
left: 24px;
|
|
21
|
+
z-index: 1100;
|
|
22
|
+
height: 36px;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
background: rgba(255, 255, 255, 0.72);
|
|
27
|
+
backdrop-filter: blur(16px) saturate(180%);
|
|
28
|
+
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
29
|
+
border: 1px solid rgba(226, 232, 240, 0.85);
|
|
30
|
+
border-radius: 18px;
|
|
31
|
+
padding: 3px;
|
|
32
|
+
gap: 3px;
|
|
33
|
+
box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(0, 0, 0, 0.02), inset 0 1px 1px rgba(255, 255, 255, 0.9);
|
|
34
|
+
user-select: none;
|
|
35
|
+
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
36
|
+
transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
|
37
|
+
box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
|
38
|
+
border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
39
|
+
|
|
40
|
+
&:hover {
|
|
41
|
+
background: rgba(255, 255, 255, 0.85);
|
|
42
|
+
box-shadow: 0 4px 14px -2px rgba(15, 23, 42, 0.1), 0 2px 4px rgba(0, 0, 0, 0.03), inset 0 1px 1px rgba(255, 255, 255, 1);
|
|
43
|
+
border-color: rgba(203, 213, 225, 0.9);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.nav-tab-btn {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
gap: 6px;
|
|
51
|
+
height: 30px;
|
|
52
|
+
padding: 0 14px;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
border-radius: 15px;
|
|
55
|
+
font-size: 13px;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
color: #475569;
|
|
58
|
+
text-decoration: none;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
62
|
+
color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
63
|
+
box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
64
|
+
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
65
|
+
outline: none;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
|
|
68
|
+
.tab-icon {
|
|
69
|
+
font-size: 16px;
|
|
70
|
+
width: 16px;
|
|
71
|
+
height: 16px;
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
color: #64748b;
|
|
76
|
+
transition: color 0.15s ease, transform 0.15s ease;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.tab-label {
|
|
80
|
+
color: #475569;
|
|
81
|
+
transition: color 0.15s ease;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&:hover:not(.active) {
|
|
85
|
+
background: rgba(255, 255, 255, 0.7);
|
|
86
|
+
color: #0f172a;
|
|
87
|
+
|
|
88
|
+
.tab-icon {
|
|
89
|
+
color: #1a73e8;
|
|
90
|
+
}
|
|
91
|
+
.tab-label {
|
|
92
|
+
color: #0f172a;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&.active {
|
|
97
|
+
background: rgba(26, 115, 232, 0.95) !important;
|
|
98
|
+
backdrop-filter: blur(12px);
|
|
99
|
+
-webkit-backdrop-filter: blur(12px);
|
|
100
|
+
color: #ffffff !important;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.35), inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
103
|
+
|
|
104
|
+
.tab-icon,
|
|
105
|
+
.tab-label,
|
|
106
|
+
span {
|
|
107
|
+
color: #ffffff !important;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&:active {
|
|
112
|
+
transform: scale(0.97);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TranslatePipe } from '../../core/i18n/translate.pipe';
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
19
|
+
|
|
20
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'app-nav-switcher',
|
|
24
|
+
standalone: true,
|
|
25
|
+
imports: [TranslatePipe, RouterLink, RouterLinkActive],
|
|
26
|
+
template: `
|
|
27
|
+
<nav class="floating-nav-switcher" [attr.aria-label]="'Main Navigation' | t">
|
|
28
|
+
<a
|
|
29
|
+
routerLink="/"
|
|
30
|
+
routerLinkActive="active"
|
|
31
|
+
[routerLinkActiveOptions]="{exact: true}"
|
|
32
|
+
class="nav-tab-btn"
|
|
33
|
+
[title]="'Return to Home Launcher to start a new task' | t"
|
|
34
|
+
>
|
|
35
|
+
<span class="material-symbols-outlined tab-icon">add_task</span>
|
|
36
|
+
<span class="tab-label">{{ 'New / Home' | t }}</span>
|
|
37
|
+
</a>
|
|
38
|
+
<a
|
|
39
|
+
routerLink="/workspace"
|
|
40
|
+
routerLinkActive="active"
|
|
41
|
+
class="nav-tab-btn"
|
|
42
|
+
[title]="'Open Workspace' | t"
|
|
43
|
+
>
|
|
44
|
+
<span class="material-symbols-outlined tab-icon">space_dashboard</span>
|
|
45
|
+
<span class="tab-label">{{ 'Workspace' | t }}</span>
|
|
46
|
+
</a>
|
|
47
|
+
</nav>
|
|
48
|
+
`,
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.Eager,
|
|
50
|
+
styleUrls: ['./nav-switcher.component.scss']
|
|
51
|
+
})
|
|
52
|
+
export class NavSwitcherComponent {}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface AppReference {
|
|
18
|
+
name: string;
|
|
19
|
+
icon: string;
|
|
20
|
+
pkg?: string;
|
|
21
|
+
category?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SuggestionCategory =
|
|
25
|
+
| 'all'
|
|
26
|
+
| 'flash'
|
|
27
|
+
| 'pro'
|
|
28
|
+
| 'cross_app'
|
|
29
|
+
| 'monitor';
|
|
30
|
+
|
|
31
|
+
export interface SmartSuggestion {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
description: string;
|
|
35
|
+
goal: string;
|
|
36
|
+
profile: 'flash' | 'pro';
|
|
37
|
+
category: 'flash' | 'pro' | 'cross_app' | 'monitor';
|
|
38
|
+
tag: string;
|
|
39
|
+
apps: AppReference[];
|
|
40
|
+
requiredPackages?: string[];
|
|
41
|
+
matchMode?: 'any' | 'all';
|
|
42
|
+
priority?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Recognized Android App Package Registry
|
|
47
|
+
*/
|
|
48
|
+
export const APP_REGISTRY: Record<string, AppReference> = {
|
|
49
|
+
// Google Suite & System
|
|
50
|
+
'com.google.android.apps.maps': { name: 'Maps', icon: 'explore', pkg: 'com.google.android.apps.maps', category: 'navigation' },
|
|
51
|
+
'com.google.android.gm': { name: 'Gmail', icon: 'mail', pkg: 'com.google.android.gm', category: 'productivity' },
|
|
52
|
+
'com.android.chrome': { name: 'Chrome', icon: 'public', pkg: 'com.android.chrome', category: 'browser' },
|
|
53
|
+
'com.google.android.youtube': { name: 'YouTube', icon: 'smart_display', pkg: 'com.google.android.youtube', category: 'entertainment' },
|
|
54
|
+
'com.android.settings': { name: 'Settings', icon: 'settings', pkg: 'com.android.settings', category: 'system' },
|
|
55
|
+
'com.google.android.deskclock': { name: 'Clock', icon: 'timer', pkg: 'com.google.android.deskclock', category: 'utility' },
|
|
56
|
+
'com.android.deskclock': { name: 'Clock', icon: 'timer', pkg: 'com.android.deskclock', category: 'utility' },
|
|
57
|
+
'com.google.android.calculator': { name: 'Calculator', icon: 'calculate', pkg: 'com.google.android.calculator', category: 'utility' },
|
|
58
|
+
'com.android.calculator2': { name: 'Calculator', icon: 'calculate', pkg: 'com.android.calculator2', category: 'utility' },
|
|
59
|
+
'com.google.android.apps.photos': { name: 'Photos', icon: 'photo_library', pkg: 'com.google.android.apps.photos', category: 'media' },
|
|
60
|
+
'com.google.android.calendar': { name: 'Calendar', icon: 'calendar_month', pkg: 'com.google.android.calendar', category: 'productivity' },
|
|
61
|
+
'com.google.android.keep': { name: 'Keep Notes', icon: 'note_alt', pkg: 'com.google.android.keep', category: 'productivity' },
|
|
62
|
+
'com.android.vending': { name: 'Play Store', icon: 'storefront', pkg: 'com.android.vending', category: 'tools' },
|
|
63
|
+
'com.google.android.apps.messaging': { name: 'Messages', icon: 'chat', pkg: 'com.google.android.apps.messaging', category: 'communication' },
|
|
64
|
+
|
|
65
|
+
// Popular Ecosystem Apps
|
|
66
|
+
'com.tencent.mm': { name: 'WeChat', icon: 'forum', pkg: 'com.tencent.mm', category: 'social' },
|
|
67
|
+
'com.xingin.xhs': { name: 'Xiaohongshu', icon: 'auto_stories', pkg: 'com.xingin.xhs', category: 'social' },
|
|
68
|
+
'com.sankuai.meituan': { name: 'Meituan', icon: 'restaurant', pkg: 'com.sankuai.meituan', category: 'lifestyle' },
|
|
69
|
+
'com.dianping.v1': { name: 'Dianping', icon: 'star', pkg: 'com.dianping.v1', category: 'lifestyle' },
|
|
70
|
+
'tv.danmaku.bili': { name: 'Bilibili', icon: 'video_library', pkg: 'tv.danmaku.bili', category: 'entertainment' },
|
|
71
|
+
'com.eg.android.AlipayGphone': { name: 'Alipay', icon: 'account_balance_wallet', pkg: 'com.eg.android.AlipayGphone', category: 'finance' },
|
|
72
|
+
'com.netease.cloudmusic': { name: 'NetEase Music', icon: 'headphones', pkg: 'com.netease.cloudmusic', category: 'entertainment' },
|
|
73
|
+
'com.spotify.music': { name: 'Spotify', icon: 'music_note', pkg: 'com.spotify.music', category: 'entertainment' }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Curated, clean task preset library (1~2 representative tasks per common app)
|
|
78
|
+
*/
|
|
79
|
+
export const SMART_TASK_LIBRARY: SmartSuggestion[] = [
|
|
80
|
+
// 1. Google Maps
|
|
81
|
+
{
|
|
82
|
+
id: 'maps_coffee',
|
|
83
|
+
title: 'Find Specialty Coffee',
|
|
84
|
+
description: 'Search nearby top-rated cafes in Google Maps',
|
|
85
|
+
goal: 'Open Google Maps, search for top-rated specialty coffee shops nearby, and view the top result details.',
|
|
86
|
+
profile: 'flash',
|
|
87
|
+
category: 'flash',
|
|
88
|
+
tag: 'Maps',
|
|
89
|
+
apps: [{ name: 'Maps', icon: 'explore', pkg: 'com.google.android.apps.maps' }],
|
|
90
|
+
requiredPackages: ['com.google.android.apps.maps'],
|
|
91
|
+
priority: 95
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'pro_commute_share',
|
|
95
|
+
title: 'Commute ETA & Message Draft',
|
|
96
|
+
description: 'Check transit time on Maps and draft arrival ETA in Messages',
|
|
97
|
+
goal: 'Open Google Maps to check commute time to the International Airport, calculate arrival time, then open Messages and draft an ETA text message.',
|
|
98
|
+
profile: 'pro',
|
|
99
|
+
category: 'cross_app',
|
|
100
|
+
tag: 'Maps + Messages',
|
|
101
|
+
apps: [
|
|
102
|
+
{ name: 'Maps', icon: 'explore', pkg: 'com.google.android.apps.maps' },
|
|
103
|
+
{ name: 'Messages', icon: 'chat', pkg: 'com.google.android.apps.messaging' }
|
|
104
|
+
],
|
|
105
|
+
requiredPackages: ['com.google.android.apps.maps', 'com.google.android.apps.messaging'],
|
|
106
|
+
matchMode: 'all',
|
|
107
|
+
priority: 92
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// 2. Gmail
|
|
111
|
+
{
|
|
112
|
+
id: 'gmail_receipts',
|
|
113
|
+
title: 'Search Order Receipts',
|
|
114
|
+
description: 'Find recent flight or delivery confirmation emails in Gmail',
|
|
115
|
+
goal: 'Open Gmail and search for recent flight or package delivery confirmation emails.',
|
|
116
|
+
profile: 'flash',
|
|
117
|
+
category: 'flash',
|
|
118
|
+
tag: 'Gmail',
|
|
119
|
+
apps: [{ name: 'Gmail', icon: 'mail', pkg: 'com.google.android.gm' }],
|
|
120
|
+
requiredPackages: ['com.google.android.gm'],
|
|
121
|
+
priority: 90
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'pro_email_to_calendar',
|
|
125
|
+
title: 'Email Itinerary to Calendar',
|
|
126
|
+
description: 'Extract flight or event dates from Gmail and schedule in Calendar',
|
|
127
|
+
goal: 'Open Gmail to find the latest event invitation or itinerary, extract dates and location, then open Google Calendar and create a corresponding calendar event.',
|
|
128
|
+
profile: 'pro',
|
|
129
|
+
category: 'cross_app',
|
|
130
|
+
tag: 'Gmail + Calendar',
|
|
131
|
+
apps: [
|
|
132
|
+
{ name: 'Gmail', icon: 'mail', pkg: 'com.google.android.gm' },
|
|
133
|
+
{ name: 'Calendar', icon: 'calendar_month', pkg: 'com.google.android.calendar' }
|
|
134
|
+
],
|
|
135
|
+
requiredPackages: ['com.google.android.gm'],
|
|
136
|
+
priority: 94
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// 3. Chrome
|
|
140
|
+
{
|
|
141
|
+
id: 'chrome_research',
|
|
142
|
+
title: 'Search AI News Breakthroughs',
|
|
143
|
+
description: 'Search latest multimodal AI developments in Chrome browser',
|
|
144
|
+
goal: 'Open Chrome browser and search for latest breakthroughs in multimodal mobile AI agents.',
|
|
145
|
+
profile: 'flash',
|
|
146
|
+
category: 'flash',
|
|
147
|
+
tag: 'Chrome',
|
|
148
|
+
apps: [{ name: 'Chrome', icon: 'public', pkg: 'com.android.chrome' }],
|
|
149
|
+
requiredPackages: ['com.android.chrome'],
|
|
150
|
+
priority: 88
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'pro_research_keep',
|
|
154
|
+
title: 'Product Research & Notes Note',
|
|
155
|
+
description: 'Compare top 3 headphones on Chrome and record comparison in Keep',
|
|
156
|
+
goal: 'Open Chrome, research top 3 noise-cancelling headphones comparing price and battery life, then write a structured comparison summary note in Keep Notes.',
|
|
157
|
+
profile: 'pro',
|
|
158
|
+
category: 'pro',
|
|
159
|
+
tag: 'Chrome + Keep',
|
|
160
|
+
apps: [
|
|
161
|
+
{ name: 'Chrome', icon: 'public', pkg: 'com.android.chrome' },
|
|
162
|
+
{ name: 'Keep Notes', icon: 'note_alt', pkg: 'com.google.android.keep' }
|
|
163
|
+
],
|
|
164
|
+
requiredPackages: ['com.android.chrome'],
|
|
165
|
+
priority: 91
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// 4. YouTube
|
|
169
|
+
{
|
|
170
|
+
id: 'youtube_lofi',
|
|
171
|
+
title: 'Play Lo-Fi Music Radio',
|
|
172
|
+
description: 'Search and play a Lo-Fi hip hop live stream on YouTube',
|
|
173
|
+
goal: 'Open YouTube, search for "Lofi hip hop beats relaxing radio" and tap on the live stream.',
|
|
174
|
+
profile: 'flash',
|
|
175
|
+
category: 'flash',
|
|
176
|
+
tag: 'YouTube',
|
|
177
|
+
apps: [{ name: 'YouTube', icon: 'smart_display', pkg: 'com.google.android.youtube' }],
|
|
178
|
+
requiredPackages: ['com.google.android.youtube'],
|
|
179
|
+
priority: 85
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// 5. Settings
|
|
183
|
+
{
|
|
184
|
+
id: 'settings_display_wifi',
|
|
185
|
+
title: 'Dark Mode & Wi-Fi Check',
|
|
186
|
+
description: 'Toggle dark theme and verify network connection in Settings',
|
|
187
|
+
goal: 'Open Settings app, navigate to Display settings, ensure Dark theme is enabled, and check Wi-Fi connection status.',
|
|
188
|
+
profile: 'flash',
|
|
189
|
+
category: 'flash',
|
|
190
|
+
tag: 'Settings',
|
|
191
|
+
apps: [{ name: 'Settings', icon: 'settings', pkg: 'com.android.settings' }],
|
|
192
|
+
requiredPackages: ['com.android.settings'],
|
|
193
|
+
priority: 87
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'pro_settings_qa',
|
|
197
|
+
title: 'Subsystem Health & Crash Probe',
|
|
198
|
+
description: 'Traverse Settings submenus to verify screens and check for crash dialogs',
|
|
199
|
+
goal: 'Explore Settings submenus (Network, Connected devices, Apps, Battery, Storage), verify each screen loads properly without ANR or crash dialogs, and summarize results.',
|
|
200
|
+
profile: 'pro',
|
|
201
|
+
category: 'monitor',
|
|
202
|
+
tag: 'Settings QA',
|
|
203
|
+
apps: [{ name: 'Settings', icon: 'settings', pkg: 'com.android.settings' }],
|
|
204
|
+
requiredPackages: ['com.android.settings'],
|
|
205
|
+
priority: 93
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// 6. Clock
|
|
209
|
+
{
|
|
210
|
+
id: 'clock_timer',
|
|
211
|
+
title: '25-Min Pomodoro Timer',
|
|
212
|
+
description: 'Start a 25-minute focus countdown timer in Clock app',
|
|
213
|
+
goal: 'Open Clock app, switch to Timer tab, set 25 minutes and start the countdown timer.',
|
|
214
|
+
profile: 'flash',
|
|
215
|
+
category: 'flash',
|
|
216
|
+
tag: 'Clock',
|
|
217
|
+
apps: [{ name: 'Clock', icon: 'timer', pkg: 'com.google.android.deskclock' }],
|
|
218
|
+
requiredPackages: ['com.google.android.deskclock', 'com.android.deskclock'],
|
|
219
|
+
priority: 86
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// 7. Calculator
|
|
223
|
+
{
|
|
224
|
+
id: 'calc_gratuity',
|
|
225
|
+
title: 'Split Bill & Calculate Tip',
|
|
226
|
+
description: 'Calculate 18% gratuity on $186.40 for 3 people in Calculator',
|
|
227
|
+
goal: 'Open Calculator and calculate 18% tip on a bill of $186.40, then divide by 3 people.',
|
|
228
|
+
profile: 'flash',
|
|
229
|
+
category: 'flash',
|
|
230
|
+
tag: 'Calculator',
|
|
231
|
+
apps: [{ name: 'Calculator', icon: 'calculate', pkg: 'com.google.android.calculator' }],
|
|
232
|
+
requiredPackages: ['com.google.android.calculator', 'com.android.calculator2'],
|
|
233
|
+
priority: 84
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// 8. Photos
|
|
237
|
+
{
|
|
238
|
+
id: 'photos_inspect',
|
|
239
|
+
title: 'Inspect Recent Screenshot',
|
|
240
|
+
description: 'Open Google Photos and review the latest screenshot taken',
|
|
241
|
+
goal: 'Open Google Photos and view the most recent screenshot in the screenshots album.',
|
|
242
|
+
profile: 'flash',
|
|
243
|
+
category: 'flash',
|
|
244
|
+
tag: 'Photos',
|
|
245
|
+
apps: [{ name: 'Photos', icon: 'photo_library', pkg: 'com.google.android.apps.photos' }],
|
|
246
|
+
requiredPackages: ['com.google.android.apps.photos'],
|
|
247
|
+
priority: 82
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
// 9. WeChat
|
|
251
|
+
{
|
|
252
|
+
id: 'wechat_browse',
|
|
253
|
+
title: 'Check WeChat Messages',
|
|
254
|
+
description: 'Open WeChat and view top recent chat conversations',
|
|
255
|
+
goal: 'Open WeChat and view the top recent chat messages.',
|
|
256
|
+
profile: 'flash',
|
|
257
|
+
category: 'flash',
|
|
258
|
+
tag: 'WeChat',
|
|
259
|
+
apps: [{ name: 'WeChat', icon: 'forum', pkg: 'com.tencent.mm' }],
|
|
260
|
+
requiredPackages: ['com.tencent.mm'],
|
|
261
|
+
priority: 89
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'pro_wechat_to_calendar',
|
|
265
|
+
title: 'WeChat Notice to Calendar',
|
|
266
|
+
description: 'Extract meeting notice from WeChat chat and add to Calendar',
|
|
267
|
+
goal: 'Open WeChat, locate the latest meeting announcement or event message in the top chat, extract the time and topic, then open Calendar and schedule an event.',
|
|
268
|
+
profile: 'pro',
|
|
269
|
+
category: 'cross_app',
|
|
270
|
+
tag: 'WeChat + Calendar',
|
|
271
|
+
apps: [
|
|
272
|
+
{ name: 'WeChat', icon: 'forum', pkg: 'com.tencent.mm' },
|
|
273
|
+
{ name: 'Calendar', icon: 'calendar_month', pkg: 'com.google.android.calendar' }
|
|
274
|
+
],
|
|
275
|
+
requiredPackages: ['com.tencent.mm'],
|
|
276
|
+
priority: 93
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// 10. Xiaohongshu
|
|
280
|
+
{
|
|
281
|
+
id: 'xhs_coffee_guide',
|
|
282
|
+
title: 'RED Cafe Guide Search',
|
|
283
|
+
description: 'Search trending specialty cafe reviews on Xiaohongshu',
|
|
284
|
+
goal: 'Open Xiaohongshu, search for top-rated specialty coffee shops, and view the top post.',
|
|
285
|
+
profile: 'flash',
|
|
286
|
+
category: 'flash',
|
|
287
|
+
tag: 'Xiaohongshu',
|
|
288
|
+
apps: [{ name: 'Xiaohongshu', icon: 'auto_stories', pkg: 'com.xingin.xhs' }],
|
|
289
|
+
requiredPackages: ['com.xingin.xhs'],
|
|
290
|
+
priority: 87
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
// 11. Meituan / Dianping
|
|
294
|
+
{
|
|
295
|
+
id: 'meituan_ramen_search',
|
|
296
|
+
title: 'Meituan Food Search',
|
|
297
|
+
description: 'Search top-rated Ramen nearby on Meituan or Dianping',
|
|
298
|
+
goal: 'Open Meituan or Dianping, search for top-rated Ramen nearby, and view top restaurant rating.',
|
|
299
|
+
profile: 'flash',
|
|
300
|
+
category: 'flash',
|
|
301
|
+
tag: 'Meituan',
|
|
302
|
+
apps: [{ name: 'Meituan', icon: 'restaurant', pkg: 'com.sankuai.meituan' }],
|
|
303
|
+
requiredPackages: ['com.sankuai.meituan', 'com.dianping.v1'],
|
|
304
|
+
priority: 86
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// 12. Bilibili
|
|
308
|
+
{
|
|
309
|
+
id: 'bilibili_stream',
|
|
310
|
+
title: 'Bilibili Tech Video',
|
|
311
|
+
description: 'Search and play an AI Agent tutorial video on Bilibili',
|
|
312
|
+
goal: 'Open Bilibili, search for "AI Agent Architecture", and play the top matching video.',
|
|
313
|
+
profile: 'flash',
|
|
314
|
+
category: 'flash',
|
|
315
|
+
tag: 'Bilibili',
|
|
316
|
+
apps: [{ name: 'Bilibili', icon: 'video_library', pkg: 'tv.danmaku.bili' }],
|
|
317
|
+
requiredPackages: ['tv.danmaku.bili'],
|
|
318
|
+
priority: 85
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// 13. Play Store
|
|
322
|
+
{
|
|
323
|
+
id: 'pro_playstore_review',
|
|
324
|
+
title: 'Play Store App Review Study',
|
|
325
|
+
description: 'Compare top task management apps and ratings on Google Play',
|
|
326
|
+
goal: 'Open Google Play Store, search for top rated task management apps, compare ratings and latest user reviews of the top 2 candidates, and record recommendations.',
|
|
327
|
+
profile: 'pro',
|
|
328
|
+
category: 'pro',
|
|
329
|
+
tag: 'Play Store',
|
|
330
|
+
apps: [{ name: 'Play Store', icon: 'storefront', pkg: 'com.android.vending' }],
|
|
331
|
+
requiredPackages: ['com.android.vending'],
|
|
332
|
+
priority: 88
|
|
333
|
+
}
|
|
334
|
+
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { LanguageService, LANGUAGE_STORAGE_KEY } from './language.service';
|
|
4
|
+
import { TranslatePipe } from './translate.pipe';
|
|
5
|
+
|
|
6
|
+
describe('UI language', () => {
|
|
7
|
+
let storage: Map<string, string>;
|
|
8
|
+
let documentStub: { documentElement: { lang: string }; defaultView: { localStorage: { getItem: jasmine.Spy; setItem: jasmine.Spy } } };
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
storage = new Map();
|
|
12
|
+
documentStub = {
|
|
13
|
+
documentElement: { lang: '' },
|
|
14
|
+
defaultView: { localStorage: {
|
|
15
|
+
getItem: jasmine.createSpy().and.callFake((key: string) => storage.get(key) ?? null),
|
|
16
|
+
setItem: jasmine.createSpy().and.callFake((key: string, value: string) => storage.set(key, value))
|
|
17
|
+
} }
|
|
18
|
+
};
|
|
19
|
+
TestBed.configureTestingModule({ providers: [{ provide: DOCUMENT, useValue: documentStub }] });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('defaults to Chinese and switches an existing pipe without changing input text', () => {
|
|
23
|
+
const service = TestBed.inject(LanguageService);
|
|
24
|
+
const pipe = TestBed.runInInjectionContext(() => new TranslatePipe());
|
|
25
|
+
expect(pipe.transform('Recommended Tasks')).toBe('推荐任务');
|
|
26
|
+
service.setLanguage('en');
|
|
27
|
+
expect(pipe.transform('Recommended Tasks')).toBe('Recommended Tasks');
|
|
28
|
+
expect(documentStub.documentElement.lang).toBe('en');
|
|
29
|
+
expect(storage.get(LANGUAGE_STORAGE_KEY)).toBe('en');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('restores the saved English preference', () => {
|
|
33
|
+
storage.set(LANGUAGE_STORAGE_KEY, 'en');
|
|
34
|
+
expect(TestBed.inject(LanguageService).language()).toBe('en');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('works when storage access is blocked', () => {
|
|
38
|
+
documentStub.defaultView.localStorage.getItem.and.throwError('blocked');
|
|
39
|
+
documentStub.defaultView.localStorage.setItem.and.throwError('blocked');
|
|
40
|
+
const service = TestBed.inject(LanguageService);
|
|
41
|
+
expect(service.language()).toBe('zh');
|
|
42
|
+
expect(() => service.setLanguage('en')).not.toThrow();
|
|
43
|
+
expect(service.translate('Workspace')).toBe('Workspace');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('preserves unknown text, identifiers and English whitespace', () => {
|
|
47
|
+
const service = TestBed.inject(LanguageService);
|
|
48
|
+
expect(service.translate('855ef847 / gemini-3.8-flash')).toBe('855ef847 / gemini-3.8-flash');
|
|
49
|
+
expect(service.translate('Custom server error: foo')).toBe('Custom server error: foo');
|
|
50
|
+
expect(service.translate('Python 3.12.14 Ready')).toBe('Python 3.12.14 已就绪');
|
|
51
|
+
expect(service.translate(' Workspace ')).toBe(' 工作区 ');
|
|
52
|
+
service.setLanguage('en');
|
|
53
|
+
expect(service.translate(' Workspace ')).toBe(' Workspace ');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import { inject, Injectable, signal } from '@angular/core';
|
|
3
|
+
import { ZH_CN } from './zh-cn';
|
|
4
|
+
|
|
5
|
+
export type UiLanguage = 'zh' | 'en';
|
|
6
|
+
export const LANGUAGE_STORAGE_KEY = 'artemis.ui.language';
|
|
7
|
+
|
|
8
|
+
/** UI copy only: task text, model output and device identifiers stay untouched. */
|
|
9
|
+
@Injectable({ providedIn: 'root' })
|
|
10
|
+
export class LanguageService {
|
|
11
|
+
private readonly document = inject(DOCUMENT);
|
|
12
|
+
private readonly current = signal<UiLanguage>(this.readPreference());
|
|
13
|
+
readonly language = this.current.asReadonly();
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.updateDocument();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setLanguage(language: UiLanguage): void {
|
|
20
|
+
this.current.set(language);
|
|
21
|
+
this.updateDocument();
|
|
22
|
+
try {
|
|
23
|
+
this.document.defaultView?.localStorage.setItem(LANGUAGE_STORAGE_KEY, language);
|
|
24
|
+
} catch {
|
|
25
|
+
// The switch still works if the browser disallows persistent storage.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
translate(value: string | null | undefined): string {
|
|
30
|
+
if (!value) return '';
|
|
31
|
+
if (this.language() === 'en') return value;
|
|
32
|
+
const key = value.replace(/\s+/g, ' ').trim();
|
|
33
|
+
const translated = ZH_CN[key];
|
|
34
|
+
if (translated === undefined) {
|
|
35
|
+
// Known diagnostic templates preserve variable identifiers verbatim.
|
|
36
|
+
return value
|
|
37
|
+
.replace(/^Python ([\d.]+) Ready$/, 'Python $1 已就绪')
|
|
38
|
+
.replace(/^Python ([\d.]+) Unsupported$/, 'Python $1 不受支持')
|
|
39
|
+
.replace(/^(\d+) failed$/, '$1 项失败')
|
|
40
|
+
.replace(/^Stop current task: /, '停止当前任务:')
|
|
41
|
+
.replace(/^Stop current task \((.+)\)$/, '停止当前任务($1)')
|
|
42
|
+
.replace(/^ADB connected to (.+), but the Android lock screen is active\. Unlock the device and enter the home screen before running a task\.$/,
|
|
43
|
+
'ADB 已连接到 $1,但设备处于锁屏状态。请解锁并进入主屏幕后再运行任务。')
|
|
44
|
+
.replace(/^ADB connected to (.+), but Android lock-screen state could not be verified\. Keep the device unlocked on the home screen and check again\.$/,
|
|
45
|
+
'ADB 已连接到 $1,但无法确认锁屏状态。请保持设备解锁并停留在主屏幕,然后再次检查。');
|
|
46
|
+
}
|
|
47
|
+
return value.match(/^\s*/)?.[0] + translated + value.match(/\s*$/)?.[0];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private readPreference(): UiLanguage {
|
|
51
|
+
try {
|
|
52
|
+
return this.document.defaultView?.localStorage.getItem(LANGUAGE_STORAGE_KEY) === 'en' ? 'en' : 'zh';
|
|
53
|
+
} catch {
|
|
54
|
+
return 'zh';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private updateDocument(): void {
|
|
59
|
+
this.document.documentElement.lang = this.language() === 'zh' ? 'zh-CN' : 'en';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { inject, Pipe, PipeTransform } from '@angular/core';
|
|
2
|
+
import { LanguageService } from './language.service';
|
|
3
|
+
|
|
4
|
+
@Pipe({ name: 't', standalone: true, pure: false })
|
|
5
|
+
export class TranslatePipe implements PipeTransform {
|
|
6
|
+
private readonly language = inject(LanguageService);
|
|
7
|
+
|
|
8
|
+
transform(value: string | null | undefined): string {
|
|
9
|
+
return this.language.translate(value);
|
|
10
|
+
}
|
|
11
|
+
}
|