feat(mcp): 用 MCP 服务替换内置 AI Agent (#319)

在现有 HTTP 服务挂载 /mcp,让外部 AI 经共享 API Key 调用原分辨率截图、批量键鼠、ATX/WOL 和虚拟介质八个工具。所有调用共用有界 FIFO 和全局限流,复用现有采集管线与硬件控制器。

移除内置模型调用、网页 Agent 面板及旧配置,设置页提供可持续查看的 Key、统一的地址/密钥复制样式。补齐 DEB 的 libudev1 运行依赖。

验证:583 项 Rust 库测试、前端生产构建、ARMv7 设备视觉操作与介质/WOL 实测,以及默认 Release 构建和 DEB 包校验通过。

Refs #319
This commit is contained in:
mofeng-git
2026-10-19 12:12:03 +08:00
parent 3f970ee834
commit 7ea2936436
50 changed files with 2910 additions and 3713 deletions

View File

@@ -38,7 +38,6 @@ import {
BarChart3,
Terminal,
MoreHorizontal,
Bot,
ChevronUp,
ChevronDown,
Keyboard,
@@ -73,7 +72,6 @@ const props = defineProps<{
showPower?: boolean
atxEnabled?: boolean
showTerminal?: boolean
showComputerUse?: boolean
showPasteText?: boolean
showMic?: boolean
scaleMode?: VideoScaleMode
@@ -111,7 +109,6 @@ const emit = defineEmits<{
(e: 'reset'): void
(e: 'wol', macAddress: string): void
(e: 'openTerminal'): void
(e: 'openComputerUse'): void
}>()
const pasteOpen = ref(false)
@@ -166,7 +163,7 @@ let layoutResizeObserver: ResizeObserver | null = null
type CollapsibleItem =
| 'msd' | 'atx' | 'paste'
| 'stats' | 'terminal' | 'settings' | 'ai'
| 'stats' | 'terminal' | 'settings'
interface ItemSpec {
id: CollapsibleItem
@@ -180,7 +177,6 @@ const ITEM_SPECS: ItemSpec[] = [
{ id: 'stats', side: 'right' },
{ id: 'terminal', side: 'right' },
{ id: 'settings', side: 'right' },
{ id: 'ai', side: 'right' },
]
const measuredWidths = ref<Map<CollapsibleItem, { icon: number; label: number }>>(new Map())
@@ -256,7 +252,7 @@ watch(locale, () => {
void measureLayout()
})
watch([() => props.layout, () => props.showComputerUse, floatingCollapsed, minimalDisplayControls], () => {
watch([() => props.layout, floatingCollapsed, minimalDisplayControls], () => {
// Layout changes can move this bar while its portal based controls are open.
// Close every transient surface first so no old portal remains attached to
// the document after the new layout is painted.
@@ -296,7 +292,6 @@ const collapsibleItems = computed(() => {
if (item.id === 'paste' && !showPasteText.value) return false
if (item.id === 'stats' && !showStats.value) return false
if (item.id === 'terminal' && props.showTerminal === false) return false
if (item.id === 'ai' && props.showComputerUse === false) return false
return true
})
return items
@@ -309,7 +304,7 @@ const visibleSet = computed(() => {
if (isSidebarLayout.value) {
// Reserve More and a gap between the two groups before assigning vertical slots.
let available = barHeight.value - coreHeight.value - fixedHeight.value - actionHeight.value - 16
const priority: CollapsibleItem[] = ['paste', 'settings', 'msd', 'atx', 'stats', 'terminal', 'ai']
const priority: CollapsibleItem[] = ['paste', 'settings', 'msd', 'atx', 'stats', 'terminal']
for (const id of priority) {
if (!collapsibleItems.value.some(item => item.id === id)) continue
if (available < actionHeight.value) break
@@ -567,26 +562,7 @@ const hasRightOverflow = computed(() => {
</TooltipProvider>
</div>
<!-- Computer Use - Optional -->
<TooltipProvider v-if="isVisible('ai')">
<Tooltip>
<TooltipTrigger as-child>
<Button
variant="ghost"
size="sm"
class="h-8 gap-1.5 text-xs"
:aria-label="t('computerUse.title')"
@click="emit('openComputerUse')"
>
<Bot class="size-3.5 sm:size-4" />
<span v-if="visibleSet.get('ai') === 'label'">AI</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{{ t('computerUse.title') }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<!-- Settings - Adaptive -->
<div v-if="isVisible('settings')">
@@ -661,10 +637,6 @@ const hasRightOverflow = computed(() => {
{{ t('actionbar.webTerminal') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="!isFloatingLayout && props.showComputerUse !== false && !isVisible('ai')" @click="openFromOverflow(() => emit('openComputerUse'))">
<Bot class="size-4 mr-2" />{{ t('computerUse.title') }}
</DropdownMenuItem>
<!-- Settings -->
<DropdownMenuItem v-if="!isFloatingLayout && !isVisible('settings')" @click="openFromOverflow(() => router.push('/settings'))">
<Settings class="size-4 mr-2" />
@@ -752,8 +724,6 @@ const hasRightOverflow = computed(() => {
<!-- Settings -->
<Button data-measure="settings-icon" variant="ghost" size="sm" class="h-8 gap-1.5 text-xs"><Settings class="size-4" /></Button>
<Button data-measure="settings-label" variant="ghost" size="sm" class="h-8 gap-1.5 text-xs"><Settings class="size-4" />{{ t('actionbar.settings') }}</Button>
<Button data-measure="ai-icon" variant="ghost" size="sm" class="h-8 gap-1.5 text-xs"><Bot class="size-4" /></Button>
<Button data-measure="ai-label" variant="ghost" size="sm" class="h-8 gap-1.5 text-xs"><Bot class="size-4" />AI</Button>
</div>
</div>
</template>