@pheem49/mint 1.5.5 → 1.6.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 (222) hide show
  1. package/.codex +0 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/.github/workflows/ci.yml +45 -0
  4. package/.github/workflows/release.yml +79 -0
  5. package/Cargo.lock +5792 -0
  6. package/Cargo.toml +32 -0
  7. package/README.md +387 -353
  8. package/assets/icon.png +0 -0
  9. package/bin/mint +0 -0
  10. package/crates/mint-cli/Cargo.toml +23 -0
  11. package/crates/mint-cli/src/agent.rs +851 -0
  12. package/crates/mint-cli/src/gmail.rs +216 -0
  13. package/crates/mint-cli/src/image.rs +142 -0
  14. package/crates/mint-cli/src/main.rs +2837 -0
  15. package/crates/mint-cli/src/mcp.rs +63 -0
  16. package/crates/mint-cli/src/onboard.rs +1149 -0
  17. package/crates/mint-cli/src/setup.rs +390 -0
  18. package/crates/mint-cli/src/skills.rs +8 -0
  19. package/crates/mint-cli/src/updater.rs +279 -0
  20. package/crates/mint-core/Cargo.toml +22 -0
  21. package/crates/mint-core/src/agent_loop.rs +94 -0
  22. package/crates/mint-core/src/api_server.rs +991 -0
  23. package/crates/mint-core/src/channels.rs +248 -0
  24. package/crates/mint-core/src/chat.rs +895 -0
  25. package/crates/mint-core/src/code_tools.rs +729 -0
  26. package/crates/mint-core/src/config.rs +368 -0
  27. package/crates/mint-core/src/files.rs +159 -0
  28. package/crates/mint-core/src/knowledge.rs +541 -0
  29. package/crates/mint-core/src/lib.rs +84 -0
  30. package/crates/mint-core/src/mcp.rs +273 -0
  31. package/crates/mint-core/src/memory.rs +673 -0
  32. package/crates/mint-core/src/orchestration.rs +2157 -0
  33. package/crates/mint-core/src/pictures.rs +314 -0
  34. package/crates/mint-core/src/plugins.rs +727 -0
  35. package/crates/mint-core/src/safety.rs +416 -0
  36. package/crates/mint-core/src/semantic.rs +254 -0
  37. package/crates/mint-core/src/shell.rs +317 -0
  38. package/crates/mint-core/src/skills.rs +71 -0
  39. package/crates/mint-core/src/symbols.rs +157 -0
  40. package/crates/mint-core/src/tasks.rs +308 -0
  41. package/crates/mint-core/src/tts.rs +92 -0
  42. package/crates/mint-core/src/weather.rs +93 -0
  43. package/crates/mint-core/src/web_search.rs +200 -0
  44. package/crates/mint-core/src/workflows.rs +81 -0
  45. package/crates/mint-core/tests/mcp_stdio.rs +45 -0
  46. package/crates/mint-core/tests/memory_persistence.rs +172 -0
  47. package/crates/mint-core/tests/pictures_storage.rs +14 -0
  48. package/crates/mint-core/tests/task_lifecycle.rs +87 -0
  49. package/package.json +35 -99
  50. package/src/bin/index.js +16 -0
  51. package/src/renderer/index-web.html +17 -0
  52. package/src/renderer/index.html +17 -0
  53. package/src/renderer/public/Live2DCubismCore.js +9 -0
  54. package/src/renderer/public/assets/icon.png +0 -0
  55. package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
  56. package/src/renderer/src/App.tsx +33 -0
  57. package/src/renderer/src/calculator.ts +47 -0
  58. package/src/renderer/src/components/ChatPanel.tsx +1598 -0
  59. package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
  60. package/src/renderer/src/components/Live2DStage.tsx +374 -0
  61. package/src/renderer/src/components/MintDashboard.tsx +950 -0
  62. package/src/renderer/src/components/ModelPanel.tsx +154 -0
  63. package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
  64. package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
  65. package/src/renderer/src/components/ScreenPicker.tsx +579 -0
  66. package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
  67. package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
  68. package/src/renderer/src/components/WidgetWindow.tsx +36 -0
  69. package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
  70. package/src/{UI → renderer/src/css}/settings.css +69 -16
  71. package/src/renderer/src/css/spotlight.css +113 -0
  72. package/src/renderer/src/css/styles.css +3722 -0
  73. package/src/renderer/src/css/widget.css +185 -0
  74. package/src/renderer/src/env.d.ts +116 -0
  75. package/src/renderer/src/index.css +379 -0
  76. package/src/renderer/src/main.tsx +13 -0
  77. package/src/renderer/src/tauri.ts +996 -0
  78. package/src/renderer/src-web/App.tsx +25 -0
  79. package/src/renderer/src-web/calculator.ts +47 -0
  80. package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
  81. package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
  82. package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
  83. package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
  84. package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
  85. package/src/renderer/src-web/css/settings.css +1100 -0
  86. package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
  87. package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
  88. package/src/{UI → renderer/src-web/css}/widget.css +2 -2
  89. package/src/renderer/src-web/env.d.ts +107 -0
  90. package/src/renderer/src-web/index.css +379 -0
  91. package/src/renderer/src-web/main.tsx +13 -0
  92. package/src/renderer/src-web/tauri.ts +983 -0
  93. package/tsconfig.json +30 -0
  94. package/vite.config.ts +33 -0
  95. package/vite.config.web.ts +51 -0
  96. package/GUIDE_TH.md +0 -125
  97. package/assets/Agent_Mint.png +0 -0
  98. package/assets/CLI_Screen.png +0 -0
  99. package/assets/Settings.png +0 -0
  100. package/benchmark_ai.js +0 -71
  101. package/install.ps1 +0 -64
  102. package/install.sh +0 -54
  103. package/main.js +0 -139
  104. package/mint-cli-logic.js +0 -3
  105. package/mint-cli.js +0 -410
  106. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
  107. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
  108. package/preload-picker.js +0 -11
  109. package/preload-settings.js +0 -11
  110. package/preload.js +0 -41
  111. package/scripts/install_linux_desktop_entry.js +0 -48
  112. package/src/AI_Brain/Gemini_API.js +0 -813
  113. package/src/AI_Brain/agent_orchestrator.js +0 -73
  114. package/src/AI_Brain/autonomous_brain.js +0 -179
  115. package/src/AI_Brain/behavior_memory.js +0 -135
  116. package/src/AI_Brain/headless_agent.js +0 -143
  117. package/src/AI_Brain/knowledge_base.js +0 -349
  118. package/src/AI_Brain/memory_store.js +0 -662
  119. package/src/AI_Brain/proactive_engine.js +0 -172
  120. package/src/AI_Brain/provider_adapter.js +0 -365
  121. package/src/Automation_Layer/browser_automation.js +0 -149
  122. package/src/Automation_Layer/file_operations.js +0 -286
  123. package/src/Automation_Layer/open_app.js +0 -85
  124. package/src/Automation_Layer/open_website.js +0 -38
  125. package/src/CLI/approval_handler.js +0 -47
  126. package/src/CLI/chat_router.js +0 -247
  127. package/src/CLI/chat_ui.js +0 -1159
  128. package/src/CLI/cli_colors.js +0 -115
  129. package/src/CLI/cli_formatters.js +0 -94
  130. package/src/CLI/code_agent.js +0 -1667
  131. package/src/CLI/code_session_memory.js +0 -62
  132. package/src/CLI/gmail_auth.js +0 -210
  133. package/src/CLI/image_input.js +0 -90
  134. package/src/CLI/intent_detectors.js +0 -181
  135. package/src/CLI/interactive_chat.js +0 -658
  136. package/src/CLI/list_features.js +0 -64
  137. package/src/CLI/onboarding.js +0 -416
  138. package/src/CLI/repo_summarizer.js +0 -282
  139. package/src/CLI/semantic_code_search.js +0 -312
  140. package/src/CLI/skill_manager.js +0 -41
  141. package/src/CLI/slash_command_handler.js +0 -418
  142. package/src/CLI/symbol_indexer.js +0 -231
  143. package/src/CLI/updater.js +0 -230
  144. package/src/CLI/workspace_manager.js +0 -90
  145. package/src/Channels/brave_search_bridge.js +0 -35
  146. package/src/Channels/discord_bridge.js +0 -66
  147. package/src/Channels/google_search_bridge.js +0 -38
  148. package/src/Channels/line_bridge.js +0 -60
  149. package/src/Channels/slack_bridge.js +0 -48
  150. package/src/Channels/telegram_bridge.js +0 -41
  151. package/src/Channels/whatsapp_bridge.js +0 -57
  152. package/src/Command_Parser/parser.js +0 -45
  153. package/src/Plugins/dev_tools.js +0 -41
  154. package/src/Plugins/discord.js +0 -20
  155. package/src/Plugins/docker.js +0 -47
  156. package/src/Plugins/gmail.js +0 -251
  157. package/src/Plugins/google_calendar.js +0 -252
  158. package/src/Plugins/mcp_manager.js +0 -95
  159. package/src/Plugins/notion.js +0 -256
  160. package/src/Plugins/obsidian.js +0 -54
  161. package/src/Plugins/plugin_manager.js +0 -81
  162. package/src/Plugins/spotify.js +0 -173
  163. package/src/Plugins/system_metrics.js +0 -31
  164. package/src/Plugins/system_monitor.js +0 -72
  165. package/src/System/action_executor.js +0 -178
  166. package/src/System/bridge_manager.js +0 -76
  167. package/src/System/chat_history_manager.js +0 -83
  168. package/src/System/config_manager.js +0 -194
  169. package/src/System/custom_workflows.js +0 -163
  170. package/src/System/daemon_manager.js +0 -67
  171. package/src/System/google_tts_urls.js +0 -51
  172. package/src/System/granular_automation.js +0 -157
  173. package/src/System/ipc_handlers.js +0 -332
  174. package/src/System/notifications.js +0 -23
  175. package/src/System/optional_require.js +0 -23
  176. package/src/System/picture_store.js +0 -109
  177. package/src/System/proactive_loop.js +0 -153
  178. package/src/System/safety_manager.js +0 -273
  179. package/src/System/sandbox_runner.js +0 -182
  180. package/src/System/screen_capture.js +0 -175
  181. package/src/System/smart_context.js +0 -227
  182. package/src/System/system_automation.js +0 -162
  183. package/src/System/system_events.js +0 -79
  184. package/src/System/system_info.js +0 -125
  185. package/src/System/task_manager.js +0 -222
  186. package/src/System/tool_registry.js +0 -293
  187. package/src/System/window_manager.js +0 -220
  188. package/src/UI/floating.css +0 -80
  189. package/src/UI/floating.html +0 -17
  190. package/src/UI/floating.js +0 -67
  191. package/src/UI/live2d_manager.js +0 -600
  192. package/src/UI/preload-floating.js +0 -7
  193. package/src/UI/preload-spotlight.js +0 -11
  194. package/src/UI/preload-widget.js +0 -5
  195. package/src/UI/proactive-glow.html +0 -42
  196. package/src/UI/renderer.js +0 -2127
  197. package/src/UI/screenPicker.html +0 -214
  198. package/src/UI/screenPicker.js +0 -262
  199. package/src/UI/settings.html +0 -577
  200. package/src/UI/settings.js +0 -770
  201. package/src/UI/spotlight.html +0 -23
  202. package/src/UI/spotlight.js +0 -185
  203. package/src/UI/widget.html +0 -29
  204. package/src/UI/widget.js +0 -10
  205. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  206. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
  207. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
  208. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
  209. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
  210. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
  211. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
  212. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
  213. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
  214. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
  215. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
  216. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
  217. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
  218. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
  219. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
  220. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
  221. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
  222. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
@@ -0,0 +1,579 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+
3
+ interface Rect {
4
+ startX: number
5
+ startY: number
6
+ currentX: number
7
+ currentY: number
8
+ width: number
9
+ height: number
10
+ }
11
+
12
+ interface NormalRect {
13
+ x: number
14
+ y: number
15
+ width: number
16
+ height: number
17
+ }
18
+
19
+ export default function ScreenPicker() {
20
+ const bgCanvasRef = useRef<HTMLCanvasElement>(null)
21
+ const overlayCanvasRef = useRef<HTMLCanvasElement>(null)
22
+ const translationBoxRef = useRef<HTMLDivElement>(null)
23
+
24
+ const [isDrawing, setIsDrawing] = useState(false)
25
+ const [coords, setCoords] = useState<Rect>({ startX: 0, startY: 0, currentX: 0, currentY: 0, width: 0, height: 0 })
26
+ const [baseImage, setBaseImage] = useState<HTMLImageElement | null>(null)
27
+ const [selectedRect, setSelectedRect] = useState<NormalRect | null>(null)
28
+
29
+ const [isTranslateMode, setIsTranslateMode] = useState(false)
30
+ const [isContinuousTranslateActive, setIsContinuousTranslateActive] = useState(false)
31
+ const [translationText, setTranslationText] = useState('')
32
+ const [translationPos, setTranslationPos] = useState({ left: 0, top: 0, maxWidth: 400 })
33
+
34
+ const baseImageRef = useRef<HTMLImageElement | null>(null)
35
+ const isOverlayInteractableRef = useRef(true)
36
+ const screenshotRequestedRef = useRef(false)
37
+
38
+ // Initialize canvases and listen to screenshots
39
+ useEffect(() => {
40
+ const loadScreenshot = (base64Data: string) => {
41
+ const img = new Image()
42
+ img.onload = () => {
43
+ baseImageRef.current = img
44
+ setBaseImage(img)
45
+ const bg = bgCanvasRef.current
46
+ if (bg) {
47
+ bg.width = window.innerWidth
48
+ bg.height = window.innerHeight
49
+ const bgCtx = bg.getContext('2d')
50
+ bgCtx?.drawImage(img, 0, 0, bg.width, bg.height)
51
+ drawDarkOverlay()
52
+ }
53
+ }
54
+ img.src = base64Data
55
+ }
56
+
57
+ const handleResize = () => {
58
+ const bg = bgCanvasRef.current
59
+ const overlay = overlayCanvasRef.current
60
+ if (bg && overlay) {
61
+ bg.width = window.innerWidth
62
+ bg.height = window.innerHeight
63
+ overlay.width = window.innerWidth
64
+ overlay.height = window.innerHeight
65
+ if (baseImageRef.current) {
66
+ const bgCtx = bg.getContext('2d')
67
+ bgCtx?.drawImage(baseImageRef.current, 0, 0, bg.width, bg.height)
68
+ }
69
+ drawDarkOverlay()
70
+ }
71
+ }
72
+
73
+ if (window.screenPickerApi) {
74
+ const pendingCapture = window.localStorage.getItem('mint:pending-screen-capture')
75
+ if (pendingCapture) {
76
+ window.localStorage.removeItem('mint:pending-screen-capture')
77
+ screenshotRequestedRef.current = true
78
+ loadScreenshot(pendingCapture)
79
+ } else if (!screenshotRequestedRef.current) {
80
+ screenshotRequestedRef.current = true
81
+ window.screenPickerApi.onScreenshot(loadScreenshot)
82
+ }
83
+
84
+ window.screenPickerApi.onTranslationResult((thaiText) => {
85
+ setTranslationText(thaiText)
86
+ })
87
+ }
88
+
89
+ window.addEventListener('resize', handleResize)
90
+ handleResize()
91
+
92
+ return () => {
93
+ window.removeEventListener('resize', handleResize)
94
+ }
95
+ }, [])
96
+
97
+ const drawDarkOverlay = () => {
98
+ const overlay = overlayCanvasRef.current
99
+ if (!overlay) return
100
+ const overlayCtx = overlay.getContext('2d')
101
+ if (!overlayCtx) return
102
+ overlayCtx.clearRect(0, 0, overlay.width, overlay.height)
103
+ overlayCtx.fillStyle = 'rgba(0, 0, 0, 0.5)'
104
+ overlayCtx.fillRect(0, 0, overlay.width, overlay.height)
105
+ }
106
+
107
+ const normalizeRect = (r: Rect): NormalRect => {
108
+ return {
109
+ x: Math.min(r.startX, r.currentX),
110
+ y: Math.min(r.startY, r.currentY),
111
+ width: Math.abs(r.width),
112
+ height: Math.abs(r.height)
113
+ }
114
+ }
115
+
116
+ const drawSelectionOutline = (rect: NormalRect) => {
117
+ const overlay = overlayCanvasRef.current
118
+ if (!overlay || !rect || rect.width === 0 || rect.height === 0) return
119
+ const overlayCtx = overlay.getContext('2d')
120
+ if (!overlayCtx) return
121
+ overlayCtx.clearRect(0, 0, overlay.width, overlay.height)
122
+ overlayCtx.strokeStyle = isTranslateMode ? '#10b981' : '#00ff88'
123
+ overlayCtx.lineWidth = 3
124
+ overlayCtx.strokeRect(rect.x, rect.y, rect.width, rect.height)
125
+ }
126
+
127
+ const drawSelection = (currentCoords: Rect) => {
128
+ const overlay = overlayCanvasRef.current
129
+ if (!overlay) return
130
+ const overlayCtx = overlay.getContext('2d')
131
+ if (!overlayCtx) return
132
+
133
+ drawDarkOverlay()
134
+
135
+ const rect = normalizeRect(currentCoords)
136
+ overlayCtx.clearRect(rect.x, rect.y, rect.width, rect.height)
137
+ overlayCtx.strokeStyle = isTranslateMode ? '#10b981' : '#00ff88'
138
+ overlayCtx.lineWidth = 2
139
+ overlayCtx.strokeRect(rect.x, rect.y, rect.width, rect.height)
140
+ }
141
+
142
+ const resetSelectionOverlay = () => {
143
+ setSelectedRect(null)
144
+ const overlay = overlayCanvasRef.current
145
+ if (overlay) overlay.style.pointerEvents = 'auto'
146
+ isOverlayInteractableRef.current = true
147
+ drawDarkOverlay()
148
+ }
149
+
150
+ const setOverlayInteractable = (isInteractable: boolean) => {
151
+ if (isOverlayInteractableRef.current === isInteractable) return
152
+ isOverlayInteractableRef.current = isInteractable
153
+ window.screenPickerApi?.setOverlayInteractable(isInteractable)
154
+ }
155
+
156
+ const stopTranslationMode = () => {
157
+ setIsContinuousTranslateActive(false)
158
+ const overlay = overlayCanvasRef.current
159
+ if (overlay) overlay.style.pointerEvents = 'auto'
160
+ window.screenPickerApi?.stopContinuousTranslation()
161
+ setOverlayInteractable(true)
162
+
163
+ const bg = bgCanvasRef.current
164
+ if (bg && baseImage) {
165
+ const bgCtx = bg.getContext('2d')
166
+ bgCtx?.clearRect(0, 0, bg.width, bg.height)
167
+ bgCtx?.drawImage(baseImage, 0, 0, bg.width, bg.height)
168
+ }
169
+
170
+ resetSelectionOverlay()
171
+ }
172
+
173
+ const setTranslationBoxPosition = (rect: NormalRect) => {
174
+ const margin = 10
175
+ const boxWidth = Math.min(400, Math.max(240, rect.width))
176
+ const left = Math.max(margin, Math.min(rect.x, window.innerWidth - boxWidth - margin))
177
+
178
+ // Estimate translation box height
179
+ const boxHeight = translationBoxRef.current?.offsetHeight || 80
180
+ const preferredTop = rect.y + rect.height + margin
181
+ const fallbackTop = Math.max(margin, rect.y - margin - boxHeight)
182
+ const top = preferredTop + boxHeight <= window.innerHeight ? preferredTop : fallbackTop
183
+
184
+ setTranslationPos({ left, top, maxWidth: boxWidth })
185
+ }
186
+
187
+ const cropAndSend = (rect: Rect) => {
188
+ if (rect.width === 0 || rect.height === 0 || !baseImage) return
189
+ const { x, y, width: w, height: h } = normalizeRect(rect)
190
+
191
+ const bg = bgCanvasRef.current
192
+ if (!bg) return
193
+
194
+ const scaleX = baseImage.width / bg.width
195
+ const scaleY = baseImage.height / bg.height
196
+
197
+ const cropX = x * scaleX
198
+ const cropY = y * scaleY
199
+ const cropW = w * scaleX
200
+ const cropH = h * scaleY
201
+
202
+ const cropCanvas = document.createElement('canvas')
203
+ cropCanvas.width = cropW
204
+ cropCanvas.height = cropH
205
+ const cropCtx = cropCanvas.getContext('2d')
206
+ if (!cropCtx) return
207
+
208
+ cropCtx.drawImage(baseImage, cropX, cropY, cropW, cropH, 0, 0, cropW, cropH)
209
+ const croppedBase64 = cropCanvas.toDataURL('image/png')
210
+
211
+ if (isTranslateMode) {
212
+ setIsContinuousTranslateActive(true)
213
+ const normRect = { x, y, width: w, height: h }
214
+ setSelectedRect(normRect)
215
+
216
+ const bg = bgCanvasRef.current
217
+ if (bg) {
218
+ const bgCtx = bg.getContext('2d')
219
+ bgCtx?.clearRect(0, 0, bg.width, bg.height)
220
+ }
221
+
222
+ const overlay = overlayCanvasRef.current
223
+ if (overlay) overlay.style.pointerEvents = 'none'
224
+
225
+ drawSelectionOutline(normRect)
226
+ setTranslationText('Auto-Translating...')
227
+ setTranslationBoxPosition(normRect)
228
+ setOverlayInteractable(false)
229
+
230
+ window.screenPickerApi?.startContinuousTranslation(normRect)
231
+ } else {
232
+ window.screenPickerApi?.sendSelection(croppedBase64)
233
+ }
234
+ }
235
+
236
+ // Keyboard controls
237
+ useEffect(() => {
238
+ const handleKeyDown = (e: KeyboardEvent) => {
239
+ if (e.key === 'Escape') {
240
+ if (isContinuousTranslateActive) {
241
+ e.preventDefault()
242
+ stopTranslationMode()
243
+ } else {
244
+ window.screenPickerApi?.closePicker()
245
+ }
246
+ }
247
+ }
248
+ window.addEventListener('keydown', handleKeyDown)
249
+ return () => window.removeEventListener('keydown', handleKeyDown)
250
+ }, [isContinuousTranslateActive])
251
+
252
+ // Mouse move tracker for translation box interactivity
253
+ useEffect(() => {
254
+ const handleMouseMove = (e: MouseEvent) => {
255
+ if (!isContinuousTranslateActive) return
256
+ const box = translationBoxRef.current
257
+ if (!box) return
258
+
259
+ const rect = box.getBoundingClientRect()
260
+ const isInsideBox =
261
+ e.clientX >= rect.left &&
262
+ e.clientX <= rect.right &&
263
+ e.clientY >= rect.top &&
264
+ e.clientY <= rect.bottom
265
+
266
+ setOverlayInteractable(isInsideBox)
267
+ }
268
+ window.addEventListener('mousemove', handleMouseMove)
269
+ return () => window.removeEventListener('mousemove', handleMouseMove)
270
+ }, [isContinuousTranslateActive])
271
+
272
+ const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
273
+ if (isContinuousTranslateActive) return
274
+ setIsDrawing(true)
275
+ const newCoords = {
276
+ startX: e.clientX,
277
+ startY: e.clientY,
278
+ currentX: e.clientX,
279
+ currentY: e.clientY,
280
+ width: 0,
281
+ height: 0
282
+ }
283
+ setCoords(newCoords)
284
+ }
285
+
286
+ const handleCanvasMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
287
+ if (!isDrawing) return
288
+ const newCoords = {
289
+ ...coords,
290
+ currentX: e.clientX,
291
+ currentY: e.clientY,
292
+ width: e.clientX - coords.startX,
293
+ height: e.clientY - coords.startY
294
+ }
295
+ setCoords(newCoords)
296
+ drawSelection(newCoords)
297
+ }
298
+
299
+ const handleMouseUp = (e: React.MouseEvent<HTMLCanvasElement>) => {
300
+ if (!isDrawing) return
301
+ setIsDrawing(false)
302
+ const finalCoords = {
303
+ ...coords,
304
+ currentX: e.clientX,
305
+ currentY: e.clientY,
306
+ width: e.clientX - coords.startX,
307
+ height: e.clientY - coords.startY
308
+ }
309
+ setCoords(finalCoords)
310
+ cropAndSend(finalCoords)
311
+ }
312
+
313
+ const handleToggleTranslateMode = () => {
314
+ const nextMode = !isTranslateMode
315
+ setIsTranslateMode(nextMode)
316
+ if (nextMode) {
317
+ resetSelectionOverlay()
318
+ } else {
319
+ if (isContinuousTranslateActive) {
320
+ stopTranslationMode()
321
+ } else {
322
+ resetSelectionOverlay()
323
+ }
324
+ }
325
+ }
326
+
327
+ const handleFullscreen = () => {
328
+ if (baseImage && !isTranslateMode) {
329
+ window.screenPickerApi?.sendSelection(baseImage.src)
330
+ }
331
+ }
332
+
333
+ const handleCancel = () => {
334
+ window.screenPickerApi?.closePicker()
335
+ }
336
+
337
+ return (
338
+ <div style={{ width: '100vw', height: '100vh', overflow: 'hidden', position: 'relative', background: 'transparent' }}>
339
+ <style>{`
340
+ .vision-glow {
341
+ position: absolute;
342
+ inset: 0;
343
+ pointer-events: none;
344
+ box-shadow: inset 0 0 70px rgba(16, 185, 129, 0.18);
345
+ z-index: 5;
346
+ }
347
+
348
+ #toolbar {
349
+ position: absolute;
350
+ top: 24px;
351
+ left: 50%;
352
+ transform: translateX(-50%);
353
+ z-index: 10;
354
+ background: rgba(15, 23, 42, 0.85);
355
+ backdrop-filter: blur(10px);
356
+ -webkit-backdrop-filter: blur(10px);
357
+ border: 1px solid rgba(255, 255, 255, 0.08);
358
+ padding: 8px 20px;
359
+ border-radius: 999px;
360
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 12px;
364
+ color: #f8fafc;
365
+ font-family: 'Outfit', 'Inter', sans-serif;
366
+ animation: slide-down 0.4s cubic-bezier(0.16, 1, 0.3, 1);
367
+ }
368
+
369
+ @keyframes slide-down {
370
+ from { transform: translate(-50%, -20px); opacity: 0; }
371
+ to { transform: translate(-50%, 0); opacity: 1; }
372
+ }
373
+
374
+ .hint {
375
+ font-size: 0.82rem;
376
+ color: #94a3b8;
377
+ margin-right: 12px;
378
+ font-weight: 400;
379
+ }
380
+
381
+ .screen-picker-btn {
382
+ font-family: inherit;
383
+ font-size: 0.8rem;
384
+ font-weight: 500;
385
+ padding: 8px 16px;
386
+ border-radius: 999px;
387
+ border: 1px solid transparent;
388
+ cursor: pointer;
389
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
390
+ display: flex;
391
+ align-items: center;
392
+ gap: 6px;
393
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
394
+ }
395
+
396
+ .screen-picker-btn:hover {
397
+ transform: translateY(-1px);
398
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
399
+ }
400
+
401
+ .screen-picker-btn:active {
402
+ transform: translateY(0);
403
+ }
404
+
405
+ .btn-translate {
406
+ background: rgba(16, 185, 129, 0.15);
407
+ color: #a7f3d0;
408
+ border-color: rgba(16, 185, 129, 0.3);
409
+ }
410
+
411
+ .btn-translate:hover {
412
+ background: rgba(16, 185, 129, 0.3);
413
+ color: #ecfdf5;
414
+ }
415
+
416
+ .btn-translate.active {
417
+ background: #10b981;
418
+ color: #ffffff;
419
+ border-color: #059669;
420
+ box-shadow: 0 0 15px rgba(16, 185, 129, 0.4);
421
+ }
422
+
423
+ .btn-primary {
424
+ background: rgba(16, 185, 129, 0.15);
425
+ color: #a7f3d0;
426
+ border-color: rgba(16, 185, 129, 0.3);
427
+ }
428
+
429
+ .btn-primary:hover {
430
+ background: rgba(16, 185, 129, 0.3);
431
+ color: #ecfdf5;
432
+ }
433
+
434
+ .btn-danger {
435
+ background: rgba(239, 68, 68, 0.15);
436
+ color: #fecaca;
437
+ border-color: rgba(239, 68, 68, 0.3);
438
+ }
439
+
440
+ .btn-danger:hover {
441
+ background: rgba(239, 68, 68, 0.3);
442
+ color: #fef2f2;
443
+ }
444
+
445
+ .loading-spinner {
446
+ display: inline-block;
447
+ width: 14px;
448
+ height: 14px;
449
+ border: 2px solid rgba(255, 255, 255, 0.2);
450
+ border-radius: 50%;
451
+ border-top-color: #10b981;
452
+ animation: picker-spin 0.8s linear infinite;
453
+ margin-right: 8px;
454
+ }
455
+
456
+ @keyframes picker-spin {
457
+ to { transform: rotate(360deg); }
458
+ }
459
+
460
+ #translation-box {
461
+ position: absolute;
462
+ background: rgba(15, 23, 42, 0.9);
463
+ backdrop-filter: blur(10px);
464
+ -webkit-backdrop-filter: blur(10px);
465
+ border: 1px solid rgba(255, 255, 255, 0.1);
466
+ color: #f8fafc;
467
+ padding: 14px 18px;
468
+ border-radius: 12px;
469
+ font-family: 'Inter', sans-serif;
470
+ font-size: 0.9rem;
471
+ line-height: 1.6;
472
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05);
473
+ z-index: 100;
474
+ animation: fade-in 0.2s ease-out;
475
+ }
476
+
477
+ @keyframes fade-in {
478
+ from { opacity: 0; transform: scale(0.95); }
479
+ to { opacity: 1; transform: scale(1); }
480
+ }
481
+
482
+ .close-translate-btn {
483
+ position: absolute;
484
+ top: -10px;
485
+ right: -10px;
486
+ background: #ef4444;
487
+ color: white;
488
+ border: 2px solid rgba(15, 23, 42, 0.95);
489
+ border-radius: 999px;
490
+ min-width: 38px;
491
+ height: 22px;
492
+ display: flex;
493
+ align-items: center;
494
+ justify-content: center;
495
+ cursor: pointer;
496
+ font-size: 0.7rem;
497
+ font-weight: 700;
498
+ letter-spacing: 0.05em;
499
+ text-transform: uppercase;
500
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
501
+ transition: all 0.2s;
502
+ }
503
+
504
+ .close-translate-btn:hover {
505
+ background: #dc2626;
506
+ transform: scale(1.05);
507
+ }
508
+ `}</style>
509
+
510
+ <div className="vision-glow"></div>
511
+ {!isContinuousTranslateActive && (
512
+ <div id="toolbar">
513
+ <span className="hint" id="hint-text">
514
+ {isTranslateMode ? 'Drag over text to translate to Thai' : 'Click and drag to select a region'}
515
+ </span>
516
+ <button
517
+ className={`screen-picker-btn btn-translate ${isTranslateMode ? 'active' : ''}`}
518
+ onClick={handleToggleTranslateMode}
519
+ style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}
520
+ >
521
+ {isTranslateMode ? (
522
+ 'Stop Translate'
523
+ ) : (
524
+ <>
525
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
526
+ <circle cx="12" cy="12" r="10"></circle>
527
+ <line x1="2" y1="12" x2="22" y2="12"></line>
528
+ <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
529
+ </svg>
530
+ <span>Live Translate</span>
531
+ </>
532
+ )}
533
+ </button>
534
+ {!isTranslateMode && (
535
+ <button className="screen-picker-btn btn-primary" onClick={handleFullscreen}>
536
+ Full Screen
537
+ </button>
538
+ )}
539
+ <button className="screen-picker-btn btn-danger" onClick={handleCancel}>
540
+ Cancel
541
+ </button>
542
+ </div>
543
+ )}
544
+
545
+ <canvas ref={bgCanvasRef} id="bg-canvas" style={{ position: 'absolute', top: 0, left: 0, zIndex: 1 }} />
546
+ <canvas
547
+ ref={overlayCanvasRef}
548
+ id="overlay-canvas"
549
+ style={{ position: 'absolute', top: 0, left: 0, zIndex: 2, cursor: 'crosshair' }}
550
+ onMouseDown={handleMouseDown}
551
+ onMouseMove={handleCanvasMouseMove}
552
+ onMouseUp={handleMouseUp}
553
+ />
554
+
555
+ <div
556
+ ref={translationBoxRef}
557
+ id="translation-box"
558
+ style={{
559
+ display: isContinuousTranslateActive ? 'block' : 'none',
560
+ position: 'absolute',
561
+ left: `${translationPos.left}px`,
562
+ top: `${translationPos.top}px`,
563
+ maxWidth: `${translationPos.maxWidth}px`,
564
+ zIndex: 100
565
+ }}
566
+ >
567
+ <div className="close-translate-btn" onClick={stopTranslationMode}>
568
+ Esc
569
+ </div>
570
+ <div id="translation-content">
571
+ {translationText === 'Auto-Translating...' && (
572
+ <span className="loading-spinner"></span>
573
+ )}
574
+ {translationText}
575
+ </div>
576
+ </div>
577
+ </div>
578
+ )
579
+ }