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.
Files changed (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,2130 @@
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
+ <div class="home-container" [class.system-ready]="isReady()" [class.system-pending]="!isReady()" [class.pro-theme-active]="selectedProfile() === 'pro'">
18
+ <!-- Dynamic Aurora Glow Background -->
19
+ <div class="aurora-background">
20
+ <div class="glow glow-1"></div>
21
+ <div class="glow glow-2"></div>
22
+ <div class="glow glow-3"></div>
23
+ <div class="glow glow-pending"></div>
24
+ <div class="glow glow-ready"></div>
25
+ <div class="glow glow-pro"></div>
26
+ </div>
27
+
28
+
29
+ <!-- Main Content Layout -->
30
+ <main class="home-content">
31
+
32
+ <!-- Task entry section -->
33
+ <section class="hero-section">
34
+ <h1 class="hero-title">
35
+ {{ 'What would you like to do on your phone?' | t }}
36
+ </h1>
37
+ </section>
38
+
39
+ @if (copyFailed()) {
40
+ <p class="clipboard-error" role="alert">{{ 'Copy failed. Please select the text and copy it manually.' | t }}</p>
41
+ }
42
+
43
+ <!-- Top View Mode Stepper / Tab Switcher (Frosted Glass Segmented Control matching top-left nav) -->
44
+ <div class="mode-tabs-container">
45
+ <div class="mode-tabs">
46
+ <button
47
+ type="button"
48
+ class="mode-tab-btn"
49
+ [class.active]="activeTab() === 'diagnostics'"
50
+ (click)="setTab('diagnostics')"
51
+ >
52
+ @if (isReady()) {
53
+ <span class="material-symbols-outlined tab-icon icon-ready">check_circle</span>
54
+ } @else {
55
+ <span class="material-symbols-outlined tab-icon">tune</span>
56
+ }
57
+ <span class="tab-label">{{ 'System Setup & Prerequisites' | t }}</span>
58
+ </button>
59
+
60
+ <button
61
+ type="button"
62
+ class="mode-tab-btn"
63
+ [class.active]="activeTab() === 'launcher'"
64
+ (click)="setTab('launcher')"
65
+ >
66
+ @if (!isReady()) {
67
+ <span class="material-symbols-outlined tab-icon icon-locked">lock</span>
68
+ } @else {
69
+ <span class="material-symbols-outlined tab-icon">rocket_launch</span>
70
+ }
71
+ <span class="tab-label">{{ 'Launch Autonomous Task' | t }}</span>
72
+ </button>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- ========================================================================= -->
77
+ <!-- VIEW 1: SYSTEM SETUP & FOOLPROOF STEP-BY-STEP GUIDANCE -->
78
+ <!-- ========================================================================= -->
79
+ @if (activeTab() === 'diagnostics') {
80
+ <section class="setup-guide-view">
81
+
82
+ <!-- =================================================================== -->
83
+ <!-- STEP 1: ENVIRONMENT & SYSTEM DEPENDENCIES (FIRST & FOUNDATIONAL) -->
84
+ <!-- =================================================================== -->
85
+ <div class="guide-card" [class.card-pass]="isEnvironmentReady()" [class.card-warn]="!isEnvironmentReady()">
86
+
87
+ <div class="guide-card-header">
88
+ <div class="step-badge" [class.step-pass]="isEnvironmentReady()">
89
+ @if (isEnvironmentReady()) {
90
+ <span class="material-symbols-outlined step-check-icon">check</span>
91
+ } @else {
92
+ <span>1</span>
93
+ }
94
+ </div>
95
+ <div class="step-info">
96
+ <div class="step-title-row">
97
+ <h3 class="step-title">{{ 'System & Environment' | t }}</h3>
98
+ </div>
99
+ <p class="step-desc">{{ 'Core tools, Android debug bridge, and video recording runtime' | t }}</p>
100
+ </div>
101
+
102
+ <button
103
+ type="button"
104
+ class="btn-header-action"
105
+ [disabled]="isRefreshingDiagnostics()"
106
+ (click)="refreshReadiness()"
107
+ [title]="'Re-check all system dependencies' | t"
108
+ >
109
+ <span class="material-symbols-outlined" [class.spinning]="isRefreshingDiagnostics()">refresh</span>
110
+ <span>{{ isRefreshingDiagnostics() ? ('Checking...' | t) : ('Re-check' | t) }}</span>
111
+ </button>
112
+ </div>
113
+
114
+ <div class="guide-card-body">
115
+ <!-- One-Click Dependency Installer Bar when any env dep is missing -->
116
+ @if (hasReadinessReport() && !isEnvironmentReady()) {
117
+ <div class="one-click-install-bar">
118
+ <div class="install-bar-left">
119
+ <div class="install-bar-icon-box">
120
+ <span class="material-symbols-outlined bolt-icon">bolt</span>
121
+ </div>
122
+ <div class="install-bar-text">
123
+ <div class="install-bar-title-row">
124
+ <div class="install-bar-title">{{ 'Automated Dependency Setup' | t }}</div>
125
+ <div class="os-selector-pills">
126
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'linux'"
127
+ (click)="setSelectedOs('linux')">{{ 'Linux' | t }}</button>
128
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'darwin'"
129
+ (click)="setSelectedOs('darwin')">{{ 'macOS' | t }}</button>
130
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'windows'"
131
+ (click)="setSelectedOs('windows')">{{ 'Windows' | t }}</button>
132
+ </div>
133
+ </div>
134
+ <div class="install-bar-desc">{{ 'Run in terminal to install ADB, FFmpeg, scrcpy, and project dependencies:' | t }}</div>
135
+ </div>
136
+ </div>
137
+ <div class="code-box small-box">
138
+ <code>{{ oneClickSetupCmd() }}</code>
139
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'one_click_deps'"
140
+ (click)="copyToClipboard(oneClickSetupCmd(), 'one_click_deps')">
141
+ <span class="material-symbols-outlined">{{ copiedId() === 'one_click_deps' ? 'check' : 'content_copy'
142
+ }}</span>
143
+ <span>{{ copiedId() === 'one_click_deps' ? ('Copied!' | t) : ('Copy' | t) }}</span>
144
+ </button>
145
+ </div>
146
+ </div>
147
+ }
148
+
149
+ <!-- 4-Card Environment Matrix Grid -->
150
+ <div class="env-matrix-grid">
151
+
152
+ <!-- 1. Python Runtime Probe Card -->
153
+ <div class="env-item-card" [class.item-pass]="pythonProbe()?.status === 'pass'"
154
+ [class.item-fail]="pythonProbe()?.status === 'fail'">
155
+ <div class="item-header">
156
+ <div class="item-icon-box">
157
+ <span class="material-symbols-outlined item-icon">terminal</span>
158
+ </div>
159
+ <div class="item-title-group">
160
+ <div class="item-name">{{ 'Python Runtime' | t }}</div>
161
+ <div class="item-subtitle">{{ (pythonProbe()?.summary || ('Checking…' | t)) | t }}</div>
162
+ </div>
163
+ <div class="item-status">
164
+ <span class="status-dot" [class.dot-pass]="pythonProbe()?.status === 'pass'" [class.dot-fail]="pythonProbe()?.status === 'fail'"></span>
165
+ </div>
166
+ </div>
167
+ @if (pythonProbe()?.status === 'fail') {
168
+ <div class="item-body">
169
+ <div class="item-action-box">
170
+ <div class="code-box small-box">
171
+ <span class="terminal-prefix">$</span>
172
+ <code class="code-text">uv python install 3.12</code>
173
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'py_install'"
174
+ (click)="copyToClipboard('uv python install 3.12', 'py_install')">
175
+ <span class="material-symbols-outlined">{{ copiedId() === 'py_install' ? 'check' : 'content_copy'
176
+ }}</span>
177
+ <span>{{ copiedId() === 'py_install' ? ('Copied!' | t) : ('Copy' | t) }}</span>
178
+ </button>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ }
183
+ </div>
184
+
185
+ <!-- 2. Android ADB Platform-Tools Card -->
186
+ <div class="env-item-card" [class.item-pass]="adbProbe()?.metadata?.['installed']"
187
+ [class.item-fail]="adbProbe()?.status === 'fail'">
188
+ <div class="item-header">
189
+ <div class="item-icon-box">
190
+ <span class="material-symbols-outlined item-icon">construction</span>
191
+ </div>
192
+ <div class="item-title-group">
193
+ <div class="item-name">{{ 'Android Platform-Tools' | t }}</div>
194
+ <div class="item-subtitle">{{ !adbProbe() ? ('Checking…' | t) : (adbProbe()?.metadata?.['installed'] ? ('ADB Installed' | t) : ('ADB Missing' | t)) }}</div>
195
+ </div>
196
+ <div class="item-status">
197
+ <span class="status-dot" [class.dot-pass]="adbProbe()?.metadata?.['installed']" [class.dot-fail]="adbProbe()?.status === 'fail'"></span>
198
+ </div>
199
+ </div>
200
+ @if (adbProbe()?.status === 'fail') {
201
+ <div class="item-body">
202
+ <div class="item-action-box">
203
+ <div class="code-box small-box">
204
+ <span class="terminal-prefix">$</span>
205
+ <code class="code-text">{{ adbInstallCmd() }}</code>
206
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'env_adb_cmd'"
207
+ (click)="copyToClipboard(adbInstallCmd(), 'env_adb_cmd')">
208
+ <span class="material-symbols-outlined">{{ copiedId() === 'env_adb_cmd' ? 'check' : 'content_copy'
209
+ }}</span>
210
+ <span>{{ copiedId() === 'env_adb_cmd' ? ('Copied!' | t) : ('Copy' | t) }}</span>
211
+ </button>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ }
216
+ </div>
217
+
218
+ <!-- 3. System Configuration Card -->
219
+ <div class="env-item-card" [class.item-pass]="configProbe()?.status === 'pass'"
220
+ [class.item-fail]="configProbe()?.status === 'fail'">
221
+ <div class="item-header">
222
+ <div class="item-icon-box">
223
+ <span class="material-symbols-outlined item-icon">tune</span>
224
+ </div>
225
+ <div class="item-title-group">
226
+ <div class="item-name">{{ 'System Configuration' | t }}</div>
227
+ <div class="item-subtitle">{{ (configProbe()?.summary || ('Config Valid' | t)) | t }}</div>
228
+ </div>
229
+ <div class="item-status">
230
+ <span class="status-dot" [class.dot-pass]="configProbe()?.status === 'pass'" [class.dot-fail]="configProbe()?.status === 'fail'"></span>
231
+ </div>
232
+ </div>
233
+ @if (configProbe()?.status === 'fail') {
234
+ <div class="item-body">
235
+ <div class="item-action-box">
236
+ <div class="code-box small-box">
237
+ <span class="terminal-prefix">$</span>
238
+ <code class="code-text">artemis init</code>
239
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'cfg_init'"
240
+ (click)="copyToClipboard('artemis init', 'cfg_init')">
241
+ <span class="material-symbols-outlined">{{ copiedId() === 'cfg_init' ? 'check' : 'content_copy'
242
+ }}</span>
243
+ <span>{{ copiedId() === 'cfg_init' ? ('Copied!' | t) : ('Copy' | t) }}</span>
244
+ </button>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ }
249
+ </div>
250
+
251
+ <!-- 4. Video Recording Toolchain Card (FFmpeg & scrcpy) -->
252
+ <div class="env-item-card" [class.item-pass]="toolchainProbe()?.status === 'pass'"
253
+ [class.item-fail]="toolchainProbe()?.status !== 'pass'">
254
+ <div class="item-header">
255
+ <div class="item-icon-box">
256
+ <span class="material-symbols-outlined item-icon">videocam</span>
257
+ </div>
258
+ <div class="item-title-group">
259
+ <div class="item-name">{{ 'Video Toolchain' | t }}</div>
260
+ <div class="item-subtitle">{{ (toolchainProbe()?.summary || ('FFmpeg & scrcpy' | t)) | t }}</div>
261
+ </div>
262
+ <div class="item-status">
263
+ <span class="status-dot" [class.dot-pass]="toolchainProbe()?.status === 'pass'" [class.dot-fail]="toolchainProbe()?.status !== 'pass'"></span>
264
+ </div>
265
+ </div>
266
+ @if (toolchainProbe()?.status !== 'pass') {
267
+ <div class="item-body">
268
+ <div class="item-action-box">
269
+ <div class="code-box small-box">
270
+ <span class="terminal-prefix">$</span>
271
+ <code class="code-text">{{ toolchainInstallCmd() }}</code>
272
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'install_toolchain'"
273
+ (click)="copyToClipboard(toolchainInstallCmd(), 'install_toolchain')">
274
+ <span class="material-symbols-outlined">{{ copiedId() === 'install_toolchain' ? 'check' : 'content_copy'
275
+ }}</span>
276
+ <span>{{ copiedId() === 'install_toolchain' ? ('Copied!' | t) : ('Copy' | t) }}</span>
277
+ </button>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ }
282
+ </div>
283
+
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <!-- =================================================================== -->
289
+ <!-- STEP 2: AI MODEL SETUP -->
290
+ <!-- =================================================================== -->
291
+ <div class="guide-card" [class.card-pass]="isCredentialsReady()" [class.card-warn]="!isCredentialsReady()">
292
+
293
+ <div class="guide-card-header">
294
+ <div class="step-badge" [class.step-pass]="isCredentialsReady()">
295
+ @if (isCredentialsReady()) {
296
+ <span class="material-symbols-outlined step-check-icon">check</span>
297
+ } @else {
298
+ <span>2</span>
299
+ }
300
+ </div>
301
+ <div class="step-info">
302
+ <div class="step-title-row">
303
+ <h3 class="step-title">{{ 'AI Model Setup' | t }}</h3>
304
+ </div>
305
+ <p class="step-desc">{{ 'Google Gemini or custom LLM providers (OpenAI, Claude, Ollama)' | t }}</p>
306
+ </div>
307
+ </div>
308
+
309
+ <div class="guide-card-body">
310
+ <!-- 2 Big Visual Choice Cards -->
311
+ <div class="model-choice-cards-row">
312
+ <!-- Card 1: Gemini (Recommended) -->
313
+ <div class="choice-card" [class.selected]="modelSetupMode() === 'gemini'" (click)="setModelSetupMode('gemini')">
314
+ <div class="choice-card-top">
315
+ <div class="choice-radio">
316
+ <span class="radio-dot" [class.checked]="modelSetupMode() === 'gemini'"></span>
317
+ </div>
318
+ <div class="choice-icon-wrap">
319
+ <svg class="brand-svg gemini-svg" viewBox="0 0 24 24" fill="none">
320
+ <defs>
321
+ <linearGradient id="geminiChoiceGrad" x1="0%" y1="0%" x2="100%" y2="100%">
322
+ <stop offset="0%" stop-color="#4285F4" />
323
+ <stop offset="50%" stop-color="#9B72CB" />
324
+ <stop offset="100%" stop-color="#D96570" />
325
+ </linearGradient>
326
+ </defs>
327
+ <path
328
+ d="M12 2C12 7.523 7.523 12 2 12C7.523 12 12 16.477 12 22C12 16.477 16.477 12 22 12C16.477 12 12 7.523 12 2Z"
329
+ fill="url(#geminiChoiceGrad)" />
330
+ </svg>
331
+ </div>
332
+ <div class="choice-text">
333
+ <div class="choice-title-row">
334
+ <span class="choice-title">{{ 'Google Gemini' | t }}</span>
335
+ <span class="tag-badge tag-rec">{{ 'Recommended' | t }}</span>
336
+ </div>
337
+ <p class="choice-desc">{{ 'Fast multimodal reasoning with free API key from Google AI Studio.' | t }}</p>
338
+ </div>
339
+ </div>
340
+ </div>
341
+
342
+ <!-- Card 2: Custom Configuration -->
343
+ <div class="choice-card" [class.selected]="modelSetupMode() === 'custom'" (click)="setModelSetupMode('custom')">
344
+ <div class="choice-card-top">
345
+ <div class="choice-radio">
346
+ <span class="radio-dot" [class.checked]="modelSetupMode() === 'custom'"></span>
347
+ </div>
348
+ <div class="choice-icon-wrap icon-gear">
349
+ <span class="material-symbols-outlined">tune</span>
350
+ </div>
351
+ <div class="choice-text">
352
+ <div class="choice-title-row">
353
+ <span class="choice-title">{{ 'Custom Configuration' | t }}</span>
354
+ </div>
355
+ <p class="choice-desc">{{ 'Define models in' | t }} <code>config/artemis.jsonc</code> {{ '& keys in' | t }} <code>.env</code>
356
+ {{ '(OpenAI, Claude, Ollama).' | t }}</p>
357
+ </div>
358
+ </div>
359
+ </div>
360
+ </div>
361
+
362
+ <!-- Dynamic Panel Based on Choice -->
363
+ @if (modelSetupMode() === 'gemini') {
364
+ <!-- 1. Gemini API Key Box -->
365
+ <div class="credentials-input-box">
366
+ <div class="input-box-header">
367
+ <div class="header-label">
368
+ <span class="material-symbols-outlined label-icon">vpn_key</span>
369
+ <span>{{ 'Enter Gemini API Key:' | t }}</span>
370
+ </div>
371
+ <a href="https://aistudio.google.com/app/apikey" target="_blank" rel="noopener noreferrer"
372
+ class="btn-header-link">
373
+ <span class="material-symbols-outlined">open_in_new</span>
374
+ <span>{{ 'Get Free API Key (Google AI Studio)' | t }}</span>
375
+ </a>
376
+ </div>
377
+
378
+ <div class="unified-key-row">
379
+ <div class="unified-key-wrapper">
380
+ <span class="material-symbols-outlined key-prefix-icon">key</span>
381
+ <input [type]="showGeminiKey() ? 'text' : 'password'" class="unified-key-input"
382
+ [class.is-masked]="!showGeminiKey()" [ngModel]="geminiKeyInput()"
383
+ (ngModelChange)="onGeminiKeyChange($event)" [placeholder]="'Enter Gemini API Key (e.g. AIzaSy...)' | t"
384
+ spellcheck="false" autocomplete="off" />
385
+
386
+ <!-- Visibility Toggle -->
387
+ <button type="button" class="btn-icon-action" [title]="showGeminiKey() ? ('Mask Key' | t) : ('Show Plaintext' | t)"
388
+ (click)="toggleGeminiKeyVisibility()">
389
+ <span class="material-symbols-outlined">
390
+ {{ showGeminiKey() ? 'visibility_off' : 'visibility' }}
391
+ </span>
392
+ </button>
393
+
394
+ <!-- Copy Button -->
395
+ @if (geminiKeyInput()) {
396
+ <button type="button" class="btn-icon-action" [title]="'Copy API Key' | t"
397
+ (click)="copyToClipboard(geminiKeyInput(), 'gemini_key_field')">
398
+ <span class="material-symbols-outlined" [class.text-emerald]="copiedId() === 'gemini_key_field'">
399
+ {{ copiedId() === 'gemini_key_field' ? 'check' : 'content_copy' }}
400
+ </span>
401
+ </button>
402
+ }
403
+ </div>
404
+
405
+ <!-- Save Button -->
406
+ <button type="button" class="btn-unified-save" [class.btn-dirty]="isGeminiModified()"
407
+ [disabled]="isSavingGeminiKey() || isTestingGeminiKey() || (!isGeminiModified() && !geminiKeyInput().trim())" (click)="saveGeminiKey()">
408
+ @if (isSavingGeminiKey()) {
409
+ <span class="material-symbols-outlined spin">sync</span>
410
+ <span>{{ geminiKeyInput().trim() ? ('Verifying & Saving...' | t) : ('Clearing...' | t) }}</span>
411
+ } @else {
412
+ <span class="material-symbols-outlined">{{ geminiKeyInput().trim() ? 'save' : 'delete_outline' }}</span>
413
+ <span>{{ !geminiKeyInput().trim() ? ('Clear Key' | t) : (isGeminiModified() ? ('Save & Apply' | t) : ('Save Key' | t)) }}</span>
414
+ }
415
+ </button>
416
+
417
+ <!-- Test Button -->
418
+ <button type="button" class="btn-unified-test" [title]="'Test if Gemini API key is valid' | t"
419
+ [disabled]="isTestingGeminiKey() || isSavingGeminiKey() || !geminiKeyInput().trim()" (click)="testGeminiKey()">
420
+ @if (isTestingGeminiKey()) {
421
+ <span class="material-symbols-outlined spin">sync</span>
422
+ <span>{{ 'Testing...' | t }}</span>
423
+ } @else {
424
+ <span class="material-symbols-outlined">network_check</span>
425
+ <span>{{ 'Test Key' | t }}</span>
426
+ }
427
+ </button>
428
+ </div>
429
+
430
+ @if (geminiSaveMessage()) {
431
+ <div class="feedback-msg success">
432
+ <span class="material-symbols-outlined">check_circle</span>
433
+ <span>{{ geminiSaveMessage() | t }}</span>
434
+ </div>
435
+ }
436
+ @if (geminiSaveError()) {
437
+ <div class="feedback-msg error">
438
+ <span class="material-symbols-outlined">error</span>
439
+ <span>{{ geminiSaveError() | t }}</span>
440
+ </div>
441
+ }
442
+ </div>
443
+
444
+ <!-- 2. OCR Enhancement Option for Gemini Mode -->
445
+ <div class="ocr-enhancement-card">
446
+ <div class="ocr-card-toggle-bar" (click)="toggleOcrConfig()">
447
+ <div class="toggle-bar-left">
448
+ <span class="material-symbols-outlined icon-ocr">document_scanner</span>
449
+ <div class="toggle-bar-titles">
450
+ <span class="ocr-main-title">{{ 'Optional: Google Cloud Vision OCR' | t }}</span>
451
+ </div>
452
+ </div>
453
+
454
+ <div class="toggle-bar-right">
455
+ @if (isOcrConfigured()) {
456
+ <span class="status-dot dot-pass" [title]="'OCR Configured' | t"></span>
457
+ }
458
+ <span class="material-symbols-outlined caret-icon">
459
+ {{ showOcrConfig() ? 'expand_less' : 'expand_more' }}
460
+ </span>
461
+ </div>
462
+ </div>
463
+
464
+ @if (showOcrConfig()) {
465
+ <div class="ocr-content-body">
466
+ <p class="ocr-help-desc">
467
+ {{ 'Provides screen text coordinate grounding to improve element detection and interaction accuracy on text-heavy apps.' | t }}
468
+ </p>
469
+
470
+ <!-- Input Box -->
471
+ <div class="credentials-input-box ocr-box">
472
+ <div class="input-box-header">
473
+ <div class="header-label">
474
+ <span class="material-symbols-outlined label-icon">key</span>
475
+ <span>{{ 'Cloud Vision API Key:' | t }}</span>
476
+ </div>
477
+ </div>
478
+
479
+ <div class="unified-key-row">
480
+ <div class="unified-key-wrapper">
481
+ <span class="material-symbols-outlined key-prefix-icon">key</span>
482
+ <input [type]="showOcrKey() ? 'text' : 'password'" class="unified-key-input" [class.is-masked]="!showOcrKey()"
483
+ [ngModel]="ocrKeyInput()" (ngModelChange)="onOcrKeyChange($event)"
484
+ [placeholder]="'Enter Vision OCR API Key (e.g. AIzaSy...)' | t" spellcheck="false" autocomplete="off" />
485
+
486
+ <!-- Visibility Toggle -->
487
+ <button type="button" class="btn-icon-action" [title]="showOcrKey() ? ('Mask Key' | t) : ('Show Plaintext' | t)"
488
+ (click)="toggleOcrKeyVisibility()">
489
+ <span class="material-symbols-outlined">
490
+ {{ showOcrKey() ? 'visibility_off' : 'visibility' }}
491
+ </span>
492
+ </button>
493
+
494
+ <!-- Copy Button -->
495
+ @if (ocrKeyInput()) {
496
+ <button type="button" class="btn-icon-action" [title]="'Copy API Key' | t"
497
+ (click)="copyToClipboard(ocrKeyInput(), 'ocr_key_field')">
498
+ <span class="material-symbols-outlined" [class.text-emerald]="copiedId() === 'ocr_key_field'">
499
+ {{ copiedId() === 'ocr_key_field' ? 'check' : 'content_copy' }}
500
+ </span>
501
+ </button>
502
+ }
503
+ </div>
504
+
505
+ <!-- Save Button -->
506
+ <button type="button" class="btn-unified-save" [class.btn-dirty]="isOcrModified()"
507
+ [disabled]="isSavingOcrKey() || isTestingOcrKey() || (!isOcrModified() && !ocrKeyInput().trim())" (click)="saveOcrKey()">
508
+ @if (isSavingOcrKey()) {
509
+ <span class="material-symbols-outlined spin">sync</span>
510
+ <span>{{ ocrKeyInput().trim() ? ('Verifying & Saving...' | t) : ('Clearing...' | t) }}</span>
511
+ } @else {
512
+ <span class="material-symbols-outlined">{{ ocrKeyInput().trim() ? 'save' : 'delete_outline' }}</span>
513
+ <span>{{ !ocrKeyInput().trim() ? ('Clear Key' | t) : (isOcrModified() ? ('Save & Apply' | t) : ('Save Key' | t)) }}</span>
514
+ }
515
+ </button>
516
+
517
+ <!-- Test Button -->
518
+ <button type="button" class="btn-unified-test" [title]="'Test if Vision OCR key is valid' | t"
519
+ [disabled]="isTestingOcrKey() || isSavingOcrKey() || !ocrKeyInput().trim()" (click)="testOcrKey()">
520
+ @if (isTestingOcrKey()) {
521
+ <span class="material-symbols-outlined spin">sync</span>
522
+ <span>{{ 'Testing...' | t }}</span>
523
+ } @else {
524
+ <span class="material-symbols-outlined">network_check</span>
525
+ <span>{{ 'Test Key' | t }}</span>
526
+ }
527
+ </button>
528
+ </div>
529
+
530
+ @if (ocrSaveMessage()) {
531
+ <div class="feedback-msg success">
532
+ <span class="material-symbols-outlined">check_circle</span>
533
+ <span>{{ ocrSaveMessage() | t }}</span>
534
+ </div>
535
+ }
536
+ @if (ocrSaveError()) {
537
+ <div class="feedback-msg error">
538
+ <span class="material-symbols-outlined">error</span>
539
+ <span>{{ ocrSaveError() | t }}</span>
540
+ </div>
541
+ }
542
+ </div>
543
+ </div>
544
+ }
545
+ </div>
546
+ } @else {
547
+ <!-- CUSTOM CONFIGURATION & .ENV INSPECTOR (NO OCR HERE) -->
548
+ <div class="custom-config-inspector">
549
+
550
+ <!-- Section 1: Active Model Config (artemis.jsonc) -->
551
+ <div class="inspector-card">
552
+ <div class="inspector-card-header">
553
+ <div class="inspector-header-left">
554
+ <span class="material-symbols-outlined header-icon">tune</span>
555
+ <div class="inspector-header-titles">
556
+ <span class="inspector-title">{{ 'Active Model Configuration' | t }}</span>
557
+ <span class="inspector-subtitle">{{ 'Defined in' | t }}
558
+ <code>{{ modelConfigEnv()?.config_path || 'config/artemis.jsonc' }}</code></span>
559
+ </div>
560
+ </div>
561
+ <div class="inspector-header-actions">
562
+ <button type="button" class="btn-file-action" [disabled]="refreshingModelConfig() || editingModelConfig() || savingModelConfig()" (click)="refreshModelConfig()" [title]="'Reload and validate JSONC and model settings from .env' | t">
563
+ <span class="material-symbols-outlined" [class.spinning]="refreshingModelConfig()">refresh</span>
564
+ <span>{{ (refreshingModelConfig() ? 'Refreshing Configuration...' : 'Refresh Configuration') | t }}</span>
565
+ </button>
566
+ @if (editingModelConfig()) {
567
+ <button type="button" class="btn-file-action" [disabled]="savingModelConfig()" (click)="cancelModelConfigEdit()">{{ 'Cancel Editing' | t }}</button>
568
+ <button type="button" class="btn-file-action btn-toggle-code" [disabled]="savingModelConfig() || !modelConfigDraft().trim() || modelConfigDraft() === originalModelConfig()" (click)="saveModelConfig()">{{ (savingModelConfig() ? 'Saving...' : 'Save Configuration') | t }}</button>
569
+ } @else {
570
+ <button type="button" class="btn-file-action btn-toggle-code" [disabled]="refreshingModelConfig() || !modelConfigEnv() || modelConfigEnv()?.config_content?.startsWith('// Error reading config:')" (click)="editModelConfig()">
571
+ <span class="material-symbols-outlined">edit</span><span>{{ 'Edit JSONC' | t }}</span>
572
+ </button>
573
+ }
574
+ <button type="button" class="btn-file-action" [title]="'Copy file path' | t"
575
+ (click)="copyToClipboard(modelConfigEnv()?.config_path || 'config/artemis.jsonc', 'config_path_btn')"
576
+ >
577
+ <span class="material-symbols-outlined" [class.text-emerald]="copiedId() === 'config_path_btn'">
578
+ {{ copiedId() === 'config_path_btn' ? 'check' : 'content_copy' }}
579
+ </span>
580
+ <span>{{ 'Copy Path' | t }}</span>
581
+ </button>
582
+ <button type="button" class="btn-file-action btn-toggle-code" [disabled]="editingModelConfig()" (click)="toggleFullConfigFile()">
583
+ <span class="material-symbols-outlined">
584
+ {{ showFullConfigFile() ? 'unfold_less' : 'code' }}
585
+ </span>
586
+ <span>{{ showFullConfigFile() ? ('Hide JSONC' | t) : ('View JSONC' | t) }}</span>
587
+ </button>
588
+ </div>
589
+ </div>
590
+
591
+ <!-- Model Summary Row -->
592
+ <div class="inspector-summary-row">
593
+ <div class="summary-metric">
594
+ <span class="metric-label">{{ 'Default Model' | t }}</span>
595
+ <div class="metric-val-pill">
596
+ <span class="provider-dot"></span>
597
+ <span class="provider-name">{{ modelConfigEnv()?.default_model?.provider || 'google' }}</span>
598
+ <span class="model-divider">/</span>
599
+ <span class="model-name">{{ modelConfigEnv()?.default_model?.model || 'gemini-3.8-flash' }}</span>
600
+ </div>
601
+ </div>
602
+
603
+ @if (modelConfigEnv()?.default_model?.fallback) {
604
+ <div class="summary-metric">
605
+ <span class="metric-label">{{ 'Fallback Model' | t }}</span>
606
+ <div class="metric-val-pill secondary">
607
+ <span>{{ modelConfigEnv()?.default_model?.fallback?.provider }}/{{
608
+ modelConfigEnv()?.default_model?.fallback?.model }}</span>
609
+ </div>
610
+ </div>
611
+ }
612
+
613
+ </div>
614
+
615
+ <!-- Full JSONC Viewer -->
616
+ @if (modelConfigSaveError()) {
617
+ <p class="config-editor-feedback error" role="alert">{{ modelConfigSaveError()! | t }}</p>
618
+ }
619
+ @if (modelConfigSaveMessage()) {
620
+ <p class="config-editor-feedback success" role="status">{{ modelConfigSaveMessage()! | t }}</p>
621
+ }
622
+ @if (showFullConfigFile()) {
623
+ <div class="jsonc-viewer-box">
624
+ @if (editingModelConfig()) {
625
+ <textarea class="jsonc-editor" [attr.aria-label]="'JSONC Configuration' | t" [ngModel]="modelConfigDraft()" (ngModelChange)="modelConfigDraft.set($event); modelConfigSaveError.set(null)" [disabled]="savingModelConfig()" spellcheck="false"></textarea>
626
+ } @else {
627
+ <pre class="jsonc-code"><code>{{ modelConfigEnv()?.config_content }}</code></pre>
628
+ }
629
+ </div>
630
+ @if (editingModelConfig()) {
631
+ <p class="config-editor-feedback">{{ 'Edit models and node settings here. API keys and endpoint URLs remain in .env.' | t }}</p>
632
+ }
633
+ }
634
+ </div>
635
+
636
+ <!-- Section 2: Environment API Keys (.env) -->
637
+ <div class="inspector-card">
638
+ <div class="inspector-card-header">
639
+ <div class="inspector-header-left">
640
+ <span class="material-symbols-outlined header-icon">vpn_key</span>
641
+ <div class="inspector-header-titles">
642
+ <span class="inspector-title">{{ 'Environment Variables & API Keys' | t }}</span>
643
+ <span class="inspector-subtitle">{{ 'Loaded from' | t }} <code>{{ modelConfigEnv()?.env_path || '.env' }}</code></span>
644
+ </div>
645
+ </div>
646
+
647
+ <button type="button" class="btn-file-action" [title]="'Copy file path' | t"
648
+ (click)="copyToClipboard(modelConfigEnv()?.env_path || '.env', 'env_path_btn')">
649
+ <span class="material-symbols-outlined" [class.text-emerald]="copiedId() === 'env_path_btn'">
650
+ {{ copiedId() === 'env_path_btn' ? 'check' : 'content_copy' }}
651
+ </span>
652
+ <span>{{ 'Copy Path' | t }}</span>
653
+ </button>
654
+ </div>
655
+
656
+ <!-- Env Keys Table -->
657
+ <div class="env-keys-table">
658
+ @for (envItem of modelConfigEnv()?.env_vars || []; track envItem.name) {
659
+ <div class="env-key-row" [class.is-configured]="envItem.is_set">
660
+ <div class="env-name-col">
661
+ <code class="env-key-name">{{ envItem.name }}</code>
662
+ </div>
663
+ <div class="env-status-col">
664
+ @if (envItem.is_set) {
665
+ <span class="badge-env-pass">
666
+ <span class="material-symbols-outlined">check_circle</span>
667
+ <span>{{ 'Configured' | t }}</span>
668
+ @if (envItem.preview) {
669
+ <code class="key-preview">({{ envItem.preview }})</code>
670
+ }
671
+ </span>
672
+ } @else {
673
+ <span class="badge-env-unset">
674
+ <span class="material-symbols-outlined">radio_button_unchecked</span>
675
+ <span>{{ 'Not Set' | t }}</span>
676
+ </span>
677
+ }
678
+ </div>
679
+ </div>
680
+ }
681
+ </div>
682
+ </div>
683
+
684
+ </div>
685
+ }
686
+ </div>
687
+ </div>
688
+
689
+ <!-- =================================================================== -->
690
+ <!-- STEP 3: ANDROID DEVICE & EMULATOR CONNECTION -->
691
+ <!-- =================================================================== -->
692
+ <div class="guide-card" [class.card-pass]="isDeviceReady()" [class.card-warn]="!isDeviceReady()">
693
+
694
+ <div class="guide-card-header">
695
+ <div class="step-badge" [class.step-pass]="isDeviceReady()">
696
+ @if (isDeviceReady()) {
697
+ <span class="material-symbols-outlined step-check-icon">check</span>
698
+ } @else {
699
+ <span>3</span>
700
+ }
701
+ </div>
702
+ <div class="step-info">
703
+ <div class="step-title-row">
704
+ <h3 class="step-title">{{ 'Android Device & Emulator Connection' | t }}</h3>
705
+ </div>
706
+ <p class="step-desc">{{ 'Local emulator, attached phone, wireless device, or another ADB server' | t }}</p>
707
+ </div>
708
+
709
+ <button
710
+ type="button"
711
+ class="btn-restart-adb btn-connection-options"
712
+ (click)="toggleConnectionMethods()"
713
+ >
714
+ <span class="material-symbols-outlined">swap_horiz</span>
715
+ <span>{{ showConnectionMethods() ? ('Hide Options' | t) : ('Change Connection' | t) }}</span>
716
+ </button>
717
+
718
+ <button
719
+ type="button"
720
+ class="btn-restart-adb"
721
+ [disabled]="isRestartingAdb()"
722
+ (click)="restartAdbServer()"
723
+ [title]="isRemoteAdbServer() ? ('Refresh devices without stopping the remote ADB server' | t) : ('Restart local ADB daemon' | t)"
724
+ >
725
+ @if (isRestartingAdb()) {
726
+ <span class="material-symbols-outlined spinning">sync</span>
727
+ <span>{{ 'Restarting...' | t }}</span>
728
+ } @else if (adbRestartFeedback()) {
729
+ <span class="material-symbols-outlined feedback-done">check</span>
730
+ <span class="feedback-text">{{ adbRestartFeedback() | t }}</span>
731
+ } @else {
732
+ <span class="material-symbols-outlined">sync</span>
733
+ <span>{{ isRemoteAdbServer() ? ('Refresh Devices' | t) : ('Restart ADB' | t) }}</span>
734
+ }
735
+ </button>
736
+ </div>
737
+
738
+ <div class="guide-card-body">
739
+
740
+ <!-- STATE A: CONNECTED & READY -->
741
+ @if (isDeviceReady()) {
742
+ <div class="connected-device-panel">
743
+ <div class="device-hero-card">
744
+ <div class="device-avatar">
745
+ <span class="material-symbols-outlined device-hero-icon">
746
+ phone_android
747
+ </span>
748
+ </div>
749
+ <div class="device-hero-details">
750
+ <div class="device-hero-name">
751
+ {{ activeDevice()?.model || activeDevice()?.serial }}
752
+ </div>
753
+ <div class="device-hero-meta">
754
+ @if (activeDevice()?.is_emulator) {
755
+ <span>{{ 'Virtual Device' | t }}</span>
756
+ <span class="meta-dot">·</span>
757
+ }
758
+ <span>{{ activeDevice()?.serial }}</span>
759
+ @if (activeDevice()?.screen_resolution) {
760
+ <span class="meta-dot">·</span>
761
+ <span>{{ activeDevice()?.screen_resolution }}</span>
762
+ }
763
+ @if (activeDevice()?.android_version) {
764
+ <span class="meta-dot">·</span>
765
+ <span>{{ 'Android' | t }} {{ activeDevice()?.android_version }}</span>
766
+ }
767
+ @if (isRemoteAdbServer()) {
768
+ <span class="meta-dot">·</span>
769
+ <span class="remote-endpoint-label">
770
+ {{ 'Remote via' | t }} {{ adbServerStatus()?.endpoint?.host }}:{{ adbServerStatus()?.endpoint?.port }}
771
+ </span>
772
+ }
773
+ </div>
774
+ </div>
775
+ <div class="device-hero-status">
776
+ <span class="status-dot dot-pass"></span>
777
+ <span class="status-text">{{ 'Connected' | t }}</span>
778
+ </div>
779
+ </div>
780
+
781
+ <!-- Multi-Device Switcher if multiple devices detected -->
782
+ @if (connectedDevices().length > 1) {
783
+ <div class="multi-device-switcher">
784
+ <span class="switcher-label">{{ 'Select Active Device:' | t }}</span>
785
+ <div class="switcher-chips">
786
+ @for (dev of connectedDevices(); track dev.serial) {
787
+ <button
788
+ type="button"
789
+ class="device-chip-btn"
790
+ [class.selected]="dev.serial === activeDevice()?.serial"
791
+ (click)="selectTargetDevice(dev.serial)"
792
+ >
793
+ <span class="material-symbols-outlined chip-icon">
794
+ phone_android
795
+ </span>
796
+ <span>{{ dev.model || dev.serial }}</span>
797
+ @if (dev.serial === activeDevice()?.serial) {
798
+ <span class="active-check">{{ '✓ Active' | t }}</span>
799
+ }
800
+ </button>
801
+ }
802
+ </div>
803
+ </div>
804
+ }
805
+ </div>
806
+ }
807
+
808
+ <!-- STATE B-1: EMULATOR LAUNCHING / DEVICE BOOTING LIVE TRACKER -->
809
+ @if (isEmulatorLaunching() || (!isDeviceReady() && adbProbe()?.summary === 'Device Booting')) {
810
+ <div class="emulator-boot-tracker-container">
811
+ <div class="emulator-boot-tracker-card">
812
+ <div class="tracker-header">
813
+ <div class="tracker-title-group">
814
+ <div class="tracker-icon-box">
815
+ <span class="material-symbols-outlined spinning-icon">sync</span>
816
+ </div>
817
+ <div class="tracker-titles">
818
+ <div class="tracker-main-heading">
819
+ <h4 class="tracker-title">
820
+ {{ 'Launching Android Emulator:' | t }} <span class="avd-highlight">{{ emulatorLaunchState()?.avd_name || activeDevice()?.serial || ('Virtual Device' | t) }}</span>
821
+ </h4>
822
+ <span class="stage-tag-badge">{{ emulatorLaunchState()?.status === 'booting' ? ('OS Booting' | t) : ('Starting' | t) }}</span>
823
+ </div>
824
+ <p class="tracker-subtitle">{{ emulatorLaunchState()?.stage_message || ('Initializing Android Virtual Device...' | t) }}</p>
825
+ </div>
826
+ </div>
827
+ <div class="tracker-actions">
828
+ <button type="button" class="btn-toggle-logs" [class.active]="showLaunchLogs()" (click)="toggleLaunchLogs()">
829
+ <span class="material-symbols-outlined">terminal</span>
830
+ <span>{{ showLaunchLogs() ? ('Hide Logs' | t) : ('View Boot Logs' | t) }}</span>
831
+ </button>
832
+ <button type="button" class="btn-stop-emu" (click)="stopEmulator()" [title]="'Terminate emulator process' | t">
833
+ <span class="material-symbols-outlined">cancel</span>
834
+ <span>{{ 'Stop' | t }}</span>
835
+ </button>
836
+ </div>
837
+ </div>
838
+
839
+ <!-- Boot Stepper Progress Flow -->
840
+ <div class="boot-stepper">
841
+ <div class="step-item" [class.done]="emulatorLaunchState()?.progress_percent! >= 35" [class.active]="emulatorLaunchState()?.status === 'starting'">
842
+ <div class="step-circle">
843
+ @if (emulatorLaunchState()?.progress_percent! >= 35) {
844
+ <span class="material-symbols-outlined check-icon">check</span>
845
+ } @else {
846
+ <span class="spinner-tiny"></span>
847
+ }
848
+ </div>
849
+ <span class="step-label">{{ '1. Process Start' | t }}</span>
850
+ </div>
851
+
852
+ <div class="step-line" [class.done]="emulatorLaunchState()?.progress_percent! >= 35"></div>
853
+
854
+ <div class="step-item" [class.done]="emulatorLaunchState()?.progress_percent! >= 65" [class.active]="emulatorLaunchState()?.status === 'waiting_for_adb'">
855
+ <div class="step-circle">
856
+ @if (emulatorLaunchState()?.progress_percent! >= 65) {
857
+ <span class="material-symbols-outlined check-icon">check</span>
858
+ } @else if (emulatorLaunchState()?.status === 'waiting_for_adb') {
859
+ <span class="spinner-tiny"></span>
860
+ } @else {
861
+ <span>2</span>
862
+ }
863
+ </div>
864
+ <span class="step-label">{{ '2. ADB Handshake' | t }}</span>
865
+ </div>
866
+
867
+ <div class="step-line" [class.done]="emulatorLaunchState()?.progress_percent! >= 65"></div>
868
+
869
+ <div class="step-item" [class.done]="emulatorLaunchState()?.status === 'ready'" [class.active]="emulatorLaunchState()?.status === 'booting' || adbProbe()?.summary === 'Device Booting'">
870
+ <div class="step-circle">
871
+ @if (emulatorLaunchState()?.status === 'ready') {
872
+ <span class="material-symbols-outlined check-icon">check</span>
873
+ } @else if (emulatorLaunchState()?.status === 'booting' || adbProbe()?.summary === 'Device Booting') {
874
+ <span class="spinner-tiny"></span>
875
+ } @else {
876
+ <span>3</span>
877
+ }
878
+ </div>
879
+ <span class="step-label">{{ '3. Android OS Boot' | t }}</span>
880
+ </div>
881
+
882
+ <div class="step-line" [class.done]="emulatorLaunchState()?.status === 'ready'"></div>
883
+
884
+ <div class="step-item" [class.done]="emulatorLaunchState()?.status === 'ready'">
885
+ <div class="step-circle">
886
+ <span class="material-symbols-outlined check-icon">check_circle</span>
887
+ </div>
888
+ <span class="step-label">{{ '4. Ready' | t }}</span>
889
+ </div>
890
+ </div>
891
+
892
+ <!-- Animated Progress Bar -->
893
+ <div class="progress-bar-container">
894
+ <div class="progress-bar-track">
895
+ <div class="progress-bar-fill" [style.width.%]="emulatorLaunchState()?.progress_percent || 25"></div>
896
+ </div>
897
+ <div class="progress-bar-meta">
898
+ <span class="progress-percent">{{ emulatorLaunchState()?.progress_percent || 25 }}{{ '% Complete' | t }}</span>
899
+ <span class="progress-timer">
900
+ <span class="material-symbols-outlined timer-icon">timer</span>
901
+ {{ 'Elapsed:' | t }} {{ emulatorLaunchState()?.elapsed_seconds || 0 }}{{ 's' | t }}
902
+ </span>
903
+ </div>
904
+ </div>
905
+
906
+ <!-- Helpful Status Notice -->
907
+ <div class="boot-tip-banner">
908
+ <span class="material-symbols-outlined tip-icon">info</span>
909
+ <span>{{ 'Android virtualization cold boot usually takes 20-45 seconds. Live progress is actively tracked above—please do not click launch repeatedly.' | t }}</span>
910
+ </div>
911
+
912
+ <!-- Expandable Console Log Stream -->
913
+ @if (showLaunchLogs()) {
914
+ <div class="boot-logs-drawer">
915
+ <div class="logs-header">
916
+ <div class="logs-header-left">
917
+ <span class="material-symbols-outlined">terminal</span>
918
+ <span>{{ 'Emulator Console Stream (stdout / stderr)' | t }}</span>
919
+ </div>
920
+ <button type="button" class="btn-copy-logs" (click)="copyToClipboard(emulatorLaunchState()?.logs?.join('\n') || '', 'boot_logs')">
921
+ <span class="material-symbols-outlined">{{ copiedId() === 'boot_logs' ? 'check' : 'content_copy' }}</span>
922
+ <span>{{ copiedId() === 'boot_logs' ? ('Copied' | t) : ('Copy Logs' | t) }}</span>
923
+ </button>
924
+ </div>
925
+ <pre class="logs-body">@if (emulatorLaunchState()?.logs?.length) {@for (log of emulatorLaunchState()?.logs; track log) {<div class="log-line">{{ log }}</div>}} @else {<div class="log-empty">{{ 'Waiting for process output...' | t }}</div>}</pre>
926
+ </div>
927
+ }
928
+ </div>
929
+ </div>
930
+ }
931
+
932
+ <!-- STATE B-2: EMULATOR LAUNCH FAILED DIAGNOSTIC CARD -->
933
+ @if (!isDeviceReady() && !isEmulatorLaunching() && emulatorLaunchState()?.status === 'failed') {
934
+ <div class="emulator-failed-container">
935
+ <div class="emulator-failed-card">
936
+ <div class="failed-header">
937
+ <div class="failed-header-left">
938
+ <div class="failed-icon-box">
939
+ <span class="material-symbols-outlined failed-icon">error</span>
940
+ </div>
941
+ <div class="failed-titles">
942
+ <h4 class="failed-title">{{ 'Emulator Launch Failed' | t }}</h4>
943
+ <p class="failed-desc">{{ emulatorLaunchState()?.error || ('The Android emulator process failed to start or crashed during initialization.' | t) }}</p>
944
+ </div>
945
+ </div>
946
+ <button type="button" class="btn-dismiss-failed" (click)="dismissEmulatorStatus()" [title]="'Dismiss error' | t">
947
+ <span class="material-symbols-outlined">close</span>
948
+ </button>
949
+ </div>
950
+
951
+ @if (emulatorLaunchState()?.logs?.length) {
952
+ <div class="failed-logs-box">
953
+ <div class="logs-header">
954
+ <span>{{ 'Process Output / Failure Logs:' | t }}</span>
955
+ <button type="button" class="btn-copy-logs" (click)="copyToClipboard(emulatorLaunchState()?.logs?.join('\n') || '', 'fail_logs')">
956
+ <span class="material-symbols-outlined">{{ copiedId() === 'fail_logs' ? 'check' : 'content_copy' }}</span>
957
+ <span>{{ copiedId() === 'fail_logs' ? ('Copied' | t) : ('Copy Logs' | t) }}</span>
958
+ </button>
959
+ </div>
960
+ <pre class="failed-logs-pre">@for (log of emulatorLaunchState()?.logs; track log) {<div class="log-line">{{ log }}</div>}</pre>
961
+ </div>
962
+ }
963
+
964
+ <div class="failed-actions-bar">
965
+ @if (emulatorLaunchState()?.avd_name) {
966
+ <button type="button" class="btn-retry-launch" [disabled]="isRemoteAdbServer()" (click)="launchAvdEmulator(emulatorLaunchState()!.avd_name!)">
967
+ <span class="material-symbols-outlined">refresh</span>
968
+ <span>{{ 'Retry Launch (' | t }}{{ emulatorLaunchState()?.avd_name }})</span>
969
+ </button>
970
+ }
971
+ <button type="button" class="btn-restart-adb-sub" (click)="restartAdbServer()">
972
+ <span class="material-symbols-outlined">sync</span>
973
+ <span>{{ 'Restart ADB Server' | t }}</span>
974
+ </button>
975
+ <button type="button" class="btn-dismiss-flat" (click)="dismissEmulatorStatus()">
976
+ <span>{{ 'Dismiss' | t }}</span>
977
+ </button>
978
+ </div>
979
+ </div>
980
+ </div>
981
+ }
982
+
983
+ <!-- STATE B-3: NO DEVICE DETECTED / READY TO CONNECT GUIDE -->
984
+ @if (showConnectionMethods() || (!isDeviceReady() && !isEmulatorLaunching() && adbProbe()?.summary !== 'Device Booting' && adbProbe()?.summary !== 'Device Unauthorized' && adbProbe()?.summary !== 'Device Locked' && adbProbe()?.summary !== 'Lock State Unknown' && adbProbe()?.status !== 'fail' && emulatorLaunchState()?.status !== 'failed')) {
985
+ <div class="foolproof-guide-container">
986
+ <!-- Method Selection Nav Cards -->
987
+ <div class="connection-methods-nav">
988
+ <button
989
+ type="button"
990
+ class="method-nav-card"
991
+ [class.active]="activeAdbGuideTab() === 'emulator'"
992
+ (click)="setAdbGuideTab('emulator')"
993
+ >
994
+ <div class="method-card-left">
995
+ <div class="method-icon-box">
996
+ <span class="material-symbols-outlined">smartphone</span>
997
+ </div>
998
+ <div class="method-info">
999
+ <div class="method-title-row">
1000
+ <span class="method-title">{{ 'Android Emulator' | t }}</span>
1001
+ </div>
1002
+ <span class="method-desc">
1003
+ @if (installedAvds().length > 0) {
1004
+ {{ installedAvds().length }} {{ 'local virtual device' | t }}{{ language.language() === 'en' && installedAvds().length > 1 ? 's' : '' }} {{ 'available' | t }}
1005
+ } @else {
1006
+ {{ 'Launch a local Android Virtual Device' | t }}
1007
+ }
1008
+ </span>
1009
+ </div>
1010
+ </div>
1011
+ <div class="method-card-right">
1012
+ <span class="radio-indicator" [class.checked]="activeAdbGuideTab() === 'emulator'"></span>
1013
+ </div>
1014
+ </button>
1015
+
1016
+ <button
1017
+ type="button"
1018
+ class="method-nav-card"
1019
+ [class.active]="activeAdbGuideTab() === 'usb'"
1020
+ (click)="setAdbGuideTab('usb')"
1021
+ >
1022
+ <div class="method-card-left">
1023
+ <div class="method-icon-box">
1024
+ <span class="material-symbols-outlined">usb</span>
1025
+ </div>
1026
+ <div class="method-info">
1027
+ <div class="method-title-row">
1028
+ <span class="method-title">{{ 'USB Cable' | t }}</span>
1029
+ </div>
1030
+ <span class="method-desc">{{ 'Connect physical phone via USB' | t }}</span>
1031
+ </div>
1032
+ </div>
1033
+ <div class="method-card-right">
1034
+ <span class="radio-indicator" [class.checked]="activeAdbGuideTab() === 'usb'"></span>
1035
+ </div>
1036
+ </button>
1037
+
1038
+ <button
1039
+ type="button"
1040
+ class="method-nav-card"
1041
+ [class.active]="activeAdbGuideTab() === 'wifi'"
1042
+ (click)="setAdbGuideTab('wifi')"
1043
+ >
1044
+ <div class="method-card-left">
1045
+ <div class="method-icon-box">
1046
+ <span class="material-symbols-outlined">wifi</span>
1047
+ </div>
1048
+ <div class="method-info">
1049
+ <div class="method-title-row">
1050
+ <span class="method-title">{{ 'Wireless ADB' | t }}</span>
1051
+ </div>
1052
+ <span class="method-desc">{{ 'Connect over local Wi-Fi' | t }}</span>
1053
+ </div>
1054
+ </div>
1055
+ <div class="method-card-right">
1056
+ <span class="radio-indicator" [class.checked]="activeAdbGuideTab() === 'wifi'"></span>
1057
+ </div>
1058
+ </button>
1059
+
1060
+ <button
1061
+ type="button"
1062
+ class="method-nav-card"
1063
+ [class.active]="activeAdbGuideTab() === 'remote'"
1064
+ (click)="setAdbGuideTab('remote')"
1065
+ >
1066
+ <div class="method-card-left">
1067
+ <div class="method-icon-box">
1068
+ <span class="material-symbols-outlined">devices</span>
1069
+ </div>
1070
+ <div class="method-info">
1071
+ <div class="method-title-row">
1072
+ <span class="method-title">{{ 'Remote Computer' | t }}</span>
1073
+ </div>
1074
+ <span class="method-desc">{{ 'Phone connected to another computer' | t }}</span>
1075
+ </div>
1076
+ </div>
1077
+ <div class="method-card-right">
1078
+ <span class="radio-indicator" [class.checked]="activeAdbGuideTab() === 'remote'"></span>
1079
+ </div>
1080
+ </button>
1081
+ </div>
1082
+
1083
+ @if (activeAdbGuideTab() !== 'remote' && isRemoteAdbServer()) {
1084
+ <div class="active-remote-notice">
1085
+ <div class="active-remote-copy">
1086
+ <span class="material-symbols-outlined">hub</span>
1087
+ <span>
1088
+ {{ 'Artemis is currently using' | t }}
1089
+ <strong>{{ adbServerStatus()?.endpoint?.host }}:{{ adbServerStatus()?.endpoint?.port }}</strong>{{ '. Switch to local ADB before using a local emulator, USB cable, or Wireless ADB.' | t }}
1090
+ </span>
1091
+ </div>
1092
+ <button
1093
+ type="button"
1094
+ class="btn-use-local-adb"
1095
+ [disabled]="isSwitchingToLocalAdb()"
1096
+ (click)="switchToLocalAdbServer()"
1097
+ >
1098
+ <span class="material-symbols-outlined" [class.spinning]="isSwitchingToLocalAdb()">sync</span>
1099
+ <span>{{ isSwitchingToLocalAdb() ? ('Switching...' | t) : ('Use Local ADB' | t) }}</span>
1100
+ </button>
1101
+ </div>
1102
+ }
1103
+
1104
+ <!-- Method 1: Emulator -->
1105
+ @if (activeAdbGuideTab() === 'emulator') {
1106
+ <div class="method-details-panel">
1107
+
1108
+ @if (installedAvds().length > 0) {
1109
+ <div class="avds-section">
1110
+ <div class="section-header-bar">
1111
+ <div class="section-title-group">
1112
+ <span class="material-symbols-outlined header-icon">devices</span>
1113
+ <span class="section-title">{{ 'Installed Virtual Devices (AVD)' | t }}</span>
1114
+ <span class="count-badge">{{ installedAvds().length }} {{ 'Available' | t }}</span>
1115
+ </div>
1116
+ </div>
1117
+
1118
+ <div class="avds-list">
1119
+ @for (avd of installedAvds(); track avd) {
1120
+ <div class="avd-card-item">
1121
+ <div class="avd-item-main">
1122
+ <div class="avd-avatar">
1123
+ <span class="material-symbols-outlined">smartphone</span>
1124
+ </div>
1125
+ <div class="avd-meta">
1126
+ <div class="avd-heading">
1127
+ <span class="avd-name">{{ avd }}</span>
1128
+ <span class="avd-type-pill">{{ 'Virtual Device' | t }}</span>
1129
+ </div>
1130
+ </div>
1131
+ </div>
1132
+
1133
+ <div class="avd-item-actions">
1134
+ <button
1135
+ type="button"
1136
+ class="btn-avd-launch"
1137
+ [class.launching]="launchingAvd() === avd || (isEmulatorLaunching() && emulatorLaunchState()?.avd_name === avd)"
1138
+ [disabled]="isEmulatorLaunching() || isRemoteAdbServer()"
1139
+ (click)="launchAvdEmulator(avd)"
1140
+ [title]="isEmulatorLaunching() ? ('An emulator is currently launching...' | t) : ('Launch ' | t) + avd"
1141
+ >
1142
+ @if (launchingAvd() === avd || (isEmulatorLaunching() && emulatorLaunchState()?.avd_name === avd)) {
1143
+ <span class="spinner-icon"></span>
1144
+ <span>{{ emulatorLaunchState()?.status === 'booting' ? ('Booting OS' | t) : ('Starting' | t) }} ({{ emulatorLaunchState()?.elapsed_seconds || 0 }}{{ 's)' | t }}</span>
1145
+ } @else {
1146
+ <span class="material-symbols-outlined action-icon">play_arrow</span>
1147
+ <span>{{ 'Launch' | t }}</span>
1148
+ }
1149
+ </button>
1150
+ </div>
1151
+ </div>
1152
+ }
1153
+ </div>
1154
+ </div>
1155
+ } @else {
1156
+ <div class="emulator-setup-guide-container">
1157
+ <div class="empty-avd-banner">
1158
+ <div class="banner-left">
1159
+ <div class="empty-avd-icon-box">
1160
+ <span class="material-symbols-outlined">devices_off</span>
1161
+ </div>
1162
+ <div class="banner-titles">
1163
+ <div class="banner-title-row">
1164
+ <h4 class="empty-title">{{ 'No Android Virtual Devices (AVD) Detected' | t }}</h4>
1165
+ <div class="os-selector-pills">
1166
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'linux'"
1167
+ (click)="setSelectedOs('linux')">{{ 'Linux' | t }}</button>
1168
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'darwin'"
1169
+ (click)="setSelectedOs('darwin')">{{ 'macOS' | t }}</button>
1170
+ <button type="button" class="os-pill" [class.active]="effectiveOs() === 'windows'"
1171
+ (click)="setSelectedOs('windows')">{{ 'Windows' | t }}</button>
1172
+ </div>
1173
+ </div>
1174
+ <p class="empty-desc">{{ 'Follow the step-by-step instructions below to install and start an emulator:' | t }}</p>
1175
+ </div>
1176
+ </div>
1177
+
1178
+ <div class="mode-switch-pills">
1179
+ <button
1180
+ type="button"
1181
+ class="pill-btn"
1182
+ [class.active]="emulatorSetupMode() === 'studio'"
1183
+ (click)="setEmulatorSetupMode('studio')"
1184
+ >
1185
+ <span class="material-symbols-outlined pill-icon">desktop_windows</span>
1186
+ <span>{{ 'Android Studio (GUI)' | t }}</span>
1187
+ </button>
1188
+ <button
1189
+ type="button"
1190
+ class="pill-btn"
1191
+ [class.active]="emulatorSetupMode() === 'cli'"
1192
+ (click)="setEmulatorSetupMode('cli')"
1193
+ >
1194
+ <span class="material-symbols-outlined pill-icon">terminal</span>
1195
+ <span>{{ 'Command Line (CLI)' | t }}</span>
1196
+ </button>
1197
+ </div>
1198
+ </div>
1199
+
1200
+ <!-- Option 1: Android Studio Guide -->
1201
+ @if (emulatorSetupMode() === 'studio') {
1202
+ <div class="steps-flow-grid emu-flow-grid">
1203
+ <div class="flow-step-card">
1204
+ <div class="step-card-header">
1205
+ <span class="step-number-badge">1</span>
1206
+ <span class="material-symbols-outlined step-card-icon">download</span>
1207
+ </div>
1208
+ <div class="step-card-title">{{ 'Install Android Studio' | t }}</div>
1209
+ <div class="step-card-desc">
1210
+ {{ 'Download for' | t }} {{ effectiveOs() === 'darwin' ? ('macOS' | t) : (effectiveOs() === 'windows' ? ('Windows' | t) : ('Linux' | t)) }} {{ 'from' | t }} <a href="https://developer.android.com/studio" target="_blank" rel="noopener" class="guide-link">{{ 'developer.android.com/studio' | t }}</a> {{ 'and install.' | t }}
1211
+ </div>
1212
+ </div>
1213
+
1214
+ <div class="flow-step-card">
1215
+ <div class="step-card-header">
1216
+ <span class="step-number-badge">2</span>
1217
+ <span class="material-symbols-outlined step-card-icon">developer_mode</span>
1218
+ </div>
1219
+ <div class="step-card-title">{{ 'Open Device Manager' | t }}</div>
1220
+ <div class="step-card-desc">
1221
+ {{ 'On Studio welcome screen, open' | t }} <strong>{{ 'More Actions &gt; Virtual Device Manager' | t }}</strong> {{ '(or' | t }} <strong>{{ 'Tools &gt; Device Manager' | t }}</strong>).
1222
+ </div>
1223
+ </div>
1224
+
1225
+ <div class="flow-step-card">
1226
+ <div class="step-card-header">
1227
+ <span class="step-number-badge">3</span>
1228
+ <span class="material-symbols-outlined step-card-icon">add_circle</span>
1229
+ </div>
1230
+ <div class="step-card-title">{{ 'Create Virtual Device' | t }}</div>
1231
+ <div class="step-card-desc">
1232
+ {{ 'Click' | t }} <strong>{{ 'Create Device' | t }}</strong>{{ ', choose' | t }} <strong>{{ 'Pixel 8' | t }}</strong>{{ ', select' | t }} <strong>{{ 'Android 14 (API 34)' | t }}</strong>{{ ', and click' | t }} <strong>{{ 'Finish' | t }}</strong>.
1233
+ </div>
1234
+ </div>
1235
+
1236
+ <div class="flow-step-card">
1237
+ <div class="step-card-header">
1238
+ <span class="step-number-badge">4</span>
1239
+ <span class="material-symbols-outlined step-card-icon">play_arrow</span>
1240
+ </div>
1241
+ <div class="step-card-title">{{ 'Launch Emulator' | t }}</div>
1242
+ <div class="step-card-desc">
1243
+ {{ 'Click ▶' | t }} <strong>{{ 'Play' | t }}</strong> {{ 'in Device Manager. Artemis will detect and connect to it automatically!' | t }}
1244
+ </div>
1245
+ </div>
1246
+ </div>
1247
+ }
1248
+
1249
+ <!-- Option 2: Command Line Guide -->
1250
+ @if (emulatorSetupMode() === 'cli') {
1251
+ <div class="cli-steps-list">
1252
+ <div class="cli-step-item">
1253
+ <div class="step-badge-num">1</div>
1254
+ <div class="step-content">
1255
+ <div class="step-title">{{ emuHypervisorTitle() | t }}</div>
1256
+ <p class="step-text">{{ emuHypervisorDesc() | t }}</p>
1257
+ <div class="code-box small-box">
1258
+ <span class="terminal-prefix">{{ effectiveOs() === 'windows' ? ('PS>' | t) : '$' }}</span>
1259
+ <code class="code-text">{{ emuHypervisorCmd() }}</code>
1260
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'emu_kvm'"
1261
+ (click)="copyToClipboard(emuHypervisorCmd(), 'emu_kvm')">
1262
+ <span class="material-symbols-outlined">{{ copiedId() === 'emu_kvm' ? 'check' : 'content_copy' }}</span>
1263
+ <span>{{ copiedId() === 'emu_kvm' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1264
+ </button>
1265
+ </div>
1266
+ </div>
1267
+ </div>
1268
+
1269
+ <div class="cli-step-item">
1270
+ <div class="step-badge-num">2</div>
1271
+ <div class="step-content">
1272
+ <div class="step-title">{{ 'Download Emulator & System Image (API 34)' | t }}</div>
1273
+ <p class="step-text">{{ 'Install Android 14 Google APIs system image using SDK Manager:' | t }}</p>
1274
+ <div class="code-box small-box">
1275
+ <span class="terminal-prefix">{{ effectiveOs() === 'windows' ? ('PS>' | t) : '$' }}</span>
1276
+ <code class="code-text">{{ emuSdkInstallCmd() }}</code>
1277
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'emu_sdk'"
1278
+ (click)="copyToClipboard(emuSdkInstallCmd(), 'emu_sdk')">
1279
+ <span class="material-symbols-outlined">{{ copiedId() === 'emu_sdk' ? 'check' : 'content_copy' }}</span>
1280
+ <span>{{ copiedId() === 'emu_sdk' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1281
+ </button>
1282
+ </div>
1283
+ </div>
1284
+ </div>
1285
+
1286
+ <div class="cli-step-item">
1287
+ <div class="step-badge-num">3</div>
1288
+ <div class="step-content">
1289
+ <div class="step-title">{{ 'Create Virtual Device (Pixel 8 AVD)' | t }}</div>
1290
+ <p class="step-text">{{ 'Initialize a new Android Virtual Device named' | t }} <code>Pixel_8_API_34</code>:</p>
1291
+ <div class="code-box small-box">
1292
+ <span class="terminal-prefix">{{ effectiveOs() === 'windows' ? ('PS>' | t) : '$' }}</span>
1293
+ <code class="code-text">{{ emuCreateAvdCmd() }}</code>
1294
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'emu_create'"
1295
+ (click)="copyToClipboard(emuCreateAvdCmd(), 'emu_create')">
1296
+ <span class="material-symbols-outlined">{{ copiedId() === 'emu_create' ? 'check' : 'content_copy' }}</span>
1297
+ <span>{{ copiedId() === 'emu_create' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1298
+ </button>
1299
+ </div>
1300
+ </div>
1301
+ </div>
1302
+
1303
+ <div class="cli-step-item">
1304
+ <div class="step-badge-num">4</div>
1305
+ <div class="step-content">
1306
+ <div class="step-title">{{ 'Start the Emulator' | t }}</div>
1307
+ <p class="step-text">{{ 'Launch the virtual device (or add' | t }} <code>-no-window</code> {{ 'for headless execution):' | t }}</p>
1308
+ <div class="code-box small-box">
1309
+ <span class="terminal-prefix">{{ effectiveOs() === 'windows' ? ('PS>' | t) : '$' }}</span>
1310
+ <code class="code-text">emulator -avd Pixel_8_API_34 -no-boot-anim</code>
1311
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'emu_run'"
1312
+ (click)="copyToClipboard('emulator -avd Pixel_8_API_34 -no-boot-anim', 'emu_run')">
1313
+ <span class="material-symbols-outlined">{{ copiedId() === 'emu_run' ? 'check' : 'content_copy' }}</span>
1314
+ <span>{{ copiedId() === 'emu_run' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1315
+ </button>
1316
+ </div>
1317
+ </div>
1318
+ </div>
1319
+ </div>
1320
+ }
1321
+ </div>
1322
+ }
1323
+
1324
+ </div>
1325
+ }
1326
+
1327
+ <!-- Method 2: Physical Phone via USB -->
1328
+ @if (activeAdbGuideTab() === 'usb') {
1329
+ <div class="method-details-panel">
1330
+ <div class="usb-guide-intro">
1331
+ <span class="material-symbols-outlined intro-icon">cable</span>
1332
+ <span>{{ 'Connect your Android phone via USB cable and enable USB Debugging:' | t }}</span>
1333
+ </div>
1334
+
1335
+ <div class="steps-flow-grid">
1336
+ <div class="flow-step-card">
1337
+ <div class="step-card-header">
1338
+ <span class="step-number-badge">1</span>
1339
+ <span class="material-symbols-outlined step-card-icon">usb</span>
1340
+ </div>
1341
+ <div class="step-card-title">{{ 'Connect USB Cable' | t }}</div>
1342
+ <div class="step-card-desc">{{ 'Plug your Android phone into this computer with a data cable.' | t }}</div>
1343
+ </div>
1344
+
1345
+ <div class="flow-step-card">
1346
+ <div class="step-card-header">
1347
+ <span class="step-number-badge">2</span>
1348
+ <span class="material-symbols-outlined step-card-icon">touch_app</span>
1349
+ </div>
1350
+ <div class="step-card-title">{{ 'Enable Developer Options' | t }}</div>
1351
+ <div class="step-card-desc">{{ 'In phone' | t }} <strong>{{ 'Settings > About Phone' | t }}</strong>{{ ', tap' | t }} <strong>{{ 'Build Number' | t }}</strong> {{ '7 times.' | t }}</div>
1352
+ </div>
1353
+
1354
+ <div class="flow-step-card">
1355
+ <div class="step-card-header">
1356
+ <span class="step-number-badge">3</span>
1357
+ <span class="material-symbols-outlined step-card-icon">toggle_on</span>
1358
+ </div>
1359
+ <div class="step-card-title">{{ 'Enable USB Debugging' | t }}</div>
1360
+ <div class="step-card-desc">{{ 'In' | t }} <strong>{{ 'Settings > Developer Options' | t }}</strong>{{ ', turn' | t }} <strong>{{ 'USB Debugging' | t }}</strong> {{ 'ON.' | t }}
1361
+ </div>
1362
+ </div>
1363
+
1364
+ <div class="flow-step-card">
1365
+ <div class="step-card-header">
1366
+ <span class="step-number-badge">4</span>
1367
+ <span class="material-symbols-outlined step-card-icon">verified_user</span>
1368
+ </div>
1369
+ <div class="step-card-title">{{ 'Authorize Computer' | t }}</div>
1370
+ <div class="step-card-desc">{{ 'Check' | t }} <strong>{{ '"Always allow"' | t }}</strong> {{ 'and tap' | t }} <strong>{{ 'Allow' | t }}</strong> {{ 'on the phone prompt.' | t }}
1371
+ </div>
1372
+ </div>
1373
+ </div>
1374
+ </div>
1375
+ }
1376
+
1377
+ <!-- Method 3: Wireless ADB -->
1378
+ @if (activeAdbGuideTab() === 'wifi') {
1379
+ <div class="method-details-panel">
1380
+ <div class="wifi-guide-intro">
1381
+ <span class="material-symbols-outlined intro-icon">wifi</span>
1382
+ <span>{{ 'Connect your phone and computer on the same Wi-Fi network:' | t }}</span>
1383
+ </div>
1384
+
1385
+ <!-- Interactive Wi-Fi Connection Generator Box -->
1386
+ <div class="wifi-interactive-box">
1387
+ <div class="wifi-form-row">
1388
+ <div class="wifi-input-col ip-col">
1389
+ <label class="wifi-label">{{ 'Device IP Address' | t }}</label>
1390
+ <div class="wifi-input-wrap">
1391
+ <span class="material-symbols-outlined wrap-icon">router</span>
1392
+ <input type="text" class="wifi-input" [ngModel]="wifiHost()" (ngModelChange)="wifiHost.set($event)"
1393
+ [placeholder]="'e.g. 192.168.1.108' | t" spellcheck="false" />
1394
+ </div>
1395
+ </div>
1396
+
1397
+ <div class="wifi-input-col port-col">
1398
+ <label class="wifi-label">{{ 'Port' | t }}</label>
1399
+ <div class="wifi-input-wrap">
1400
+ <span class="material-symbols-outlined wrap-icon">tag</span>
1401
+ <input type="text" class="wifi-input" [ngModel]="wifiPort()" (ngModelChange)="wifiPort.set($event)"
1402
+ placeholder="5555" spellcheck="false" />
1403
+ </div>
1404
+ </div>
1405
+
1406
+ <div class="wifi-btn-col">
1407
+ <button type="button" class="btn-wifi-connect" [disabled]="isConnectingWifi() || isRemoteAdbServer() || !wifiHost().trim()"
1408
+ (click)="connectWifiDevice()">
1409
+ @if (isConnectingWifi()) {
1410
+ <span class="material-symbols-outlined spinning">sync</span>
1411
+ <span>{{ 'Connecting...' | t }}</span>
1412
+ } @else {
1413
+ <span class="material-symbols-outlined">bolt</span>
1414
+ <span>{{ 'Connect' | t }}</span>
1415
+ }
1416
+ </button>
1417
+ </div>
1418
+ </div>
1419
+
1420
+ @if (wifiConnectMessage()) {
1421
+ <div class="wifi-status-alert success">
1422
+ <span class="material-symbols-outlined">check_circle</span>
1423
+ <span>{{ wifiConnectMessage() | t }}</span>
1424
+ </div>
1425
+ }
1426
+ @if (wifiConnectError()) {
1427
+ <div class="wifi-status-alert error">
1428
+ <span class="material-symbols-outlined">error</span>
1429
+ <span>{{ wifiConnectError() | t }}</span>
1430
+ </div>
1431
+ }
1432
+ </div>
1433
+ <div class="info-callout-box">
1434
+ <span class="material-symbols-outlined callout-icon">help_outline</span>
1435
+ <div class="callout-text">
1436
+ {{ 'Find IP & port in phone' | t }} <strong>{{ 'Settings > Developer Options > Wireless Debugging' | t }}</strong>.
1437
+ </div>
1438
+ </div>
1439
+ </div>
1440
+ }
1441
+
1442
+ <!-- Method 4: ADB server on another computer -->
1443
+ @if (activeAdbGuideTab() === 'remote') {
1444
+ <div class="method-details-panel remote-adb-panel">
1445
+ <div class="remote-guide-intro">
1446
+ <span class="material-symbols-outlined intro-icon">hub</span>
1447
+ <span>{{ 'Enter the host and port of an ADB server endpoint that this computer can access, so Artemis can discover and control a phone physically connected to another computer.' | t }}</span>
1448
+ </div>
1449
+
1450
+ <div class="remote-adb-interactive-box">
1451
+ <div class="remote-adb-form-row">
1452
+ <div class="remote-adb-input-col host-col">
1453
+ <label class="remote-adb-label" for="remote-adb-host">{{ 'ADB Server Host' | t }}</label>
1454
+ <div class="remote-adb-input-wrap">
1455
+ <span class="material-symbols-outlined wrap-icon">dns</span>
1456
+ <input
1457
+ id="remote-adb-host"
1458
+ type="text"
1459
+ class="remote-adb-input"
1460
+ [ngModel]="remoteAdbHost()"
1461
+ (ngModelChange)="updateRemoteAdbHost($event)"
1462
+ placeholder="127.0.0.1"
1463
+ autocomplete="off"
1464
+ spellcheck="false"
1465
+ />
1466
+ </div>
1467
+ </div>
1468
+
1469
+ <div class="remote-adb-input-col port-col">
1470
+ <label class="remote-adb-label" for="remote-adb-port">{{ 'ADB Server Port' | t }}</label>
1471
+ <div class="remote-adb-input-wrap">
1472
+ <span class="material-symbols-outlined wrap-icon">tag</span>
1473
+ <input
1474
+ id="remote-adb-port"
1475
+ type="text"
1476
+ inputmode="numeric"
1477
+ class="remote-adb-input"
1478
+ [ngModel]="remoteAdbPort()"
1479
+ (ngModelChange)="updateRemoteAdbPort($event)"
1480
+ placeholder="5038"
1481
+ autocomplete="off"
1482
+ spellcheck="false"
1483
+ />
1484
+ </div>
1485
+ </div>
1486
+
1487
+ <div class="remote-adb-btn-col">
1488
+ <button
1489
+ type="button"
1490
+ class="btn-remote-adb-connect"
1491
+ [disabled]="isConnectingRemoteAdb() || isActivatingRemoteAdb() || !remoteAdbHost().trim()"
1492
+ (click)="connectRemoteAdbServer()"
1493
+ >
1494
+ @if (isConnectingRemoteAdb()) {
1495
+ <span class="material-symbols-outlined spinning">sync</span>
1496
+ <span>{{ 'Checking...' | t }}</span>
1497
+ } @else {
1498
+ <span class="material-symbols-outlined">bolt</span>
1499
+ <span>{{ 'Test Connection' | t }}</span>
1500
+ }
1501
+ </button>
1502
+ </div>
1503
+ </div>
1504
+
1505
+ <label class="remember-remote-adb">
1506
+ <input
1507
+ type="checkbox"
1508
+ [ngModel]="rememberRemoteAdb()"
1509
+ (ngModelChange)="rememberRemoteAdb.set($event)"
1510
+ />
1511
+ <span>{{ 'Remember this endpoint for future Artemis launches' | t }}</span>
1512
+ </label>
1513
+
1514
+ @if (remoteAdbMessage()) {
1515
+ <div class="remote-adb-status-alert success">
1516
+ <span class="material-symbols-outlined">check_circle</span>
1517
+ <span>{{ remoteAdbMessage() | t }}</span>
1518
+ </div>
1519
+ }
1520
+ @if (remoteAdbError()) {
1521
+ <div class="remote-adb-status-alert error" role="alert">
1522
+ <span class="material-symbols-outlined">error</span>
1523
+ <span>{{ remoteAdbError() | t }}</span>
1524
+ </div>
1525
+ }
1526
+
1527
+ @if (remoteAdbDevices().length > 0) {
1528
+ <div class="remote-device-results">
1529
+ @for (device of remoteAdbDevices(); track device.serial) {
1530
+ <div class="remote-device-row">
1531
+ <span class="material-symbols-outlined">phone_android</span>
1532
+ <div class="remote-device-copy">
1533
+ <strong>{{ device.model || device.serial }}</strong>
1534
+ <span>{{ device.serial }}</span>
1535
+ </div>
1536
+ <span class="remote-device-state" [class.ready]="device.state === 'device'">
1537
+ {{ device.state === 'device' ? ('Ready' | t) : device.state }}
1538
+ </span>
1539
+ </div>
1540
+ }
1541
+ </div>
1542
+ }
1543
+
1544
+ @if (remoteAdbProbeResult()?.success && !isProbedRemoteAdbActive()) {
1545
+ <button
1546
+ type="button"
1547
+ class="btn-remote-adb-connect btn-use-tested-endpoint"
1548
+ [disabled]="isActivatingRemoteAdb()"
1549
+ (click)="activateRemoteAdbServer()"
1550
+ >
1551
+ @if (isActivatingRemoteAdb()) {
1552
+ <span class="material-symbols-outlined spinning">sync</span>
1553
+ <span>{{ 'Connecting...' | t }}</span>
1554
+ } @else {
1555
+ <span class="material-symbols-outlined">check</span>
1556
+ <span>{{ remoteAdbHasReadyDevice() ? ('Use Connection' | t) : ('Use Anyway' | t) }}</span>
1557
+ }
1558
+ </button>
1559
+ }
1560
+ </div>
1561
+
1562
+ <p class="remote-adb-explanation">
1563
+ {{ 'Artemis sends all ADB commands to this server instead of the default local server. Any phone visible to that server becomes available to Artemis.' | t }}
1564
+ </p>
1565
+ </div>
1566
+ }
1567
+
1568
+ </div>
1569
+ }
1570
+
1571
+ <!-- STATE C: CONNECTED DEVICE IS LOCKED / LOCK STATE UNVERIFIED -->
1572
+ @if (adbProbe()?.status === 'warn' && (adbProbe()?.summary === 'Device Locked' || adbProbe()?.summary === 'Lock State Unknown')) {
1573
+ <div class="device-locked-panel">
1574
+ <div class="device-locked-card">
1575
+ <span class="material-symbols-outlined alert-icon">lock</span>
1576
+ <div class="locked-details">
1577
+ <div class="locked-title">
1578
+ {{ adbProbe()?.summary === 'Device Locked' ? ('Device Is Locked' | t) : ('Unable to Verify Lock State' | t) }}
1579
+ </div>
1580
+ <div class="locked-desc">
1581
+ {{ adbProbe()?.description | t }}
1582
+ </div>
1583
+ </div>
1584
+ <button type="button" class="btn-check-unlock" [disabled]="isRefreshingDiagnostics()" (click)="refreshReadiness()">
1585
+ <span class="material-symbols-outlined" [class.spinning]="isRefreshingDiagnostics()">refresh</span>
1586
+ <span>{{ isRefreshingDiagnostics() ? ('Checking...' | t) : ('Check Again' | t) }}</span>
1587
+ </button>
1588
+ </div>
1589
+
1590
+ <!-- Multi-Device Switcher if multiple devices detected -->
1591
+ @if (connectedDevices().length > 1) {
1592
+ <div class="multi-device-switcher" style="margin-top: 12px;">
1593
+ <span class="switcher-label">{{ 'Select Active Device:' | t }}</span>
1594
+ <div class="switcher-chips">
1595
+ @for (dev of connectedDevices(); track dev.serial) {
1596
+ <button
1597
+ type="button"
1598
+ class="device-chip-btn"
1599
+ [class.selected]="dev.serial === activeDevice()?.serial"
1600
+ (click)="selectTargetDevice(dev.serial)"
1601
+ >
1602
+ <span class="material-symbols-outlined chip-icon">
1603
+ phone_android
1604
+ </span>
1605
+ <span>{{ dev.model || dev.serial }}</span>
1606
+ @if (dev.serial === activeDevice()?.serial) {
1607
+ <span class="active-check">{{ '✓ Active' | t }}</span>
1608
+ }
1609
+ </button>
1610
+ }
1611
+ </div>
1612
+ </div>
1613
+ }
1614
+ </div>
1615
+ }
1616
+
1617
+ <!-- STATE C: UNAUTHORIZED DEVICE -->
1618
+ @if (adbProbe()?.status === 'warn' && adbProbe()?.summary === 'Device Unauthorized') {
1619
+ <div class="unauthorized-panel">
1620
+ <div class="unauthorized-card">
1621
+ <span class="material-symbols-outlined alert-icon">screen_lock_portrait</span>
1622
+ <div class="unauth-details">
1623
+ <div class="unauth-title">{{ 'USB Debugging Authorization Pending' | t }}</div>
1624
+ <div class="unauth-desc">
1625
+ {{ 'Your Android device was detected, but it requires authorization. Please unlock your phone screen and tap' | t }} <strong>{{ '"Allow USB Debugging"' | t }}</strong>.
1626
+ </div>
1627
+ </div>
1628
+ <button type="button" class="btn-retry-auth" (click)="restartAdbServer()">
1629
+ <span class="material-symbols-outlined">refresh</span>
1630
+ <span>{{ isRemoteAdbServer() ? ('Refresh Devices' | t) : ('Restart ADB & Check' | t) }}</span>
1631
+ </button>
1632
+ </div>
1633
+ </div>
1634
+ }
1635
+
1636
+ <!-- STATE D: ADB MISSING -->
1637
+ @if (adbProbe()?.status === 'fail') {
1638
+ <div class="adb-missing-panel">
1639
+ <div class="error-msg-box">
1640
+ <span class="material-symbols-outlined err-icon">error</span>
1641
+ <div>
1642
+ <strong>{{ 'Android Platform Tools (adb) not found.' | t }}</strong> {{ 'Please install ADB on your operating system:' | t }}
1643
+ </div>
1644
+ </div>
1645
+
1646
+ <div class="install-commands-row">
1647
+ <div class="install-card" [class.highlight-os]="effectiveOs() === 'linux'">
1648
+ <div class="install-os">
1649
+ <span>{{ 'Linux (Ubuntu / Debian)' | t }}</span>
1650
+ @if (effectiveOs() === 'linux') {
1651
+ <span class="detected-badge">{{ 'Detected' | t }}</span>
1652
+ }
1653
+ </div>
1654
+ <div class="code-box small-box">
1655
+ <span class="terminal-prefix">$</span>
1656
+ <code class="code-text">sudo apt-get install -y adb</code>
1657
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'linux_adb'"
1658
+ (click)="copyToClipboard('sudo apt-get install -y adb', 'linux_adb')">
1659
+ <span class="material-symbols-outlined">{{ copiedId() === 'linux_adb' ? 'check' : 'content_copy' }}</span>
1660
+ <span>{{ copiedId() === 'linux_adb' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1661
+ </button>
1662
+ </div>
1663
+ </div>
1664
+
1665
+ <div class="install-card" [class.highlight-os]="effectiveOs() === 'darwin'">
1666
+ <div class="install-os">
1667
+ <span>{{ 'macOS (Homebrew)' | t }}</span>
1668
+ @if (effectiveOs() === 'darwin') {
1669
+ <span class="detected-badge">{{ 'Detected' | t }}</span>
1670
+ }
1671
+ </div>
1672
+ <div class="code-box small-box">
1673
+ <span class="terminal-prefix">$</span>
1674
+ <code class="code-text">brew install android-platform-tools</code>
1675
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'mac_adb'"
1676
+ (click)="copyToClipboard('brew install android-platform-tools', 'mac_adb')">
1677
+ <span class="material-symbols-outlined">{{ copiedId() === 'mac_adb' ? 'check' : 'content_copy' }}</span>
1678
+ <span>{{ copiedId() === 'mac_adb' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1679
+ </button>
1680
+ </div>
1681
+ </div>
1682
+
1683
+ <div class="install-card" [class.highlight-os]="effectiveOs() === 'windows'">
1684
+ <div class="install-os">
1685
+ <span>{{ 'Windows (WinGet)' | t }}</span>
1686
+ @if (effectiveOs() === 'windows') {
1687
+ <span class="detected-badge">{{ 'Detected' | t }}</span>
1688
+ }
1689
+ </div>
1690
+ <div class="code-box small-box">
1691
+ <span class="terminal-prefix">></span>
1692
+ <code class="code-text">winget install Google.PlatformTools</code>
1693
+ <button type="button" class="btn-copy-code" [class.copied]="copiedId() === 'win_adb'"
1694
+ (click)="copyToClipboard('winget install Google.PlatformTools', 'win_adb')">
1695
+ <span class="material-symbols-outlined">{{ copiedId() === 'win_adb' ? 'check' : 'content_copy' }}</span>
1696
+ <span>{{ copiedId() === 'win_adb' ? ('Copied!' | t) : ('Copy' | t) }}</span>
1697
+ </button>
1698
+ </div>
1699
+ </div>
1700
+ </div>
1701
+ </div>
1702
+ }
1703
+
1704
+ </div>
1705
+ </div>
1706
+
1707
+ <!-- Ready Banner Footer: Direct One-Click Launch -->
1708
+ @if (isReady()) {
1709
+ <div class="ready-banner-footer">
1710
+ <div class="ready-banner-left">
1711
+ <div class="step-badge step-pass">
1712
+ <span class="material-symbols-outlined step-check-icon">check</span>
1713
+ </div>
1714
+ <div class="ready-text-group">
1715
+ <h4 class="ready-title">{{ 'System Ready for Autonomous Tasks' | t }}</h4>
1716
+ <p class="ready-desc">{{ 'All dependencies, AI model credentials, and Android device are verified and ready to run.' | t }}</p>
1717
+ </div>
1718
+ </div>
1719
+ <button type="button" class="btn-proceed-launch" (click)="setTab('launcher')">
1720
+ <span>{{ 'Launch Autonomous Task' | t }}</span>
1721
+ <span class="material-symbols-outlined rocket-icon">rocket_launch</span>
1722
+ </button>
1723
+ </div>
1724
+ }
1725
+
1726
+ </section>
1727
+ }
1728
+
1729
+ <!-- ========================================================================= -->
1730
+ <!-- VIEW 2: TASK EXECUTION LAUNCHER (UNIFIED SCULPTURED COMPOSER) -->
1731
+ <!-- ========================================================================= -->
1732
+ @if (activeTab() === 'launcher') {
1733
+ <section class="task-launcher-view">
1734
+
1735
+ <!-- Prerequisites Drop Alert if any blocker probe is not passing -->
1736
+ @if (hasReadinessReport() && !isReady()) {
1737
+ <div class="launcher-warning-bar" (click)="setTab('diagnostics')">
1738
+ <span class="material-symbols-outlined warn-icon">{{ adbProbe()?.summary === 'Device Locked' ? 'lock' : 'warning' }}</span>
1739
+ <span>
1740
+ {{ adbProbe()?.summary === 'Device Locked'
1741
+ ? ('Android device is locked. Unlock it and enter the home screen before running a task.' | t)
1742
+ : ('Prerequisites incomplete: runtime, credentials, or Android device is not ready. Click here to open Setup Guide.' | t) }}
1743
+ </span>
1744
+ <span class="material-symbols-outlined arrow">arrow_forward</span>
1745
+ </div>
1746
+ }
1747
+
1748
+ <!-- Task composer -->
1749
+ <div class="composer-card" [class.is-pro-active]="selectedProfile() === 'pro'">
1750
+
1751
+ <!-- Smart Intent Recommendation Strip (if detected) -->
1752
+ @if (showIntentSuggestion()) {
1753
+ <div class="intent-suggestion-strip">
1754
+ <div class="suggestion-left">
1755
+ <span class="material-symbols-outlined sparkle-icon">auto_awesome</span>
1756
+ <span class="suggestion-text">
1757
+ {{ 'Continuous monitoring or complex task detected. Recommended:' | t }} <strong>{{ 'Pro Mode' | t }}</strong>
1758
+ </span>
1759
+ </div>
1760
+ <button type="button" class="btn-switch-pro" (click)="setProfile('pro')">
1761
+ <span>{{ 'Switch to Pro' | t }}</span>
1762
+ <span class="material-symbols-outlined btn-arrow">arrow_forward</span>
1763
+ </button>
1764
+ </div>
1765
+ }
1766
+
1767
+ <!-- Borderless task input -->
1768
+ <div class="composer-input-area">
1769
+ <textarea
1770
+ id="task-input-field"
1771
+ class="composer-textarea"
1772
+ rows="3"
1773
+ [(ngModel)]="taskGoal"
1774
+ [disabled]="isSubmitting()"
1775
+ [placeholder]="'Describe what you want to achieve on your phone (e.g. Open Gmail and search for latest emails)...' | t"
1776
+ (keydown.control.enter)="runTask()"
1777
+ (keydown.meta.enter)="runTask()"
1778
+ ></textarea>
1779
+ </div>
1780
+
1781
+ <!-- Contextual Model Descriptor Strip (Clean & Unified for Flash & Pro) -->
1782
+ <div class="composer-model-descriptor" [class.is-pro]="selectedProfile() === 'pro'">
1783
+ <div class="descriptor-icon-wrap">
1784
+ <span class="material-symbols-outlined">{{ selectedProfile() === 'flash' ? 'bolt' : 'psychology' }}</span>
1785
+ </div>
1786
+ <div class="descriptor-body">
1787
+ @if (selectedProfile() === 'flash') {
1788
+ <p class="desc-line">
1789
+ <span class="mode-name">{{ 'Flash' | t }}</span>
1790
+ <span class="bullet">·</span>
1791
+ <span>{{ 'Token-efficient, rapid' | t }} <strong>{{ '3–5s/step' | t }}</strong> {{ 'decisions straight from the live screen. Best for everyday tasks with a clear path, however long they run.' | t }}</span>
1792
+ <span class="limitation-tag">{{ '(No plan, no result check, no ADB commands)' | t }}</span>
1793
+ </p>
1794
+ } @else {
1795
+ <p class="desc-line">
1796
+ <span class="mode-name">{{ 'Pro' | t }}</span>
1797
+ <span class="bullet">·</span>
1798
+ <span>{{ 'Plans first, checks its results and recovers when a tap is blocked. Handles' | t }} <strong>{{ '100+ step' | t }}</strong> {{ 'workflows and live phone monitoring (' | t }}<strong>{{ '15–40s' | t }}</strong> {{ '/ step).' | t }}</span>
1799
+ </p>
1800
+ }
1801
+ </div>
1802
+ </div>
1803
+
1804
+ <!-- Error Banner if any -->
1805
+ @if (errorMessage()) {
1806
+ <div class="error-banner">
1807
+ <span class="material-symbols-outlined error-icon">error</span>
1808
+ <span>{{ errorMessage() | t }}</span>
1809
+ </div>
1810
+ }
1811
+
1812
+ <!-- Composer Bottom Action Bar -->
1813
+ <div class="composer-action-bar">
1814
+ <div class="bar-left">
1815
+ <!-- Frosted Glass Architecture Toggle Capsule -->
1816
+ <div class="model-toggle-capsule">
1817
+ <button
1818
+ type="button"
1819
+ class="model-toggle-btn btn-flash"
1820
+ [class.active]="selectedProfile() === 'flash'"
1821
+ (click)="setProfile('flash')"
1822
+ [title]="'Flash: fast single-look loop for everyday app tasks (3–5s/step)' | t"
1823
+ >
1824
+ <span class="material-symbols-outlined btn-icon">bolt</span>
1825
+ <span class="btn-text">{{ 'Flash' | t }}</span>
1826
+ </button>
1827
+ <button
1828
+ type="button"
1829
+ class="model-toggle-btn btn-pro"
1830
+ [class.active]="selectedProfile() === 'pro'"
1831
+ (click)="setProfile('pro')"
1832
+ [title]="'Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks (15–40s/step)' | t"
1833
+ >
1834
+ <span class="material-symbols-outlined btn-icon">psychology</span>
1835
+ <span class="btn-text">{{ 'Pro' | t }}</span>
1836
+ </button>
1837
+ </div>
1838
+ </div>
1839
+
1840
+ <div class="bar-right">
1841
+ @if (taskGoal().length > 0) {
1842
+ <span class="char-count">{{ taskGoal().length }} {{ 'chars' | t }}</span>
1843
+ }
1844
+
1845
+ <!-- Run Button -->
1846
+ <button
1847
+ type="button"
1848
+ class="btn-run-composer"
1849
+ [class.btn-run-pro]="selectedProfile() === 'pro'"
1850
+ [disabled]="!taskGoal().trim() || isSubmitting() || !isReady()"
1851
+ [title]="!isReady() ? (adbProbe()?.summary === 'Device Locked' ? ('Unlock the Android device before running' | t) : ('Complete System Setup before running' | t)) : ('Run task' | t)"
1852
+ (click)="runTask()"
1853
+ >
1854
+ @if (isSubmitting()) {
1855
+ <span class="spinner"></span><span>{{ 'Starting...' | t }}</span>
1856
+ } @else {
1857
+ <span class="material-symbols-outlined run-icon">play_arrow</span><span>{{ 'Run Task' | t }}</span>
1858
+ }
1859
+ </button>
1860
+ </div>
1861
+ </div>
1862
+ </div>
1863
+
1864
+ <!-- Pro Mode Standalone Expected Output Card (Clean & Minimalist) -->
1865
+ @if (selectedProfile() === 'pro') {
1866
+ <div class="pro-outputter-standalone-card">
1867
+ <div class="outputter-card-header">
1868
+ <span class="outputter-title">{{ 'Expected Output' | t }}</span>
1869
+ <span class="outputter-optional-tag">{{ '(Optional)' | t }}</span>
1870
+ </div>
1871
+
1872
+ <div class="outputter-input-wrapper">
1873
+ <input
1874
+ type="text"
1875
+ class="outputter-standalone-input"
1876
+ [ngModel]="expectedOutput()"
1877
+ (ngModelChange)="expectedOutput.set($event)"
1878
+ [placeholder]="'Specify expected output or schema (e.g. \'Extract SMS verification code as JSON\' or \'Summary of discounts and total price\')...' | t"
1879
+ spellcheck="false"
1880
+ />
1881
+ @if (expectedOutput().trim()) {
1882
+ <button
1883
+ type="button"
1884
+ class="btn-clear-standalone"
1885
+ (click)="expectedOutput.set('')"
1886
+ [title]="'Clear expected output' | t"
1887
+ >
1888
+ <span class="material-symbols-outlined">close</span>
1889
+ </button>
1890
+ }
1891
+ </div>
1892
+ </div>
1893
+
1894
+ <!-- Pro Mode Tuning Card: verification depth & explorer perception sliders -->
1895
+ <div class="pro-tuning-card" [class.tip-open]="activeTuningTip() !== null">
1896
+ <div class="tuning-card-header">
1897
+ <span class="tuning-title">{{ 'Pro settings' | t }}</span>
1898
+ @if (!isTuningDefault()) {
1899
+ <button type="button" class="btn-tuning-reset" (click)="resetTuning()" [title]="'Back to the defaults' | t">{{ 'Reset' | t }}</button>
1900
+ }
1901
+ </div>
1902
+
1903
+ <div class="tuning-sliders">
1904
+ @for (slider of tuningSliders(); track slider.kind) {
1905
+ <div
1906
+ class="tuning-slider"
1907
+ [class.verify]="slider.kind === 'verify'"
1908
+ [class.explore]="slider.kind === 'explore'"
1909
+ [class.dragging]="activeTuningTip() === slider.kind"
1910
+ [style.--pct]="slider.fraction"
1911
+ >
1912
+ <div class="slider-head">
1913
+ <span class="slider-name">{{ slider.name | t }}</span>
1914
+ <span class="slider-value">{{ slider.level.label | t }}</span>
1915
+ </div>
1916
+ <div class="slider-ends" aria-hidden="true">
1917
+ <span>{{ slider.ends[0] | t }}</span>
1918
+ <span>{{ slider.ends[1] | t }}</span>
1919
+ </div>
1920
+
1921
+ <div
1922
+ class="slider-track-wrap"
1923
+ (pointerenter)="showTuningTip(slider.kind)"
1924
+ (pointerleave)="hideTuningTip(slider.kind)"
1925
+ >
1926
+ <div class="slider-rail" aria-hidden="true">
1927
+ <div class="slider-fill"></div>
1928
+ @for (lvl of slider.ladder; track lvl.id; let i = $index) {
1929
+ <span
1930
+ class="slider-notch"
1931
+ [class.reached]="i <= slider.index"
1932
+ [style.--n]="notchFraction(i, slider.ladder.length)"
1933
+ ></span>
1934
+ }
1935
+ @if (slider.index === slider.ladder.length - 1) {
1936
+ <!-- Re-created each time the thumb reaches the end, so the spread replays. -->
1937
+ <div class="slider-solid"></div>
1938
+ <svg class="slider-dither" width="100%" height="6" aria-hidden="true">
1939
+ @for (px of ditherPixels; track $index) {
1940
+ <rect
1941
+ [attr.x]="px.x + '%'"
1942
+ [attr.y]="px.y"
1943
+ width="2"
1944
+ height="2"
1945
+ [style.opacity]="px.opacity"
1946
+ [style.animation-delay.ms]="px.delay"
1947
+ />
1948
+ }
1949
+ </svg>
1950
+ }
1951
+ </div>
1952
+ <div class="slider-thumb" aria-hidden="true"></div>
1953
+ @if (slider.index === slider.ladder.length - 1) {
1954
+ <span class="thumb-ripple" aria-hidden="true"></span>
1955
+ }
1956
+ <input
1957
+ type="range"
1958
+ class="slider-input"
1959
+ min="0"
1960
+ [max]="slider.ladder.length - 1"
1961
+ step="1"
1962
+ [value]="slider.index"
1963
+ (input)="setTuningIndex(slider.kind, $any($event.target).value)"
1964
+ (focus)="showTuningTip(slider.kind)"
1965
+ (blur)="hideTuningTip(slider.kind)"
1966
+ [attr.aria-label]="slider.name"
1967
+ [attr.aria-valuetext]="(slider.level.label | t) + ': ' + (slider.level.tagline | t)"
1968
+ />
1969
+
1970
+ @if (activeTuningTip() === slider.kind) {
1971
+ <div class="tuning-tip-arrow" aria-hidden="true"></div>
1972
+ <div class="tuning-tip" role="tooltip">
1973
+ <div class="tip-heading">
1974
+ <span class="tip-title">{{ slider.level.label | t }}</span>
1975
+ <span class="tip-latency">{{ slider.level.latency | t }}</span>
1976
+ </div>
1977
+ <p class="tip-tagline">{{ slider.level.tagline | t }}</p>
1978
+ <div class="tip-section">
1979
+ <span class="tip-section-title">{{ 'What happens' | t }}</span>
1980
+ <ul>
1981
+ @for (line of slider.level.runs; track $index) {
1982
+ <li>{{ line | t }}</li>
1983
+ }
1984
+ </ul>
1985
+ </div>
1986
+ @if (slider.level.skips?.length) {
1987
+ <div class="tip-section skips">
1988
+ <span class="tip-section-title">{{ 'Not included' | t }}</span>
1989
+ <ul>
1990
+ @for (line of slider.level.skips; track $index) {
1991
+ <li>{{ line | t }}</li>
1992
+ }
1993
+ </ul>
1994
+ </div>
1995
+ }
1996
+ <div class="tip-best">
1997
+ <span class="tip-section-title">{{ 'Use it for' | t }}</span>
1998
+ <span>{{ slider.level.bestFor | t }}</span>
1999
+ </div>
2000
+ </div>
2001
+ }
2002
+ </div>
2003
+ </div>
2004
+ }
2005
+ </div>
2006
+ </div>
2007
+ }
2008
+
2009
+ <!-- Recommended Tasks Hub (Clean & Direct) -->
2010
+ <div class="smart-suggestions-container">
2011
+ <!-- Header Bar: Clean Category Tabs & Shuffle -->
2012
+ <div class="suggestions-header-bar">
2013
+ <div class="header-left">
2014
+ <div class="section-label">
2015
+ <span class="material-symbols-outlined section-icon">auto_awesome</span>
2016
+ <span class="label-text">{{ 'Recommended Tasks' | t }}</span>
2017
+ </div>
2018
+
2019
+ <!-- Filter Tabs -->
2020
+ <div class="category-tabs-group">
2021
+ <button
2022
+ type="button"
2023
+ class="cat-tab-btn"
2024
+ [class.active]="selectedCategory() === 'all'"
2025
+ (click)="setCategory('all')"
2026
+ >
2027
+ <span>{{ 'All' | t }}</span>
2028
+ </button>
2029
+
2030
+ <button
2031
+ type="button"
2032
+ class="cat-tab-btn cat-flash"
2033
+ [class.active]="selectedCategory() === 'flash'"
2034
+ (click)="setCategory('flash')"
2035
+ >
2036
+ <span class="material-symbols-outlined tab-mini-icon">bolt</span>
2037
+ <span>{{ 'Flash' | t }}</span>
2038
+ </button>
2039
+
2040
+ <button
2041
+ type="button"
2042
+ class="cat-tab-btn cat-pro"
2043
+ [class.active]="selectedCategory() === 'pro'"
2044
+ (click)="setCategory('pro')"
2045
+ >
2046
+ <span class="material-symbols-outlined tab-mini-icon">psychology</span>
2047
+ <span>{{ 'Pro' | t }}</span>
2048
+ </button>
2049
+
2050
+ <button
2051
+ type="button"
2052
+ class="cat-tab-btn"
2053
+ [class.active]="selectedCategory() === 'cross_app'"
2054
+ (click)="setCategory('cross_app')"
2055
+ >
2056
+ <span class="material-symbols-outlined tab-mini-icon">sync_alt</span>
2057
+ <span>{{ 'Multi-App' | t }}</span>
2058
+ </button>
2059
+
2060
+ <button
2061
+ type="button"
2062
+ class="cat-tab-btn"
2063
+ [class.active]="selectedCategory() === 'monitor'"
2064
+ (click)="setCategory('monitor')"
2065
+ >
2066
+ <span class="material-symbols-outlined tab-mini-icon">monitoring</span>
2067
+ <span>{{ 'Monitor' | t }}</span>
2068
+ </button>
2069
+ </div>
2070
+ </div>
2071
+
2072
+ <div class="header-right">
2073
+ <button type="button" class="btn-shuffle-suggestions" (click)="shuffleSuggestions()" [title]="'Discover more tasks' | t">
2074
+ <span class="material-symbols-outlined shuffle-icon">shuffle</span>
2075
+ <span>{{ 'Shuffle' | t }}</span>
2076
+ </button>
2077
+ </div>
2078
+ </div>
2079
+
2080
+ <!-- Suggestions Grid -->
2081
+ <div class="suggestions-cards-grid">
2082
+ @for (item of filteredSuggestions(); track item.id) {
2083
+ <div
2084
+ class="suggestion-card"
2085
+ [class.is-pro-card]="item.profile === 'pro'"
2086
+ [class.is-flash-card]="item.profile === 'flash'"
2087
+ (click)="applySuggestion(item)"
2088
+ >
2089
+ <!-- Top Row: App Icon + Name + Mode Badge -->
2090
+ <div class="card-top-row">
2091
+ <div class="app-icons-cluster">
2092
+ @for (app of item.apps; track app.name) {
2093
+ <div class="app-icon-badge" [title]="app.name | t">
2094
+ <span class="material-symbols-outlined">{{ app.icon }}</span>
2095
+ </div>
2096
+ }
2097
+ <span class="app-names-label">{{ getAppNamesDisplay(item.apps) }}</span>
2098
+ </div>
2099
+
2100
+ <span class="mode-badge" [class.mode-pro]="item.profile === 'pro'" [class.mode-flash]="item.profile === 'flash'"
2101
+ [title]="item.profile === 'pro' ? ('Uses ARTEMIS Pro (Multi-Agent Cognitive State Graph)' | t) : ('Uses ARTEMIS Flash (Reactive Fast Loop)' | t)">
2102
+ {{ item.profile === 'pro' ? ('Pro' | t) : ('Flash' | t) }}
2103
+ </span>
2104
+ </div>
2105
+
2106
+ <!-- Middle Content: Title + Description -->
2107
+ <div class="card-body">
2108
+ <h4 class="card-title">{{ item.title | t }}</h4>
2109
+ <p class="card-desc">{{ item.description | t }}</p>
2110
+ </div>
2111
+
2112
+ <!-- Bottom Footer Row: Tag & Action -->
2113
+ <div class="card-footer-row">
2114
+ <span class="tag-pill">{{ item.tag | t }}</span>
2115
+ <div class="use-prompt-action">
2116
+ <span>{{ 'Use' | t }}</span>
2117
+ <span class="material-symbols-outlined action-arrow">arrow_forward</span>
2118
+ </div>
2119
+ </div>
2120
+ </div>
2121
+ }
2122
+ </div>
2123
+ </div>
2124
+
2125
+
2126
+ </section>
2127
+ }
2128
+
2129
+ </main>
2130
+ </div>