@pheem49/mint 1.5.2 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GUIDE_TH.md +7 -7
- package/README.md +132 -66
- package/assets/Agent_Mint.png +0 -0
- package/assets/Settings.png +0 -0
- package/main.js +12 -0
- package/package.json +3 -2
- package/preload.js +2 -0
- package/scripts/install_linux_desktop_entry.js +48 -0
- package/src/AI_Brain/Gemini_API.js +194 -491
- package/src/AI_Brain/autonomous_brain.js +46 -19
- package/src/AI_Brain/headless_agent.js +21 -2
- package/src/AI_Brain/provider_adapter.js +358 -0
- package/src/CLI/approval_handler.js +5 -0
- package/src/CLI/chat_router.js +7 -0
- package/src/CLI/chat_ui.js +397 -76
- package/src/CLI/cli_colors.js +86 -3
- package/src/CLI/cli_formatters.js +6 -1
- package/src/CLI/code_agent.js +706 -273
- package/src/CLI/interactive_chat.js +311 -149
- package/src/CLI/slash_command_handler.js +2 -2
- package/src/CLI/updater.js +21 -1
- package/src/System/config_manager.js +4 -1
- package/src/System/ipc_handlers.js +10 -0
- package/src/System/picture_store.js +109 -0
- package/src/System/task_manager.js +127 -0
- package/src/System/tool_registry.js +13 -0
- package/src/System/window_manager.js +16 -8
- package/src/UI/live2d_manager.js +42 -8
- package/src/UI/renderer.js +457 -42
- package/src/UI/settings.css +160 -96
- package/src/UI/settings.html +9 -0
- package/src/UI/settings.js +34 -2
- package/src/UI/styles.css +1350 -117
- package/privacy.txt +0 -1
|
@@ -39,6 +39,9 @@ const DEFAULT_CONFIG = {
|
|
|
39
39
|
customBgStart: '#0f172a',
|
|
40
40
|
customBgEnd: '#1e1b4b',
|
|
41
41
|
customPanelBg: '#1e293b',
|
|
42
|
+
glassBlur: 'blur(16px)',
|
|
43
|
+
fontFamily: "'Outfit', sans-serif",
|
|
44
|
+
fontSize: '15px',
|
|
42
45
|
apiKey: '',
|
|
43
46
|
geminiModel: 'gemini-2.5-flash',
|
|
44
47
|
language: 'th-TH',
|
|
@@ -187,4 +190,4 @@ function isPlaceholder(val) {
|
|
|
187
190
|
return !val || val.startsWith('your_') || val.includes('key_here') || val.trim() === '';
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
module.exports = { readConfig, writeConfig, getAvailableProviders, isPlaceholder, CONFIG_PATH };
|
|
193
|
+
module.exports = { readConfig, writeConfig, getAvailableProviders, isPlaceholder, CONFIG_PATH, CONFIG_DIR };
|
|
@@ -17,6 +17,8 @@ function registerIpcHandlers({
|
|
|
17
17
|
getWeather,
|
|
18
18
|
readConfig,
|
|
19
19
|
writeConfig,
|
|
20
|
+
saveChatImages,
|
|
21
|
+
listSavedPictures,
|
|
20
22
|
parseCommand,
|
|
21
23
|
executeAction,
|
|
22
24
|
getGoogleTtsUrls,
|
|
@@ -25,6 +27,10 @@ function registerIpcHandlers({
|
|
|
25
27
|
|
|
26
28
|
ipcMain.handle('chat-message', async (event, message, base64Image = null, base64Audio = null) => {
|
|
27
29
|
try {
|
|
30
|
+
if (base64Image && saveChatImages) {
|
|
31
|
+
saveChatImages(base64Image, { source: 'chat', message });
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
const rawResponse = await handleChat(message, base64Image, base64Audio);
|
|
29
35
|
const aiResponse = parseCommand(rawResponse);
|
|
30
36
|
|
|
@@ -79,6 +85,10 @@ function registerIpcHandlers({
|
|
|
79
85
|
|
|
80
86
|
ipcMain.handle('get-chat-history', () => getChatTranscript());
|
|
81
87
|
|
|
88
|
+
ipcMain.handle('list-saved-pictures', () => {
|
|
89
|
+
return listSavedPictures ? listSavedPictures() : [];
|
|
90
|
+
});
|
|
91
|
+
|
|
82
92
|
ipcMain.handle('open-settings', () => {
|
|
83
93
|
windowManager.createSettingsWindow();
|
|
84
94
|
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { pathToFileURL } = require('url');
|
|
6
|
+
|
|
7
|
+
const PICTURES_DIR = path.join(os.homedir(), '.config', 'mint', 'Pictures');
|
|
8
|
+
const INDEX_PATH = path.join(PICTURES_DIR, 'pictures.json');
|
|
9
|
+
|
|
10
|
+
const EXTENSIONS = {
|
|
11
|
+
'image/png': 'png',
|
|
12
|
+
'image/jpeg': 'jpg',
|
|
13
|
+
'image/jpg': 'jpg',
|
|
14
|
+
'image/webp': 'webp',
|
|
15
|
+
'image/gif': 'gif'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function ensurePicturesDir() {
|
|
19
|
+
fs.mkdirSync(PICTURES_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readIndex() {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(INDEX_PATH)) return [];
|
|
25
|
+
const parsed = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
|
|
26
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('[Pictures] Failed to read index:', error.message);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeIndex(entries) {
|
|
34
|
+
ensurePicturesDir();
|
|
35
|
+
fs.writeFileSync(INDEX_PATH, JSON.stringify(entries, null, 2), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseImageDataUri(dataUri) {
|
|
39
|
+
if (!dataUri || typeof dataUri !== 'string') return null;
|
|
40
|
+
const match = dataUri.match(/^data:(image\/[\w.+-]+);base64,([\s\S]+)$/);
|
|
41
|
+
if (!match) return null;
|
|
42
|
+
|
|
43
|
+
const mimeType = match[1].toLowerCase();
|
|
44
|
+
const extension = EXTENSIONS[mimeType] || 'png';
|
|
45
|
+
return {
|
|
46
|
+
mimeType,
|
|
47
|
+
extension,
|
|
48
|
+
buffer: Buffer.from(match[2], 'base64')
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createFilename(extension) {
|
|
53
|
+
const stamp = new Date().toISOString()
|
|
54
|
+
.replace(/[-:]/g, '')
|
|
55
|
+
.replace(/\..+$/, '')
|
|
56
|
+
.replace('T', '-');
|
|
57
|
+
const id = crypto.randomBytes(4).toString('hex');
|
|
58
|
+
return `mint-${stamp}-${id}.${extension}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function saveChatImages(base64Image, metadata = {}) {
|
|
62
|
+
const images = Array.isArray(base64Image) ? base64Image : (base64Image ? [base64Image] : []);
|
|
63
|
+
const saved = [];
|
|
64
|
+
if (images.length === 0) return saved;
|
|
65
|
+
|
|
66
|
+
ensurePicturesDir();
|
|
67
|
+
const index = readIndex();
|
|
68
|
+
|
|
69
|
+
for (const item of images) {
|
|
70
|
+
const parsed = parseImageDataUri(item);
|
|
71
|
+
if (!parsed || parsed.buffer.length === 0) continue;
|
|
72
|
+
|
|
73
|
+
const filename = createFilename(parsed.extension);
|
|
74
|
+
const filePath = path.join(PICTURES_DIR, filename);
|
|
75
|
+
fs.writeFileSync(filePath, parsed.buffer);
|
|
76
|
+
|
|
77
|
+
const entry = {
|
|
78
|
+
id: path.basename(filename, path.extname(filename)),
|
|
79
|
+
filename,
|
|
80
|
+
path: filePath,
|
|
81
|
+
mimeType: parsed.mimeType,
|
|
82
|
+
createdAt: new Date().toISOString(),
|
|
83
|
+
source: metadata.source || 'chat',
|
|
84
|
+
message: String(metadata.message || '').slice(0, 240)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
index.unshift(entry);
|
|
88
|
+
saved.push(entry);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
writeIndex(index);
|
|
92
|
+
return saved;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function listSavedPictures() {
|
|
96
|
+
ensurePicturesDir();
|
|
97
|
+
return readIndex()
|
|
98
|
+
.filter(entry => entry && entry.path && fs.existsSync(entry.path))
|
|
99
|
+
.map(entry => ({
|
|
100
|
+
...entry,
|
|
101
|
+
url: pathToFileURL(entry.path).href
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
PICTURES_DIR,
|
|
107
|
+
saveChatImages,
|
|
108
|
+
listSavedPictures
|
|
109
|
+
};
|
|
@@ -57,6 +57,12 @@ function addTask(description) {
|
|
|
57
57
|
createdAt: new Date().toISOString(),
|
|
58
58
|
updatedAt: new Date().toISOString(),
|
|
59
59
|
steps: [],
|
|
60
|
+
subtasks: [],
|
|
61
|
+
checkpoints: [],
|
|
62
|
+
artifacts: [],
|
|
63
|
+
retryCount: 0,
|
|
64
|
+
maxRetries: 1,
|
|
65
|
+
lastCheckpointAt: null,
|
|
60
66
|
result: null
|
|
61
67
|
};
|
|
62
68
|
tasks.push(newTask);
|
|
@@ -80,6 +86,120 @@ function updateTask(id, updates) {
|
|
|
80
86
|
return null;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
function getTask(id) {
|
|
90
|
+
return readTasks().find(t => t.id === id) || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeTask(task) {
|
|
94
|
+
return {
|
|
95
|
+
...task,
|
|
96
|
+
steps: Array.isArray(task.steps) ? task.steps : [],
|
|
97
|
+
subtasks: Array.isArray(task.subtasks) ? task.subtasks : [],
|
|
98
|
+
checkpoints: Array.isArray(task.checkpoints) ? task.checkpoints : [],
|
|
99
|
+
artifacts: Array.isArray(task.artifacts) ? task.artifacts : [],
|
|
100
|
+
retryCount: Number.isFinite(task.retryCount) ? task.retryCount : 0,
|
|
101
|
+
maxRetries: Number.isFinite(task.maxRetries) ? task.maxRetries : 1
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mutateTask(id, mutator) {
|
|
106
|
+
const tasks = readTasks();
|
|
107
|
+
const idx = tasks.findIndex(t => t.id === id);
|
|
108
|
+
if (idx === -1) return null;
|
|
109
|
+
const next = normalizeTask(tasks[idx]);
|
|
110
|
+
mutator(next);
|
|
111
|
+
next.updatedAt = new Date().toISOString();
|
|
112
|
+
tasks[idx] = next;
|
|
113
|
+
writeTasks(tasks);
|
|
114
|
+
return next;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function addSubtask(taskId, title, extra = {}) {
|
|
118
|
+
return mutateTask(taskId, task => {
|
|
119
|
+
task.subtasks.push({
|
|
120
|
+
id: `${taskId}-${task.subtasks.length + 1}`,
|
|
121
|
+
title,
|
|
122
|
+
status: extra.status || 'pending',
|
|
123
|
+
createdAt: new Date().toISOString(),
|
|
124
|
+
updatedAt: new Date().toISOString(),
|
|
125
|
+
...extra
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function updateSubtask(taskId, subtaskId, updates = {}) {
|
|
131
|
+
return mutateTask(taskId, task => {
|
|
132
|
+
const subtask = task.subtasks.find(item => item.id === subtaskId);
|
|
133
|
+
if (!subtask) return;
|
|
134
|
+
Object.assign(subtask, updates, { updatedAt: new Date().toISOString() });
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addCheckpoint(taskId, checkpoint = {}) {
|
|
139
|
+
return mutateTask(taskId, task => {
|
|
140
|
+
const entry = {
|
|
141
|
+
id: `${taskId}-checkpoint-${task.checkpoints.length + 1}`,
|
|
142
|
+
time: new Date().toISOString(),
|
|
143
|
+
...checkpoint
|
|
144
|
+
};
|
|
145
|
+
task.checkpoints.push(entry);
|
|
146
|
+
task.lastCheckpointAt = entry.time;
|
|
147
|
+
task.steps.push(entry);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function addArtifact(taskId, artifact = {}) {
|
|
152
|
+
return mutateTask(taskId, task => {
|
|
153
|
+
task.artifacts.push({
|
|
154
|
+
id: `${taskId}-artifact-${task.artifacts.length + 1}`,
|
|
155
|
+
time: new Date().toISOString(),
|
|
156
|
+
...artifact
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function failTaskWithRetry(id, errorMessage) {
|
|
162
|
+
return mutateTask(id, task => {
|
|
163
|
+
const retryCount = Number(task.retryCount) || 0;
|
|
164
|
+
const maxRetries = Number.isFinite(task.maxRetries) ? task.maxRetries : 1;
|
|
165
|
+
task.result = errorMessage;
|
|
166
|
+
task.retryCount = retryCount + 1;
|
|
167
|
+
task.status = task.retryCount <= maxRetries ? 'pending' : 'failed';
|
|
168
|
+
const checkpoint = {
|
|
169
|
+
id: `${id}-checkpoint-${task.checkpoints.length + 1}`,
|
|
170
|
+
time: new Date().toISOString(),
|
|
171
|
+
phase: task.status === 'pending' ? 'retry_scheduled' : 'failed',
|
|
172
|
+
message: errorMessage,
|
|
173
|
+
retryCount: task.retryCount,
|
|
174
|
+
maxRetries
|
|
175
|
+
};
|
|
176
|
+
task.checkpoints.push(checkpoint);
|
|
177
|
+
task.steps.push(checkpoint);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resumeRunningTasks() {
|
|
182
|
+
const resumed = [];
|
|
183
|
+
const tasks = readTasks().map(task => {
|
|
184
|
+
if (task.status !== 'running') return task;
|
|
185
|
+
const normalized = normalizeTask(task);
|
|
186
|
+
normalized.status = 'pending';
|
|
187
|
+
const checkpoint = {
|
|
188
|
+
id: `${normalized.id}-checkpoint-${normalized.checkpoints.length + 1}`,
|
|
189
|
+
time: new Date().toISOString(),
|
|
190
|
+
phase: 'resume_after_restart',
|
|
191
|
+
message: 'Task was running during shutdown and has been re-queued.'
|
|
192
|
+
};
|
|
193
|
+
normalized.checkpoints.push(checkpoint);
|
|
194
|
+
normalized.steps.push(checkpoint);
|
|
195
|
+
normalized.updatedAt = new Date().toISOString();
|
|
196
|
+
resumed.push(normalized);
|
|
197
|
+
return normalized;
|
|
198
|
+
});
|
|
199
|
+
writeTasks(tasks);
|
|
200
|
+
return resumed;
|
|
201
|
+
}
|
|
202
|
+
|
|
83
203
|
function clearCompletedTasks() {
|
|
84
204
|
const tasks = readTasks();
|
|
85
205
|
const activeTasks = tasks.filter(t => t.status === 'pending' || t.status === 'running');
|
|
@@ -88,8 +208,15 @@ function clearCompletedTasks() {
|
|
|
88
208
|
|
|
89
209
|
module.exports = {
|
|
90
210
|
addTask,
|
|
211
|
+
addArtifact,
|
|
212
|
+
addCheckpoint,
|
|
213
|
+
addSubtask,
|
|
214
|
+
failTaskWithRetry,
|
|
215
|
+
getTask,
|
|
91
216
|
getPendingTask,
|
|
217
|
+
resumeRunningTasks,
|
|
92
218
|
updateTask,
|
|
219
|
+
updateSubtask,
|
|
93
220
|
readTasks,
|
|
94
221
|
clearCompletedTasks
|
|
95
222
|
};
|
|
@@ -41,6 +41,19 @@ const TOOL_REGISTRY = Object.freeze({
|
|
|
41
41
|
important: true,
|
|
42
42
|
description: 'Run a non-destructive shell command after user approval.'
|
|
43
43
|
},
|
|
44
|
+
verify: {
|
|
45
|
+
permission: 'approval',
|
|
46
|
+
required: [],
|
|
47
|
+
codeAgentOnly: true,
|
|
48
|
+
important: true,
|
|
49
|
+
description: 'Run test/build/lint verification commands after user approval.'
|
|
50
|
+
},
|
|
51
|
+
plan: {
|
|
52
|
+
permission: 'approval',
|
|
53
|
+
required: ['plan'],
|
|
54
|
+
codeAgentOnly: true,
|
|
55
|
+
description: 'Present a multi-file edit plan before changing files.'
|
|
56
|
+
},
|
|
44
57
|
apply_patch: {
|
|
45
58
|
permission: 'approval',
|
|
46
59
|
required: ['patch'],
|
|
@@ -9,12 +9,19 @@ function createWindowManager(projectRoot) {
|
|
|
9
9
|
let tray = null;
|
|
10
10
|
|
|
11
11
|
function createMainWindow() {
|
|
12
|
+
const iconPath = path.join(projectRoot, 'assets', 'icon.png');
|
|
13
|
+
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize;
|
|
14
|
+
const windowWidth = Math.min(1360, Math.max(1180, screenWidth - 40));
|
|
15
|
+
const windowHeight = Math.min(920, Math.max(860, screenHeight - 40));
|
|
16
|
+
|
|
12
17
|
mainWindow = new BrowserWindow({
|
|
13
|
-
width:
|
|
14
|
-
height:
|
|
18
|
+
width: windowWidth,
|
|
19
|
+
height: windowHeight,
|
|
15
20
|
minWidth: 900,
|
|
16
21
|
minHeight: 680,
|
|
17
|
-
|
|
22
|
+
x: Math.floor((screenWidth - windowWidth) / 2),
|
|
23
|
+
y: Math.floor((screenHeight - windowHeight) / 2),
|
|
24
|
+
icon: nativeImage.createFromPath(iconPath),
|
|
18
25
|
webPreferences: {
|
|
19
26
|
preload: path.join(projectRoot, 'preload.js'),
|
|
20
27
|
nodeIntegration: false,
|
|
@@ -75,12 +82,13 @@ function createWindowManager(projectRoot) {
|
|
|
75
82
|
return settingsWindow;
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
const iconPath = path.join(projectRoot, 'assets', 'icon.png');
|
|
78
86
|
settingsWindow = new BrowserWindow({
|
|
79
|
-
width:
|
|
80
|
-
height:
|
|
81
|
-
minWidth:
|
|
82
|
-
minHeight:
|
|
83
|
-
icon:
|
|
87
|
+
width: 1020,
|
|
88
|
+
height: 720,
|
|
89
|
+
minWidth: 860,
|
|
90
|
+
minHeight: 620,
|
|
91
|
+
icon: nativeImage.createFromPath(iconPath),
|
|
84
92
|
webPreferences: {
|
|
85
93
|
preload: path.join(projectRoot, 'preload-settings.js'),
|
|
86
94
|
nodeIntegration: false,
|
package/src/UI/live2d_manager.js
CHANGED
|
@@ -18,6 +18,9 @@ window.Live2DManager = {
|
|
|
18
18
|
cat: { paramId: 'Param54', label: 'Cat Filter' }
|
|
19
19
|
},
|
|
20
20
|
pointerTrackingEnabled: true,
|
|
21
|
+
zoomMultiplier: 1,
|
|
22
|
+
interactionZoneOrigin: { x: 0.5, y: 0.58 },
|
|
23
|
+
fitModelToMount: null,
|
|
21
24
|
pointerTrackingFrame: null,
|
|
22
25
|
pointerTracking: {
|
|
23
26
|
targetX: 0,
|
|
@@ -135,7 +138,7 @@ window.Live2DManager = {
|
|
|
135
138
|
const heightScale = mountHeight / Math.max(modelHeight, 1);
|
|
136
139
|
|
|
137
140
|
// Reduced zoom to 2.0 as requested
|
|
138
|
-
const scale = Math.min(widthScale, heightScale) * 1.85;
|
|
141
|
+
const scale = Math.min(widthScale, heightScale) * 1.85 * this.zoomMultiplier;
|
|
139
142
|
|
|
140
143
|
this.model.scale.set(scale);
|
|
141
144
|
// Adjusted Y offset to 1.0 as requested
|
|
@@ -145,6 +148,7 @@ window.Live2DManager = {
|
|
|
145
148
|
};
|
|
146
149
|
this.applyModelFollowOffset();
|
|
147
150
|
};
|
|
151
|
+
this.fitModelToMount = fitModel;
|
|
148
152
|
|
|
149
153
|
requestAnimationFrame(() => {
|
|
150
154
|
fitModel();
|
|
@@ -214,9 +218,9 @@ window.Live2DManager = {
|
|
|
214
218
|
try {
|
|
215
219
|
const point = this.getPointerViewportPoint(event);
|
|
216
220
|
if (!point) return null;
|
|
217
|
-
const { x, y } = point;
|
|
221
|
+
const { x, y } = this.toInteractionZonePoint(point);
|
|
218
222
|
|
|
219
|
-
if (this.isPointInZone(x, y, 0.
|
|
223
|
+
if (this.isPointInZone(x, y, 0.36, 0.375, 0.28, 0.12)) {
|
|
220
224
|
return {
|
|
221
225
|
id: 'face',
|
|
222
226
|
label: 'Cat Ears',
|
|
@@ -225,7 +229,7 @@ window.Live2DManager = {
|
|
|
225
229
|
};
|
|
226
230
|
}
|
|
227
231
|
|
|
228
|
-
if (this.isPointInZone(x, y, 0.34, 0.
|
|
232
|
+
if (this.isPointInZone(x, y, 0.34, 0.205, 0.32, 0.155)) {
|
|
229
233
|
return {
|
|
230
234
|
id: 'head',
|
|
231
235
|
label: 'Head Pat',
|
|
@@ -234,8 +238,8 @@ window.Live2DManager = {
|
|
|
234
238
|
};
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
const isLeftHand = this.isPointInZone(x, y, 0.
|
|
238
|
-
const isRightHand = this.isPointInZone(x, y, 0.
|
|
241
|
+
const isLeftHand = this.isPointInZone(x, y, 0.14, 0.70, 0.22, 0.16);
|
|
242
|
+
const isRightHand = this.isPointInZone(x, y, 0.65, 0.69, 0.23, 0.17);
|
|
239
243
|
if (isLeftHand || isRightHand) {
|
|
240
244
|
return {
|
|
241
245
|
id: isLeftHand ? 'left-hand' : 'right-hand',
|
|
@@ -245,7 +249,7 @@ window.Live2DManager = {
|
|
|
245
249
|
};
|
|
246
250
|
}
|
|
247
251
|
|
|
248
|
-
if (this.isPointInZone(x, y, 0.
|
|
252
|
+
if (this.isPointInZone(x, y, 0.34, 0.74, 0.30, 0.24)) {
|
|
249
253
|
return {
|
|
250
254
|
id: 'lower-body',
|
|
251
255
|
label: 'Careful',
|
|
@@ -254,7 +258,7 @@ window.Live2DManager = {
|
|
|
254
258
|
};
|
|
255
259
|
}
|
|
256
260
|
|
|
257
|
-
if (this.isPointInZone(x, y, 0.
|
|
261
|
+
if (this.isPointInZone(x, y, 0.36, 0.53, 0.29, 0.145)) {
|
|
258
262
|
return {
|
|
259
263
|
id: 'body',
|
|
260
264
|
label: 'Shoulder Tap',
|
|
@@ -293,6 +297,17 @@ window.Live2DManager = {
|
|
|
293
297
|
return x >= left && x <= left + width && y >= top && y <= top + height;
|
|
294
298
|
},
|
|
295
299
|
|
|
300
|
+
toInteractionZonePoint(point) {
|
|
301
|
+
const scale = this.zoomMultiplier || 1;
|
|
302
|
+
if (Math.abs(scale - 1) < 0.001) return point;
|
|
303
|
+
|
|
304
|
+
const origin = this.interactionZoneOrigin;
|
|
305
|
+
return {
|
|
306
|
+
x: origin.x + (point.x - origin.x) / scale,
|
|
307
|
+
y: origin.y + (point.y - origin.y) / scale
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
|
|
296
311
|
cycleExpression() {
|
|
297
312
|
if (!this.model) return;
|
|
298
313
|
this.expIndex = (this.expIndex + 1) % this.expressionNames.length;
|
|
@@ -316,6 +331,25 @@ window.Live2DManager = {
|
|
|
316
331
|
this.model.buttonMode = this.interactionEnabled;
|
|
317
332
|
},
|
|
318
333
|
|
|
334
|
+
setPointerTrackingEnabled(isEnabled) {
|
|
335
|
+
this.pointerTrackingEnabled = Boolean(isEnabled);
|
|
336
|
+
if (this.pointerTrackingEnabled) return;
|
|
337
|
+
|
|
338
|
+
this.resetPointerTrackingTarget();
|
|
339
|
+
this.pointerTracking.currentX = 0;
|
|
340
|
+
this.pointerTracking.currentY = 0;
|
|
341
|
+
this.applyModelFollowOffset();
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
setZoomMultiplier(multiplier) {
|
|
345
|
+
const value = Number(multiplier);
|
|
346
|
+
this.zoomMultiplier = this.clamp(Number.isFinite(value) ? value : 1, 0.78, 1.28);
|
|
347
|
+
document.documentElement.style.setProperty('--model-zone-scale', String(this.zoomMultiplier));
|
|
348
|
+
if (typeof this.fitModelToMount === 'function') {
|
|
349
|
+
this.fitModelToMount();
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
|
|
319
353
|
getSavedInteractionEnabled() {
|
|
320
354
|
try {
|
|
321
355
|
return localStorage.getItem(this.interactionStorageKey) !== 'false';
|