@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,374 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import * as PIXI from 'pixi.js'
3
+
4
+ // Ensure PIXI is available globally for the live2d library
5
+ ;(window as any).PIXI = PIXI
6
+
7
+ interface Live2DStageProps {
8
+ scale: number
9
+ expressionIndex: number
10
+ accessoryIndex: number
11
+ isLocked: boolean
12
+ isActive?: boolean
13
+ onLoadComplete?: () => void
14
+ }
15
+
16
+ // Map Expression Index to Live2D Expression Name
17
+ const EXPRESSION_MAP: Record<number, string | null> = {
18
+ 0: null, // Default (normal)
19
+ 1: 'Dazed', // 呆猫 (Dumb Cat)
20
+ 2: 'DazedEyes', // 呆猫眼珠摇晃 (Dumb Cat Eye Roll)
21
+ 3: 'Photo', // 拍照 (Take Photo)
22
+ 4: 'Click', // 点一下 (Poke)
23
+ 5: 'CatFilter', // 猫咪滤镜 (Cat Filter)
24
+ }
25
+
26
+ // Map Accessory Index to Live2D Expression Name
27
+ const ACCESSORY_MAP: Record<number, string | null> = {
28
+ 0: null, // Default (none)
29
+ 1: 'Apron', // ผ้ากันเปื้อน (Apron)
30
+ 2: 'Glasses', // 眼鏡 (Glasses)
31
+ 3: 'Pen', // 拿笔 (Hold Pen)
32
+ }
33
+
34
+ const TRACKING_SPEED = 1.25
35
+ const MAX_DEVICE_PIXEL_RATIO = 1.25
36
+ const ACTIVE_MAX_FPS = 60
37
+
38
+ const clampToUnitCircle = (x: number, y: number) => {
39
+ if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) {
40
+ return { x: 0, y: 0 }
41
+ }
42
+ const distance = Math.hypot(x, y)
43
+ if (distance <= 1) return { x, y }
44
+
45
+ return {
46
+ x: x / distance,
47
+ y: y / distance,
48
+ }
49
+ }
50
+
51
+ export default function Live2DStage({ scale, expressionIndex, accessoryIndex, isLocked, isActive = true, onLoadComplete }: Live2DStageProps) {
52
+ const containerRef = useRef<HTMLDivElement>(null)
53
+ const canvasRef = useRef<HTMLCanvasElement>(null)
54
+ const modelRef = useRef<any>(null)
55
+ const appRef = useRef<PIXI.Application | null>(null)
56
+ const [loading, setLoading] = useState(true)
57
+ const [shouldRender, setShouldRender] = useState(isActive)
58
+ const baseWidthRef = useRef<number | null>(null)
59
+ const baseHeightRef = useRef<number | null>(null)
60
+
61
+ useEffect(() => {
62
+ if (isActive && !shouldRender) {
63
+ setShouldRender(true)
64
+ }
65
+ }, [isActive, shouldRender])
66
+
67
+ const setRenderActive = (active: boolean) => {
68
+ const app = appRef.current
69
+ const model = modelRef.current
70
+ if (!app) return
71
+
72
+ app.ticker.maxFPS = ACTIVE_MAX_FPS
73
+ if (model) {
74
+ model.autoUpdate = active
75
+ model.renderable = active
76
+ }
77
+
78
+ if (active) {
79
+ app.start()
80
+ app.render()
81
+ } else {
82
+ app.stop()
83
+ }
84
+ }
85
+
86
+ useEffect(() => {
87
+ if (!shouldRender) return
88
+ if (!canvasRef.current || !containerRef.current) return
89
+
90
+ let isMounted = true
91
+ let appInstance: PIXI.Application | null = null
92
+
93
+ // Load Live2D engine and model dynamically
94
+ const initLive2D = async () => {
95
+ try {
96
+ // Dynamically import to ensure window.PIXI exists first
97
+ const { Live2DModel } = await import('pixi-live2d-display/cubism4')
98
+
99
+ // Register ticker
100
+ try {
101
+ Live2DModel.registerTicker(PIXI.Ticker as any)
102
+ } catch (e) {
103
+ // Already registered
104
+ }
105
+
106
+ if (!isMounted) return
107
+
108
+ // Create Pixi Application
109
+ appInstance = new PIXI.Application({
110
+ view: canvasRef.current!,
111
+ backgroundAlpha: 0,
112
+ antialias: false,
113
+ autoDensity: true,
114
+ resolution: Math.min(window.devicePixelRatio || 1, MAX_DEVICE_PIXEL_RATIO),
115
+ resizeTo: containerRef.current!,
116
+ })
117
+ appInstance.ticker.maxFPS = ACTIVE_MAX_FPS
118
+ appInstance.ticker.minFPS = 10
119
+ appRef.current = appInstance
120
+
121
+ // Load the Live2D model (served from public/models)
122
+ const modelUrl = './models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json'
123
+
124
+ let json: any
125
+ try {
126
+ const res = await fetch(modelUrl)
127
+ if (!res.ok) {
128
+ throw new Error(`HTTP error ${res.status}: ${res.statusText}`)
129
+ }
130
+ json = await res.json()
131
+
132
+ // Inject the URL so pixi-live2d-display can resolve relative paths
133
+ json.url = modelUrl
134
+ } catch (e) {
135
+ console.error("Debug: Failed to fetch and parse JSON:", e)
136
+ throw e
137
+ }
138
+
139
+ const model = await Live2DModel.from(json)
140
+ if (!isMounted) {
141
+ model.destroy()
142
+ return
143
+ }
144
+
145
+ modelRef.current = model
146
+ baseWidthRef.current = model.width || model.internalModel?.originalWidth || 1000
147
+ baseHeightRef.current = model.height || model.internalModel?.originalHeight || 1000
148
+ appInstance.stage.addChild(model as any)
149
+
150
+ // Fit model size and position relative to canvas
151
+ const fitModel = () => {
152
+ if (!modelRef.current || !appRef.current) return
153
+ const m = modelRef.current
154
+ const stageHeight = appRef.current.screen.height
155
+ const stageWidth = appRef.current.screen.width
156
+
157
+ if (stageWidth < 100 || stageHeight < 100) return
158
+
159
+ const baseWidth = baseWidthRef.current || m.width || 1000
160
+ const baseHeight = baseHeightRef.current || m.height || 1000
161
+
162
+ const widthScale = stageWidth / baseWidth
163
+ const heightScale = stageHeight / baseHeight
164
+
165
+ const modelScale = Math.min(widthScale, heightScale) * 1.85 * scale
166
+ m.scale.set(modelScale)
167
+
168
+ // Center-center alignment with 55% Y offset
169
+ m.anchor.set(0.5, 0.5)
170
+ m.x = stageWidth / 2
171
+ m.y = stageHeight / 2 + stageHeight * 0.55
172
+ }
173
+
174
+ fitModel()
175
+
176
+ // Handle resizing
177
+ appInstance.renderer.on('resize', fitModel)
178
+
179
+ // Enable mouse tracking interactions
180
+ model.interactive = true
181
+
182
+ const focusController = model.internalModel.focusController
183
+ const updateFocus = focusController.update.bind(focusController)
184
+ focusController.update = (deltaMs: number) => updateFocus(deltaMs * TRACKING_SPEED)
185
+
186
+ // Match the extra mouse-driven parameters configured by the original VTube Studio model.
187
+ model.internalModel.on('beforeModelUpdate', () => {
188
+ const focus = model.internalModel.focusController
189
+ const coreModel = model.internalModel.coreModel as {
190
+ setParameterValueById: (parameterId: string, value: number) => void
191
+ }
192
+
193
+ coreModel.setParameterValueById('Param77', focus.x * 10)
194
+ coreModel.setParameterValueById('Param78', focus.y)
195
+ coreModel.setParameterValueById('Param83', focus.x)
196
+ coreModel.setParameterValueById('Param86', focus.y)
197
+ })
198
+
199
+ applyExpressionAndAccessory(model, expressionIndex, accessoryIndex)
200
+ setRenderActive(isActive && document.visibilityState === 'visible')
201
+ setLoading(false)
202
+ onLoadComplete?.()
203
+ } catch (err) {
204
+ console.error('Failed to load Live2D model:', err)
205
+ setLoading(false)
206
+ onLoadComplete?.()
207
+ }
208
+ }
209
+
210
+ initLive2D()
211
+
212
+ return () => {
213
+ isMounted = false
214
+ if (appInstance) {
215
+ appInstance.destroy(true, {
216
+ children: true,
217
+ texture: true,
218
+ baseTexture: true,
219
+ })
220
+ appRef.current = null
221
+ }
222
+ modelRef.current = null
223
+ }
224
+ }, [shouldRender])
225
+
226
+ // Update scale & position dynamically when scale changes or container resizes
227
+ useEffect(() => {
228
+ if (!containerRef.current) return
229
+
230
+ const handleResize = () => {
231
+ if (!isActive) return
232
+ if (!modelRef.current || !appRef.current) return
233
+ const app = appRef.current
234
+ app.resize()
235
+
236
+ const model = modelRef.current
237
+ const stageHeight = app.screen.height
238
+ const stageWidth = app.screen.width
239
+
240
+ if (stageWidth < 100 || stageHeight < 100) return
241
+
242
+ const baseWidth = baseWidthRef.current || model.width || 1000
243
+ const baseHeight = baseHeightRef.current || model.height || 1000
244
+
245
+ const widthScale = stageWidth / baseWidth
246
+ const heightScale = stageHeight / baseHeight
247
+
248
+ const modelScale = Math.min(widthScale, heightScale) * 1.85 * scale
249
+ model.scale.set(modelScale)
250
+
251
+ model.anchor.set(0.5, 0.5)
252
+ model.x = stageWidth / 2
253
+ model.y = stageHeight / 2 + stageHeight * 0.55
254
+ }
255
+
256
+ const resizeObserver = new ResizeObserver(() => {
257
+ handleResize()
258
+ })
259
+
260
+ resizeObserver.observe(containerRef.current)
261
+
262
+ // Trigger immediate resize/reposition
263
+ handleResize()
264
+
265
+ return () => {
266
+ resizeObserver.disconnect()
267
+ }
268
+ }, [scale, loading, isActive])
269
+
270
+ useEffect(() => {
271
+ const updateRenderState = () => {
272
+ setRenderActive(isActive && document.visibilityState === 'visible')
273
+ }
274
+
275
+ updateRenderState()
276
+ document.addEventListener('visibilitychange', updateRenderState)
277
+
278
+ return () => {
279
+ document.removeEventListener('visibilitychange', updateRenderState)
280
+ }
281
+ }, [isActive])
282
+
283
+ // Follow the pointer across the whole window and smoothly return to center when tracking stops.
284
+ useEffect(() => {
285
+ const focus = (x: number, y: number) => {
286
+ if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) {
287
+ x = 0
288
+ y = 0
289
+ }
290
+ modelRef.current?.internalModel?.focusController?.focus(x, y)
291
+ }
292
+
293
+ const centerFocus = () => focus(0, 0)
294
+
295
+ const handlePointerMove = (event: PointerEvent) => {
296
+ if (isLocked || !isActive || document.visibilityState !== 'visible' || !containerRef.current) return
297
+
298
+ const rect = containerRef.current.getBoundingClientRect()
299
+ if (!rect.width || !rect.height) return
300
+
301
+ const x = (event.clientX - (rect.left + rect.width / 2)) / (rect.width / 2)
302
+ const y = ((rect.top + rect.height / 2) - event.clientY) / (rect.height / 2)
303
+ const normalized = clampToUnitCircle(x, y)
304
+
305
+ focus(normalized.x, normalized.y)
306
+ }
307
+
308
+ if (isLocked) centerFocus()
309
+
310
+ window.addEventListener('pointermove', handlePointerMove)
311
+ window.addEventListener('blur', centerFocus)
312
+
313
+ return () => {
314
+ window.removeEventListener('pointermove', handlePointerMove)
315
+ window.removeEventListener('blur', centerFocus)
316
+ }
317
+ }, [isLocked, isActive])
318
+
319
+ // Update expressions & accessories dynamically
320
+ useEffect(() => {
321
+ if (!modelRef.current) return
322
+ applyExpressionAndAccessory(modelRef.current, expressionIndex, accessoryIndex)
323
+ }, [expressionIndex, accessoryIndex])
324
+
325
+ const applyExpressionAndAccessory = (model: any, exprIdx: number, accIdx: number) => {
326
+ const expManager = model.internalModel?.motionManager?.expressionManager
327
+ if (!expManager) return
328
+
329
+ const exprName = EXPRESSION_MAP[exprIdx]
330
+ const accName = ACCESSORY_MAP[accIdx]
331
+
332
+ if (!exprName && !accName) {
333
+ expManager.resetExpression()
334
+ return
335
+ }
336
+
337
+ // Apply the active expression or accessory
338
+ if (exprName) {
339
+ model.expression(exprName)
340
+ } else if (accName) {
341
+ model.expression(accName)
342
+ }
343
+ }
344
+
345
+ return (
346
+ <div
347
+ ref={containerRef}
348
+ style={{
349
+ position: 'relative',
350
+ width: '100%',
351
+ height: '100%',
352
+ display: 'flex',
353
+ alignItems: 'center',
354
+ justifyContent: 'center',
355
+ }}
356
+ >
357
+ {loading && (
358
+ <div style={{ position: 'absolute', color: '#9f7aea', fontSize: '14px', fontFamily: 'Outfit, sans-serif' }}>
359
+ Loading Shiroko Live2D Model...
360
+ </div>
361
+ )}
362
+ <canvas
363
+ ref={canvasRef}
364
+ style={{
365
+ width: '100%',
366
+ height: '100%',
367
+ opacity: loading ? 0 : 1,
368
+ transition: 'opacity 0.3s ease-out',
369
+ pointerEvents: isLocked ? 'none' : 'auto',
370
+ }}
371
+ />
372
+ </div>
373
+ )
374
+ }