claude-remote 0.6.0 → 0.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.
- package/bin/claude-remote.js +1 -1
- package/hooks/bridge-session-start.js +32 -32
- package/lib/http-server.js +60 -27
- package/lib/interactive-questions.js +183 -0
- package/lib/logger.js +172 -138
- package/lib/state.js +7 -6
- package/lib/ws-server.js +132 -96
- package/package.json +1 -1
- package/server.js +18 -17
- package/web/index.html +42 -5
- package/web/modules/interactions.js +205 -86
- package/web/modules/settings.js +101 -74
- package/web/modules/state.js +2 -0
- package/web/modules/websocket.js +10 -7
- package/web/styles.css +321 -84
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { state, LOG_FILE } = require('./lib/state');
|
|
5
|
-
const { initConfig } = require('./lib/cli');
|
|
6
|
-
const { log } = require('./lib/logger');
|
|
7
|
-
const { createHttpServer } = require('./lib/http-server');
|
|
8
|
-
const { setupWebSocketServer } = require('./lib/ws-server');
|
|
9
|
-
const { spawnClaude } = require('./lib/pty-manager');
|
|
10
|
-
const { setupHooks } = require('./lib/hooks');
|
|
11
|
-
const { startUploadCleanup } = require('./lib/image-upload');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { state, LOG_FILE } = require('./lib/state');
|
|
5
|
+
const { initConfig } = require('./lib/cli');
|
|
6
|
+
const { initLogger, log } = require('./lib/logger');
|
|
7
|
+
const { createHttpServer } = require('./lib/http-server');
|
|
8
|
+
const { setupWebSocketServer } = require('./lib/ws-server');
|
|
9
|
+
const { spawnClaude } = require('./lib/pty-manager');
|
|
10
|
+
const { setupHooks } = require('./lib/hooks');
|
|
11
|
+
const { startUploadCleanup } = require('./lib/image-upload');
|
|
12
12
|
|
|
13
13
|
// --- Initialize config from CLI args + env ---
|
|
14
14
|
const config = initConfig();
|
|
15
15
|
state.PORT = config.PORT;
|
|
16
16
|
state.CWD = config.CWD;
|
|
17
17
|
state.AUTH_TOKEN = config.AUTH_TOKEN;
|
|
18
|
-
state.AUTH_DISABLED = config.AUTH_DISABLED;
|
|
19
|
-
state.ENABLE_WEB = config.ENABLE_WEB;
|
|
20
|
-
state.CLAUDE_EXTRA_ARGS = config.CLAUDE_EXTRA_ARGS;
|
|
21
|
-
state.DEBUG_TTY_INPUT = config.DEBUG_TTY_INPUT;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
state.AUTH_DISABLED = config.AUTH_DISABLED;
|
|
19
|
+
state.ENABLE_WEB = config.ENABLE_WEB;
|
|
20
|
+
state.CLAUDE_EXTRA_ARGS = config.CLAUDE_EXTRA_ARGS;
|
|
21
|
+
state.DEBUG_TTY_INPUT = config.DEBUG_TTY_INPUT;
|
|
22
|
+
initLogger();
|
|
23
|
+
|
|
24
|
+
// --- Create servers ---
|
|
25
|
+
const server = createHttpServer();
|
|
26
|
+
setupWebSocketServer(server);
|
|
26
27
|
|
|
27
28
|
// --- Start periodic cleanup ---
|
|
28
29
|
startUploadCleanup();
|
package/web/index.html
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
6
6
|
<title>Claude Remote</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
8
7
|
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
<script>
|
|
9
|
+
(function(){
|
|
10
|
+
var t = localStorage.getItem('theme');
|
|
11
|
+
if (t === 'light' || t === 'dark')
|
|
12
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
13
|
+
})();
|
|
14
|
+
</script>
|
|
9
15
|
</head>
|
|
10
16
|
<body>
|
|
11
17
|
|
|
@@ -198,16 +204,20 @@
|
|
|
198
204
|
<div class="question-icon">
|
|
199
205
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"/><circle cx="12" cy="17" r=".5"/></svg>
|
|
200
206
|
</div>
|
|
201
|
-
<div>
|
|
207
|
+
<div style="flex:1">
|
|
202
208
|
<div class="question-title" id="question-header-text">Question</div>
|
|
203
209
|
<div class="question-sub">Claude needs your input</div>
|
|
204
210
|
</div>
|
|
211
|
+
<div class="question-progress" id="question-progress" style="display:none">1 / 3</div>
|
|
205
212
|
</div>
|
|
206
213
|
<div class="question-text" id="question-text"></div>
|
|
207
214
|
<div class="question-options" id="question-options"></div>
|
|
208
215
|
<div class="question-other-row">
|
|
209
216
|
<input class="question-other-input" id="question-other-input" type="text" placeholder="Other...">
|
|
210
|
-
|
|
217
|
+
</div>
|
|
218
|
+
<div class="question-nav" id="question-nav">
|
|
219
|
+
<button class="question-nav-btn" id="question-prev-btn" style="display:none">← Prev</button>
|
|
220
|
+
<button class="question-nav-btn question-nav-primary" id="question-next-btn">Submit</button>
|
|
211
221
|
</div>
|
|
212
222
|
</div>
|
|
213
223
|
</div>
|
|
@@ -242,6 +252,33 @@
|
|
|
242
252
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
243
253
|
</button>
|
|
244
254
|
</div>
|
|
255
|
+
<div class="settings-section">
|
|
256
|
+
<div class="settings-label">主题</div>
|
|
257
|
+
<div class="settings-desc">选择界面外观</div>
|
|
258
|
+
<div class="settings-options" id="theme-options">
|
|
259
|
+
<label class="settings-opt" data-theme-mode="system">
|
|
260
|
+
<input type="radio" name="theme-mode" value="system" checked>
|
|
261
|
+
<div class="settings-opt-body">
|
|
262
|
+
<div class="settings-opt-label">跟随系统</div>
|
|
263
|
+
<div class="settings-opt-desc">自动跟随设备的深色/浅色设置</div>
|
|
264
|
+
</div>
|
|
265
|
+
</label>
|
|
266
|
+
<label class="settings-opt" data-theme-mode="light">
|
|
267
|
+
<input type="radio" name="theme-mode" value="light">
|
|
268
|
+
<div class="settings-opt-body">
|
|
269
|
+
<div class="settings-opt-label">浅色</div>
|
|
270
|
+
<div class="settings-opt-desc">始终使用浅色主题</div>
|
|
271
|
+
</div>
|
|
272
|
+
</label>
|
|
273
|
+
<label class="settings-opt" data-theme-mode="dark">
|
|
274
|
+
<input type="radio" name="theme-mode" value="dark">
|
|
275
|
+
<div class="settings-opt-body">
|
|
276
|
+
<div class="settings-opt-label">深色</div>
|
|
277
|
+
<div class="settings-opt-desc">始终使用深色主题</div>
|
|
278
|
+
</div>
|
|
279
|
+
</label>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
245
282
|
<div class="settings-section">
|
|
246
283
|
<div class="settings-label">命令审批</div>
|
|
247
284
|
<div class="settings-desc">控制哪些工具需要手动审批</div>
|
|
@@ -339,8 +376,8 @@
|
|
|
339
376
|
</div>
|
|
340
377
|
</div>
|
|
341
378
|
</div>
|
|
342
|
-
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
343
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
379
|
+
<script async src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" crossorigin="anonymous"></script>
|
|
380
|
+
<script async src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" crossorigin="anonymous"></script>
|
|
344
381
|
<script type="module" src="main.js"></script>
|
|
345
382
|
</body>
|
|
346
383
|
</html>
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Interactions — AskUserQuestion + ExitPlanMode
|
|
3
|
-
// ============================================================
|
|
4
|
-
import { PLAN_OPTIONS } from './constants.js';
|
|
5
|
-
import { $, esc } from './utils.js';
|
|
6
|
-
import { S } from './state.js';
|
|
7
|
-
import { renderMd, renderPlanCard, consumePendingPlanCard } from './renderer.js';
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Interactions — AskUserQuestion + ExitPlanMode
|
|
3
|
+
// ============================================================
|
|
4
|
+
import { PLAN_OPTIONS } from './constants.js';
|
|
5
|
+
import { $, esc } from './utils.js';
|
|
6
|
+
import { S } from './state.js';
|
|
7
|
+
import { renderMd, renderPlanCard, consumePendingPlanCard } from './renderer.js';
|
|
8
|
+
import { showToast } from './toast.js';
|
|
8
9
|
|
|
9
10
|
let pendingInteractions = new Map();
|
|
10
11
|
let pendingInteractionOrder = [];
|
|
11
12
|
let questionQueue = [];
|
|
12
13
|
let currentQuestionItem = null;
|
|
13
|
-
let currentQuestionIdx = 0;
|
|
14
|
-
let activePlanToolUseId = null;
|
|
14
|
+
let currentQuestionIdx = 0;
|
|
15
|
+
let activePlanToolUseId = null;
|
|
16
|
+
let currentAnswers = [];
|
|
17
|
+
let currentOtherTexts = [];
|
|
18
|
+
let questionSubmitting = false;
|
|
15
19
|
|
|
16
20
|
export function resetInteractionState() {
|
|
17
21
|
pendingInteractions.clear();
|
|
@@ -19,10 +23,13 @@ export function resetInteractionState() {
|
|
|
19
23
|
questionQueue = [];
|
|
20
24
|
currentQuestionItem = null;
|
|
21
25
|
currentQuestionIdx = 0;
|
|
22
|
-
activePlanToolUseId = null;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
activePlanToolUseId = null;
|
|
27
|
+
currentAnswers = [];
|
|
28
|
+
currentOtherTexts = [];
|
|
29
|
+
questionSubmitting = false;
|
|
30
|
+
$('question-overlay').classList.remove('visible');
|
|
31
|
+
$('plan-overlay').classList.remove('visible');
|
|
32
|
+
}
|
|
26
33
|
|
|
27
34
|
export function enqueuePendingInteraction(toolUseId, kind, payload) {
|
|
28
35
|
if (!toolUseId) return;
|
|
@@ -119,19 +126,25 @@ function showNextQuestion() {
|
|
|
119
126
|
currentQuestionItem = null;
|
|
120
127
|
return;
|
|
121
128
|
}
|
|
122
|
-
currentQuestionItem = questionQueue[0];
|
|
123
|
-
currentQuestionIdx = 0;
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
currentQuestionItem = questionQueue[0];
|
|
130
|
+
currentQuestionIdx = 0;
|
|
131
|
+
currentAnswers = currentQuestionItem.questions.map(() => new Set());
|
|
132
|
+
currentOtherTexts = currentQuestionItem.questions.map(() => '');
|
|
133
|
+
questionSubmitting = false;
|
|
134
|
+
renderCurrentQuestion();
|
|
135
|
+
}
|
|
126
136
|
|
|
127
137
|
function finishCurrentQuestionItem(nextDelayMs = 0) {
|
|
128
138
|
const finishedToolUseId = currentQuestionItem?.toolUseId || '';
|
|
129
139
|
if (questionQueue.length > 0) questionQueue.shift();
|
|
130
|
-
currentQuestionItem = null;
|
|
131
|
-
currentQuestionIdx = 0;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
currentQuestionItem = null;
|
|
141
|
+
currentQuestionIdx = 0;
|
|
142
|
+
currentAnswers = [];
|
|
143
|
+
currentOtherTexts = [];
|
|
144
|
+
questionSubmitting = false;
|
|
145
|
+
if (finishedToolUseId) dropPendingInteraction(finishedToolUseId);
|
|
146
|
+
if (questionQueue.length > 0) {
|
|
147
|
+
setTimeout(showNextQuestion, nextDelayMs);
|
|
135
148
|
} else {
|
|
136
149
|
presentNextPendingInteraction();
|
|
137
150
|
}
|
|
@@ -141,12 +154,15 @@ function dismissQuestionInteraction(toolUseId) {
|
|
|
141
154
|
if (!toolUseId) return;
|
|
142
155
|
const wasCurrent = currentQuestionItem?.toolUseId === toolUseId;
|
|
143
156
|
questionQueue = questionQueue.filter(item => item.toolUseId !== toolUseId);
|
|
144
|
-
if (wasCurrent) {
|
|
145
|
-
currentQuestionItem = null;
|
|
146
|
-
currentQuestionIdx = 0;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
if (wasCurrent) {
|
|
158
|
+
currentQuestionItem = null;
|
|
159
|
+
currentQuestionIdx = 0;
|
|
160
|
+
currentAnswers = [];
|
|
161
|
+
currentOtherTexts = [];
|
|
162
|
+
questionSubmitting = false;
|
|
163
|
+
$('question-overlay').classList.remove('visible');
|
|
164
|
+
if (questionQueue.length > 0) {
|
|
165
|
+
setTimeout(showNextQuestion, 0);
|
|
150
166
|
} else {
|
|
151
167
|
presentNextPendingInteraction();
|
|
152
168
|
}
|
|
@@ -155,68 +171,166 @@ function dismissQuestionInteraction(toolUseId) {
|
|
|
155
171
|
|
|
156
172
|
function renderCurrentQuestion() {
|
|
157
173
|
if (!currentQuestionItem) return;
|
|
158
|
-
|
|
159
|
-
|
|
174
|
+
const questions = currentQuestionItem.questions;
|
|
175
|
+
if (currentQuestionIdx >= questions.length) {
|
|
176
|
+
submitAllAnswers();
|
|
160
177
|
return;
|
|
161
178
|
}
|
|
162
|
-
|
|
179
|
+
|
|
180
|
+
const q = questions[currentQuestionIdx];
|
|
181
|
+
const isMulti = !!q.multiSelect;
|
|
182
|
+
const total = questions.length;
|
|
183
|
+
const selected = currentAnswers[currentQuestionIdx] || new Set();
|
|
184
|
+
const otherText = currentOtherTexts[currentQuestionIdx] || '';
|
|
185
|
+
|
|
163
186
|
$('question-header-text').textContent = q.header || 'Question';
|
|
164
187
|
$('question-text').textContent = q.question || '';
|
|
165
188
|
|
|
189
|
+
const progressEl = $('question-progress');
|
|
190
|
+
if (total > 1) {
|
|
191
|
+
progressEl.textContent = `${currentQuestionIdx + 1} / ${total}`;
|
|
192
|
+
progressEl.style.display = '';
|
|
193
|
+
} else {
|
|
194
|
+
progressEl.style.display = 'none';
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
const optionsEl = $('question-options');
|
|
167
198
|
const options = q.options || [];
|
|
168
|
-
optionsEl.innerHTML = options.map((opt, i) =>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
${
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
199
|
+
optionsEl.innerHTML = options.map((opt, i) => {
|
|
200
|
+
const idx = i + 1;
|
|
201
|
+
const isSel = selected.has(idx);
|
|
202
|
+
return `
|
|
203
|
+
<button class="question-opt${isSel ? ' selected' : ''}${isMulti ? ' multi' : ''}" data-idx="${idx}"${questionSubmitting ? ' disabled' : ''}>
|
|
204
|
+
${isMulti
|
|
205
|
+
? `<span class="question-opt-check">${isSel ? '☑' : '☐'}</span>`
|
|
206
|
+
: `<span class="question-opt-num${isSel ? ' active' : ''}">${idx}</span>`}
|
|
207
|
+
<div class="question-opt-body">
|
|
208
|
+
<div class="question-opt-label">${esc(opt.label)}</div>
|
|
209
|
+
${opt.description ? `<div class="question-opt-desc">${esc(opt.description)}</div>` : ''}
|
|
210
|
+
</div>
|
|
211
|
+
</button>`;
|
|
212
|
+
}).join('');
|
|
213
|
+
|
|
214
|
+
optionsEl.querySelectorAll('.question-opt').forEach(btn => {
|
|
215
|
+
btn.addEventListener('click', () => {
|
|
216
|
+
if (questionSubmitting) return;
|
|
217
|
+
const idx = parseInt(btn.dataset.idx, 10);
|
|
218
|
+
isMulti ? toggleOption(idx) : selectOption(idx);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
$('question-other-input').value = otherText;
|
|
223
|
+
$('question-other-input').disabled = questionSubmitting;
|
|
224
|
+
|
|
225
|
+
const prevBtn = $('question-prev-btn');
|
|
226
|
+
const nextBtn = $('question-next-btn');
|
|
227
|
+
prevBtn.style.display = currentQuestionIdx > 0 ? '' : 'none';
|
|
228
|
+
prevBtn.disabled = questionSubmitting;
|
|
229
|
+
|
|
230
|
+
const isLast = currentQuestionIdx === total - 1;
|
|
231
|
+
nextBtn.disabled = questionSubmitting;
|
|
232
|
+
nextBtn.textContent = questionSubmitting
|
|
233
|
+
? 'Submitting...'
|
|
234
|
+
: ((!isLast && total > 1) ? 'Next \u2192' : (total > 1 ? 'Submit All' : 'Submit'));
|
|
235
|
+
|
|
236
|
+
$('question-overlay').classList.add('visible');
|
|
237
|
+
}
|
|
177
238
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
$('question-other-input').value = '';
|
|
186
|
-
$('question-overlay').classList.add('visible');
|
|
187
|
-
}
|
|
239
|
+
function selectOption(idx) {
|
|
240
|
+
if (!currentQuestionItem) return;
|
|
241
|
+
currentAnswers[currentQuestionIdx] = new Set([idx]);
|
|
242
|
+
currentOtherTexts[currentQuestionIdx] = '';
|
|
243
|
+
renderCurrentQuestion();
|
|
244
|
+
}
|
|
188
245
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
246
|
+
function toggleOption(idx) {
|
|
247
|
+
const selected = currentAnswers[currentQuestionIdx];
|
|
248
|
+
if (!selected) return;
|
|
249
|
+
if (selected.has(idx)) selected.delete(idx);
|
|
250
|
+
else selected.add(idx);
|
|
251
|
+
renderCurrentQuestion();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function goToPrev() {
|
|
255
|
+
if (!currentQuestionItem || currentQuestionIdx <= 0 || questionSubmitting) return;
|
|
256
|
+
currentQuestionIdx--;
|
|
257
|
+
renderCurrentQuestion();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function hasAnswerAt(index) {
|
|
261
|
+
const selected = currentAnswers[index];
|
|
262
|
+
if (selected && selected.size > 0) return true;
|
|
263
|
+
return !!String(currentOtherTexts[index] || '').trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function buildQuestionResponses() {
|
|
267
|
+
return currentQuestionItem.questions.map((_, index) => ({
|
|
268
|
+
selectedOptions: [...(currentAnswers[index] || [])].sort((a, b) => a - b),
|
|
269
|
+
otherText: String(currentOtherTexts[index] || '').trim(),
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function focusQuestion(index) {
|
|
274
|
+
currentQuestionIdx = index;
|
|
275
|
+
renderCurrentQuestion();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function goToNext() {
|
|
279
|
+
if (!currentQuestionItem || questionSubmitting) return;
|
|
280
|
+
if (!hasAnswerAt(currentQuestionIdx)) {
|
|
281
|
+
showToast('请先完成当前问题');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (currentQuestionIdx >= currentQuestionItem.questions.length - 1) {
|
|
285
|
+
submitAllAnswers();
|
|
286
|
+
} else {
|
|
287
|
+
currentQuestionIdx++;
|
|
288
|
+
renderCurrentQuestion();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function updateOtherText(value) {
|
|
293
|
+
if (!currentQuestionItem) return;
|
|
294
|
+
currentOtherTexts[currentQuestionIdx] = value;
|
|
295
|
+
const question = currentQuestionItem.questions[currentQuestionIdx];
|
|
296
|
+
if (!value.trim() || question?.multiSelect) return;
|
|
297
|
+
const selected = currentAnswers[currentQuestionIdx];
|
|
298
|
+
if (!selected || selected.size === 0) return;
|
|
299
|
+
currentAnswers[currentQuestionIdx] = new Set();
|
|
300
|
+
renderCurrentQuestion();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function handleQuestionSubmissionError(toolUseId, error) {
|
|
304
|
+
if (!currentQuestionItem) return;
|
|
305
|
+
if (toolUseId && currentQuestionItem.toolUseId && currentQuestionItem.toolUseId !== toolUseId) return;
|
|
306
|
+
questionSubmitting = false;
|
|
307
|
+
renderCurrentQuestion();
|
|
308
|
+
showToast(error || '提交问题答案失败');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function submitAllAnswers() {
|
|
312
|
+
if (!currentQuestionItem || questionSubmitting) return;
|
|
313
|
+
if (!S.ws || S.ws.readyState !== WebSocket.OPEN) {
|
|
314
|
+
showToast('Connection unavailable');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const firstIncompleteIdx = currentQuestionItem.questions.findIndex((_, index) => !hasAnswerAt(index));
|
|
319
|
+
if (firstIncompleteIdx >= 0) {
|
|
320
|
+
focusQuestion(firstIncompleteIdx);
|
|
321
|
+
showToast('请先完成所有问题');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
questionSubmitting = true;
|
|
326
|
+
renderCurrentQuestion();
|
|
327
|
+
S.ws.send(JSON.stringify({
|
|
328
|
+
type: 'answer_questions',
|
|
329
|
+
toolUseId: currentQuestionItem.toolUseId || '',
|
|
330
|
+
questions: currentQuestionItem.questions,
|
|
331
|
+
responses: buildQuestionResponses(),
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
220
334
|
|
|
221
335
|
// ---- Plan approval ----
|
|
222
336
|
export function normalizePlanContent(plan) {
|
|
@@ -292,11 +406,16 @@ function sendPlanOther() {
|
|
|
292
406
|
presentNextPendingInteraction();
|
|
293
407
|
}
|
|
294
408
|
|
|
295
|
-
export function initInteractions() {
|
|
296
|
-
$('question-
|
|
297
|
-
$('question-
|
|
298
|
-
|
|
299
|
-
|
|
409
|
+
export function initInteractions() {
|
|
410
|
+
$('question-prev-btn').addEventListener('click', goToPrev);
|
|
411
|
+
$('question-next-btn').addEventListener('click', goToNext);
|
|
412
|
+
$('question-other-input').addEventListener('input', e => {
|
|
413
|
+
if (questionSubmitting) return;
|
|
414
|
+
updateOtherText(e.target.value || '');
|
|
415
|
+
});
|
|
416
|
+
$('question-other-input').addEventListener('keydown', e => {
|
|
417
|
+
if (e.key === 'Enter') { e.preventDefault(); goToNext(); }
|
|
418
|
+
});
|
|
300
419
|
$('plan-other-btn').addEventListener('click', sendPlanOther);
|
|
301
420
|
$('plan-other-input').addEventListener('keydown', e => {
|
|
302
421
|
if (e.key === 'Enter') { e.preventDefault(); sendPlanOther(); }
|