Skip to content

Commit b5c87f2

Browse files
ChangingSelfclaude
andcommitted
feat(maibot): 添加 MaiBot 动作控制 API 和 WebUI
- 新增 /api/v1/maibot/action 端点用于控制 Amaidesu 动作和情绪 - 新增 WebUI 测试界面(DevTools 页签) - 新增 MaiBot 插件模板(integrations/amaidesu_plugin) - 支持 hotkey、expression、motion 动作类型 - 支持 happy、sad、angry 等情绪设置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e857ebc commit b5c87f2

9 files changed

Lines changed: 856 additions & 2 deletions

File tree

dashboard/src/api/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ export const debugApi = {
6464
getEventBusStats: () => api.get<EventBusStatsResponse>('/debug/event-bus/stats'),
6565
};
6666

67+
// MaiBot API
68+
export interface MaibotActionRequest {
69+
action?: string;
70+
action_params?: Record<string, unknown>;
71+
emotion?: string;
72+
priority?: number;
73+
text?: string;
74+
}
75+
76+
export interface MaibotActionResponse {
77+
success: boolean;
78+
intent_id?: string;
79+
message?: string;
80+
error?: string;
81+
}
82+
83+
export const maibotApi = {
84+
triggerAction: (request: MaibotActionRequest) =>
85+
api.post<MaibotActionResponse>('/maibot/action', request),
86+
};
87+
6788
// Message API
6889
export const messageApi = {
6990
getSessionMessages: (sessionId: string, limit: number = 100) =>

dashboard/src/views/DevTools.vue

Lines changed: 303 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,217 @@
207207
</section>
208208
</div>
209209
</el-tab-pane>
210+
211+
<!-- Tab 3: MaiBot 控制 -->
212+
<el-tab-pane name="maibot">
213+
<template #label>
214+
<span class="tab-label">
215+
<el-icon><Pointer /></el-icon>
216+
MaiBot 控制
217+
</span>
218+
</template>
219+
220+
<div class="tab-content-wrapper">
221+
<div class="inject-layout">
222+
<!-- 控制表单 -->
223+
<section class="inject-panel">
224+
<div class="panel-header">
225+
<div class="panel-title">
226+
<el-icon class="title-icon"><Pointer /></el-icon>
227+
<span>MaiBot 动作控制</span>
228+
</div>
229+
<el-tag size="small" type="success" effect="plain">外部控制</el-tag>
230+
</div>
231+
232+
<el-form
233+
:model="maibotForm"
234+
label-position="top"
235+
class="inject-form"
236+
@submit.prevent="triggerMaibotAction"
237+
>
238+
<el-form-item label="动作类型">
239+
<el-select
240+
v-model="maibotForm.action"
241+
placeholder="选择动作类型"
242+
clearable
243+
style="width: 100%"
244+
>
245+
<el-option label="hotkey (热键)" value="hotkey" />
246+
<el-option label="expression (表情)" value="expression" />
247+
<el-option label="motion (动作)" value="motion" />
248+
</el-select>
249+
</el-form-item>
250+
251+
<!-- 热键参数 -->
252+
<el-form-item v-if="maibotForm.action === 'hotkey'" label="选择热键">
253+
<el-select
254+
v-model="maibotForm.hotkey"
255+
placeholder="选择预设热键"
256+
style="width: 100%"
257+
>
258+
<el-option label="smile (微笑)" value="smile" />
259+
<el-option label="wave (挥手)" value="wave" />
260+
<el-option label="nod (点头)" value="nod" />
261+
<el-option label="shake (摇头)" value="shake" />
262+
<el-option label="clap (鼓掌)" value="clap" />
263+
<el-option label="dance (跳舞)" value="dance" />
264+
<el-option label="jump (跳跃)" value="jump" />
265+
<el-option label="sit (坐下)" value="sit" />
266+
<el-option label="lie (躺下)" value="lie" />
267+
<el-option label="run (跑步)" value="run" />
268+
</el-select>
269+
</el-form-item>
270+
271+
<!-- 表情参数 -->
272+
<el-form-item v-if="maibotForm.action === 'expression'" label="选择表情">
273+
<el-select
274+
v-model="maibotForm.expression"
275+
placeholder="选择预设表情"
276+
style="width: 100%"
277+
>
278+
<el-option label="happy (开心)" value="happy" />
279+
<el-option label="sad (难过)" value="sad" />
280+
<el-option label="angry (生气)" value="angry" />
281+
<el-option label="surprised (惊讶)" value="surprised" />
282+
<el-option label="scared (害怕)" value="scared" />
283+
<el-option label="embarrassed (尴尬)" value="embarrassed" />
284+
<el-option label="cry (哭泣)" value="cry" />
285+
<el-option label="laugh (大笑)" value="laugh" />
286+
</el-select>
287+
</el-form-item>
288+
289+
<!-- 动作参数 -->
290+
<el-form-item v-if="maibotForm.action === 'motion'" label="选择动作">
291+
<el-select
292+
v-model="maibotForm.motion"
293+
placeholder="选择预设动作"
294+
style="width: 100%"
295+
>
296+
<el-option label="wave (挥手)" value="wave" />
297+
<el-option label="nod (点头)" value="nod" />
298+
<el-option label="shake_head (摇头)" value="shake_head" />
299+
<el-option label="dance (跳舞)" value="dance" />
300+
<el-option label="jump (跳跃)" value="jump" />
301+
<el-option label="run (跑步)" value="run" />
302+
<el-option label="sit_down (坐下)" value="sit_down" />
303+
<el-option label="stand_up (站起来)" value="stand_up" />
304+
<el-option label="walk (走路)" value="walk" />
305+
<el-option label="attack (攻击)" value="attack" />
306+
</el-select>
307+
</el-form-item>
308+
309+
<el-form-item label="情绪类型">
310+
<el-select
311+
v-model="maibotForm.emotion"
312+
placeholder="选择情绪"
313+
clearable
314+
style="width: 100%"
315+
>
316+
<el-option label="happy (开心)" value="happy" />
317+
<el-option label="neutral (中性)" value="neutral" />
318+
<el-option label="sad (难过)" value="sad" />
319+
<el-option label="angry (生气)" value="angry" />
320+
<el-option label="excited (兴奋)" value="excited" />
321+
<el-option label="shy (害羞)" value="shy" />
322+
</el-select>
323+
</el-form-item>
324+
325+
<el-form-item label="优先级">
326+
<div class="importance-slider">
327+
<el-slider
328+
v-model="maibotForm.priority"
329+
:min="0"
330+
:max="100"
331+
:show-tooltip="false"
332+
/>
333+
<span class="importance-value">{{ maibotForm.priority }}</span>
334+
</div>
335+
</el-form-item>
336+
337+
<el-form-item label="回复文本 (可选)">
338+
<el-input
339+
v-model="maibotForm.text"
340+
type="textarea"
341+
:rows="2"
342+
placeholder="可选的回复文本..."
343+
resize="none"
344+
/>
345+
</el-form-item>
346+
347+
<el-form-item>
348+
<el-button
349+
type="primary"
350+
:loading="maibotLoading"
351+
:disabled="!maibotForm.action && !maibotForm.emotion"
352+
@click="triggerMaibotAction"
353+
>
354+
<el-icon><Pointer /></el-icon>
355+
触发动作/情绪
356+
</el-button>
357+
</el-form-item>
358+
</el-form>
359+
</section>
360+
361+
<!-- 控制历史 -->
362+
<section class="history-panel">
363+
<div class="panel-header">
364+
<div class="panel-title">
365+
<el-icon class="title-icon"><Clock /></el-icon>
366+
<span>控制历史</span>
367+
<el-badge :value="maibotHistory.length" :max="99" class="history-badge" />
368+
</div>
369+
<el-button
370+
size="small"
371+
:icon="Delete"
372+
:disabled="maibotHistory.length === 0"
373+
@click="maibotHistory = []"
374+
>
375+
清空
376+
</el-button>
377+
</div>
378+
379+
<el-table
380+
v-if="maibotHistory.length > 0"
381+
:data="maibotHistory"
382+
size="small"
383+
empty-text="暂无控制记录"
384+
class="history-table"
385+
>
386+
<el-table-column prop="time" label="时间" width="100">
387+
<template #default="{ row }">
388+
<span class="time-text">{{ row.time }}</span>
389+
</template>
390+
</el-table-column>
391+
<el-table-column label="动作/情绪">
392+
<template #default="{ row }">
393+
<div class="message-preview">
394+
<el-tag v-if="row.action" size="small" type="warning">{{
395+
row.action
396+
}}</el-tag>
397+
<el-tag v-if="row.emotion" size="small" type="success">{{
398+
row.emotion
399+
}}</el-tag>
400+
</div>
401+
</template>
402+
</el-table-column>
403+
<el-table-column label="状态" width="70" align="center">
404+
<template #default="{ row }">
405+
<el-tag
406+
:type="row.success ? 'success' : 'danger'"
407+
size="small"
408+
effect="plain"
409+
>
410+
{{ row.success ? '成功' : '失败' }}
411+
</el-tag>
412+
</template>
413+
</el-table-column>
414+
</el-table>
415+
416+
<el-empty v-else description="暂无控制记录" :image-size="80" />
417+
</section>
418+
</div>
419+
</div>
420+
</el-tab-pane>
210421
</el-tabs>
211422
</div>
212423
</div>
@@ -223,8 +434,9 @@ import {
223434
Refresh,
224435
Delete,
225436
RefreshRight,
437+
Pointer,
226438
} from '@element-plus/icons-vue';
227-
import { debugApi } from '@/api';
439+
import { debugApi, maibotApi } from '@/api';
228440
import type { EventBusStatsResponse, InjectMessageRequest } from '@/types';
229441
230442
// Tab state
@@ -301,6 +513,96 @@ async function retryInject(item: InjectHistoryItem) {
301513
await injectMessage();
302514
}
303515
516+
// ============ MaiBot 控制 Tab ============
517+
interface MaibotHistoryItem {
518+
time: string;
519+
action?: string;
520+
emotion?: string;
521+
priority: number;
522+
text?: string;
523+
success: boolean;
524+
error?: string;
525+
}
526+
527+
const maibotForm = ref({
528+
action: '',
529+
hotkey: '',
530+
expression: '',
531+
motion: '',
532+
emotion: '',
533+
priority: 50,
534+
text: '',
535+
});
536+
537+
const maibotLoading = ref(false);
538+
const maibotHistory = ref<MaibotHistoryItem[]>([]);
539+
540+
async function triggerMaibotAction() {
541+
if (!maibotForm.value.action && !maibotForm.value.emotion) {
542+
ElMessage.warning('请选择动作或情绪');
543+
return;
544+
}
545+
546+
maibotLoading.value = true;
547+
const time = new Date().toLocaleString('zh-CN', {
548+
hour: '2-digit',
549+
minute: '2-digit',
550+
second: '2-digit',
551+
});
552+
553+
// 根据动作类型构建参数
554+
let actionParams = {};
555+
if (maibotForm.value.action === 'hotkey' && maibotForm.value.hotkey) {
556+
actionParams = { hotkey: maibotForm.value.hotkey };
557+
} else if (maibotForm.value.action === 'expression' && maibotForm.value.expression) {
558+
actionParams = { expression: maibotForm.value.expression };
559+
} else if (maibotForm.value.action === 'motion' && maibotForm.value.motion) {
560+
actionParams = { motion: maibotForm.value.motion };
561+
}
562+
563+
const requestData = {
564+
action: maibotForm.value.action || undefined,
565+
action_params: Object.keys(actionParams).length > 0 ? actionParams : undefined,
566+
emotion: maibotForm.value.emotion || undefined,
567+
priority: maibotForm.value.priority,
568+
text: maibotForm.value.text || undefined,
569+
};
570+
571+
try {
572+
const response = await maibotApi.triggerAction(requestData);
573+
574+
maibotHistory.value.unshift({
575+
time,
576+
action: maibotForm.value.action || undefined,
577+
emotion: maibotForm.value.emotion || undefined,
578+
priority: maibotForm.value.priority,
579+
text: maibotForm.value.text || undefined,
580+
success: response.data.success,
581+
error: response.data.error,
582+
});
583+
584+
if (response.data.success) {
585+
ElMessage.success('动作/情绪触发成功');
586+
} else {
587+
ElMessage.error(response.data.error || '触发失败');
588+
}
589+
} catch (error) {
590+
console.error('Maibot API error:', error);
591+
maibotHistory.value.unshift({
592+
time,
593+
action: maibotForm.value.action || undefined,
594+
emotion: maibotForm.value.emotion || undefined,
595+
priority: maibotForm.value.priority,
596+
text: maibotForm.value.text || undefined,
597+
success: false,
598+
error: String(error),
599+
});
600+
ElMessage.error('请求失败');
601+
} finally {
602+
maibotLoading.value = false;
603+
}
604+
}
605+
304606
// ============ EventBus 统计 Tab ============
305607
const statsLoading = ref(false);
306608
const eventBusStats = ref<EventBusStatsResponse | null>(null);

0 commit comments

Comments
 (0)