399 lines
17 KiB
Vue
399 lines
17 KiB
Vue
<template>
|
|
<div class="virtual-monitor-surface">
|
|
<div class="monitor-stage">
|
|
<div class="monitor">
|
|
<div class="monitor-shell">
|
|
<div class="monitor-top">
|
|
<div class="monitor-brand">Agent Display Surface</div>
|
|
<div class="status-pill-group">
|
|
<div class="status-pill">{{ statusLabel }}</div>
|
|
<div class="progress-pill" v-if="currentProgressLabel">
|
|
<span class="pulse-dot"></span>
|
|
<span class="progress-text">{{ currentProgressLabel }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="monitor-screen" ref="screenEl">
|
|
<div class="desktop-layer">
|
|
<div class="desktop-section apps">
|
|
<div class="apps-grid" ref="appsGrid"></div>
|
|
</div>
|
|
<div class="desktop-section files">
|
|
<div class="desktop-grid" ref="desktopGrid"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window browser-window" ref="browserWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span>多模态知识浏览器</span>
|
|
</div>
|
|
<div class="browser-body">
|
|
<div class="search-bar"><span ref="browserSearchText"></span></div>
|
|
<div class="browser-status" ref="browserStatus">准备搜索...</div>
|
|
<div class="results-list results-scroll">
|
|
<ul ref="browserResults"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window extraction-window" ref="extractionWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span class="window-title">网页提取</span>
|
|
</div>
|
|
<div class="extraction-body">
|
|
<div class="extract-url" ref="extractionUrl"></div>
|
|
<div class="extract-status" ref="extractionState">等待提取</div>
|
|
<div class="extract-summary" ref="extractionSummary"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window folder-window" ref="folderWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="folderHeaderText">workspace</span>
|
|
</div>
|
|
<div class="folder-body" ref="folderBody"></div>
|
|
</div>
|
|
|
|
<div class="window editor-window" ref="editorWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="editorHeaderText">文件</span>
|
|
</div>
|
|
<div class="editor-body" ref="editorBody"></div>
|
|
</div>
|
|
|
|
<div class="window terminal-window" ref="terminalWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="terminalHeaderText">终端</span>
|
|
</div>
|
|
<div class="terminal-body">
|
|
<div class="terminal-tabs" ref="terminalTabs">
|
|
<div class="terminal-tab-list" ref="terminalTabList"></div>
|
|
<button class="terminal-tab add-tab" ref="terminalAddButton">+</button>
|
|
</div>
|
|
<div class="terminal-output" ref="terminalBody"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window command-window" ref="commandWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="commandTitle">命令行</span>
|
|
</div>
|
|
<div class="command-body">
|
|
<div class="command-input" ref="commandInput"></div>
|
|
<div class="command-output" ref="commandOutput"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window python-window" ref="pythonWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="pythonTitle">Python</span>
|
|
</div>
|
|
<div class="python-body" ref="pythonBody">
|
|
<div class="python-input" ref="pythonInput"></div>
|
|
<div class="python-output" ref="pythonOutput"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window reader-window" ref="readerWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span ref="readerTitle">阅读器</span>
|
|
</div>
|
|
<div class="reader-body">
|
|
<div class="reading-lines" ref="readerLines"></div>
|
|
<div class="ocr-panel" ref="readerOcr"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window memory-window" ref="memoryWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span class="window-title">记忆</span>
|
|
</div>
|
|
<div class="memory-body">
|
|
<div class="memory-list" ref="memoryList"></div>
|
|
</div>
|
|
<div class="memory-footer hidden" aria-hidden="true">
|
|
<span><strong ref="memoryCount"></strong></span>
|
|
<span><strong ref="memoryTime"></strong></span>
|
|
<span ref="memoryStatus"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window todo-window" ref="todoWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span class="window-title">待办事项</span>
|
|
</div>
|
|
<div class="todo-body">
|
|
<div class="todo-summary" ref="todoSummary"></div>
|
|
<div class="todo-progress" ref="todoList"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window wait-window" ref="waitWindow">
|
|
<div class="window-header">
|
|
<span class="traffic-dot red"></span>
|
|
<span class="traffic-dot yellow"></span>
|
|
<span class="traffic-dot green"></span>
|
|
<span class="window-title">等待</span>
|
|
</div>
|
|
<div class="wait-body">
|
|
<div class="flip-clock" ref="waitDisplay">
|
|
<div class="flip-digit" data-value="0"><span class="top">0</span><span class="bottom">0</span></div>
|
|
<div class="flip-digit" data-value="0"><span class="top">0</span><span class="bottom">0</span></div>
|
|
<div class="flip-separator">:</div>
|
|
<div class="flip-digit" data-value="0"><span class="top">0</span><span class="bottom">0</span></div>
|
|
<div class="flip-digit" data-value="0"><span class="top">0</span><span class="bottom">0</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="context-menu" ref="desktopMenu">
|
|
<button data-action="file">新建文件</button>
|
|
<button data-action="folder">新建文件夹</button>
|
|
<button data-action="wait">等待</button>
|
|
</div>
|
|
<div class="context-menu" ref="folderMenu">
|
|
<button data-action="file">新建文件</button>
|
|
<button data-action="folder">新建文件夹</button>
|
|
</div>
|
|
<div class="context-menu" ref="fileMenu">
|
|
<button data-action="read">阅读文件</button>
|
|
<button data-action="edit">编辑文件</button>
|
|
<button data-action="delete">删除文件</button>
|
|
</div>
|
|
<div class="context-menu" ref="focusMenu">
|
|
<button data-action="focus">聚焦文件</button>
|
|
<button data-action="unfocus">取消聚焦</button>
|
|
</div>
|
|
<div class="context-menu" ref="browserMenu">
|
|
<button data-action="save">保存网页</button>
|
|
</div>
|
|
<div class="context-menu" ref="terminalMenu">
|
|
<button data-action="snapshot">保存快照</button>
|
|
<button data-action="reset">重置终端</button>
|
|
<button data-action="close">关闭终端</button>
|
|
</div>
|
|
|
|
<div class="speech-bubble" ref="bubbleEl">
|
|
<div class="bubble-icon-slot" ref="bubbleIconSlot"></div>
|
|
<div class="bubble-text-slot" ref="bubbleTextSlot"></div>
|
|
</div>
|
|
|
|
<div class="mouse-pointer" ref="mousePointer">
|
|
<img :src="mouseIcon" alt="pointer" />
|
|
</div>
|
|
|
|
<div class="wait-overlay" ref="waitOverlay">
|
|
<div class="z z-1">Z</div>
|
|
<div class="z z-2">Z</div>
|
|
<div class="z z-3">Z</div>
|
|
<div class="z z-4">Z</div>
|
|
<div class="wait-countdown" ref="waitCountdown">5s</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="monitor-stand"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useMonitorStore } from '@/stores/monitor';
|
|
import { MonitorDirector, type MonitorElements } from '@/components/chat/monitor/MonitorDirector';
|
|
|
|
const monitorStore = useMonitorStore();
|
|
const { statusLabel, progressIndicator } = storeToRefs(monitorStore);
|
|
const currentProgressLabel = computed(() => progressIndicator.value?.label || '');
|
|
|
|
const screenEl = ref<HTMLElement | null>(null);
|
|
const appsGrid = ref<HTMLElement | null>(null);
|
|
const desktopGrid = ref<HTMLElement | null>(null);
|
|
const browserWindow = ref<HTMLElement | null>(null);
|
|
const browserSearchText = ref<HTMLElement | null>(null);
|
|
const browserStatus = ref<HTMLElement | null>(null);
|
|
const browserResults = ref<HTMLElement | null>(null);
|
|
const extractionWindow = ref<HTMLElement | null>(null);
|
|
const extractionUrl = ref<HTMLElement | null>(null);
|
|
const extractionState = ref<HTMLElement | null>(null);
|
|
const extractionSummary = ref<HTMLElement | null>(null);
|
|
const folderWindow = ref<HTMLElement | null>(null);
|
|
const folderHeaderText = ref<HTMLElement | null>(null);
|
|
const folderBody = ref<HTMLElement | null>(null);
|
|
const editorWindow = ref<HTMLElement | null>(null);
|
|
const editorHeaderText = ref<HTMLElement | null>(null);
|
|
const editorBody = ref<HTMLElement | null>(null);
|
|
const terminalWindow = ref<HTMLElement | null>(null);
|
|
const terminalHeaderText = ref<HTMLElement | null>(null);
|
|
const terminalTabs = ref<HTMLElement | null>(null);
|
|
const terminalTabList = ref<HTMLElement | null>(null);
|
|
const terminalAddButton = ref<HTMLElement | null>(null);
|
|
const terminalBody = ref<HTMLElement | null>(null);
|
|
const commandWindow = ref<HTMLElement | null>(null);
|
|
const commandTitle = ref<HTMLElement | null>(null);
|
|
const commandInput = ref<HTMLElement | null>(null);
|
|
const commandOutput = ref<HTMLElement | null>(null);
|
|
const pythonWindow = ref<HTMLElement | null>(null);
|
|
const pythonTitle = ref<HTMLElement | null>(null);
|
|
const pythonBody = ref<HTMLElement | null>(null);
|
|
const pythonInput = ref<HTMLElement | null>(null);
|
|
const pythonOutput = ref<HTMLElement | null>(null);
|
|
const readerWindow = ref<HTMLElement | null>(null);
|
|
const readerTitle = ref<HTMLElement | null>(null);
|
|
const readerLines = ref<HTMLElement | null>(null);
|
|
const readerOcr = ref<HTMLElement | null>(null);
|
|
const memoryWindow = ref<HTMLElement | null>(null);
|
|
const memoryList = ref<HTMLElement | null>(null);
|
|
const memoryStatus = ref<HTMLElement | null>(null);
|
|
const memoryCount = ref<HTMLElement | null>(null);
|
|
const memoryTime = ref<HTMLElement | null>(null);
|
|
const todoWindow = ref<HTMLElement | null>(null);
|
|
const todoSummary = ref<HTMLElement | null>(null);
|
|
const todoList = ref<HTMLElement | null>(null);
|
|
const waitWindow = ref<HTMLElement | null>(null);
|
|
const waitDisplay = ref<HTMLElement | null>(null);
|
|
const waitOverlay = ref<HTMLElement | null>(null);
|
|
const waitCountdown = ref<HTMLElement | null>(null);
|
|
const desktopMenu = ref<HTMLElement | null>(null);
|
|
const folderMenu = ref<HTMLElement | null>(null);
|
|
const fileMenu = ref<HTMLElement | null>(null);
|
|
const browserMenu = ref<HTMLElement | null>(null);
|
|
const terminalMenu = ref<HTMLElement | null>(null);
|
|
const focusMenu = ref<HTMLElement | null>(null);
|
|
const bubbleEl = ref<HTMLElement | null>(null);
|
|
const bubbleIconSlot = ref<HTMLElement | null>(null);
|
|
const bubbleTextSlot = ref<HTMLElement | null>(null);
|
|
const mousePointer = ref<HTMLElement | null>(null);
|
|
|
|
const mouseIcon = new URL('../../icons/mouse-pointer-2.svg', import.meta.url).href;
|
|
|
|
const assets = {
|
|
brainIcon: new URL('../../icons/brain.svg', import.meta.url).href,
|
|
folderIcon: new URL('../../icons/folder.svg', import.meta.url).href,
|
|
folderOpenIcon: new URL('../../icons/folder-open.svg', import.meta.url).href,
|
|
fileIcon: new URL('../../icons/file.svg', import.meta.url).href,
|
|
apps: {
|
|
browser: new URL('../../icons/globe.svg', import.meta.url).href,
|
|
terminal: new URL('../../icons/laptop.svg', import.meta.url).href,
|
|
command: new URL('../../icons/terminal.svg', import.meta.url).href,
|
|
python: new URL('../../icons/python.svg', import.meta.url).href,
|
|
memory: new URL('../../icons/sticky-note.svg', import.meta.url).href,
|
|
todo: new URL('../../icons/clipboard.svg', import.meta.url).href,
|
|
subagent: new URL('../../icons/bot.svg', import.meta.url).href
|
|
}
|
|
};
|
|
|
|
const director = ref<MonitorDirector | null>(null);
|
|
|
|
const mountDirector = async () => {
|
|
await nextTick();
|
|
if (!screenEl.value) {
|
|
return;
|
|
}
|
|
const elements: MonitorElements = {
|
|
screen: screenEl.value,
|
|
appsGrid: appsGrid.value!,
|
|
desktopGrid: desktopGrid.value!,
|
|
browserWindow: browserWindow.value!,
|
|
browserSearchText: browserSearchText.value!,
|
|
browserStatus: browserStatus.value!,
|
|
browserResults: browserResults.value!,
|
|
extractionWindow: extractionWindow.value!,
|
|
extractionUrl: extractionUrl.value!,
|
|
extractionState: extractionState.value!,
|
|
extractionSummary: extractionSummary.value!,
|
|
folderWindow: folderWindow.value!,
|
|
folderHeaderText: folderHeaderText.value!,
|
|
folderBody: folderBody.value!,
|
|
editorWindow: editorWindow.value!,
|
|
editorHeaderText: editorHeaderText.value!,
|
|
editorBody: editorBody.value!,
|
|
terminalWindow: terminalWindow.value!,
|
|
terminalHeaderText: terminalHeaderText.value!,
|
|
terminalTabs: terminalTabs.value!,
|
|
terminalTabList: terminalTabList.value!,
|
|
terminalAddButton: terminalAddButton.value!,
|
|
terminalBody: terminalBody.value!,
|
|
commandWindow: commandWindow.value!,
|
|
commandTitle: commandTitle.value!,
|
|
commandInput: commandInput.value!,
|
|
commandOutput: commandOutput.value!,
|
|
pythonWindow: pythonWindow.value!,
|
|
pythonTitle: pythonTitle.value!,
|
|
pythonBody: pythonBody.value!,
|
|
pythonInput: pythonInput.value!,
|
|
pythonOutput: pythonOutput.value!,
|
|
readerWindow: readerWindow.value!,
|
|
readerTitle: readerTitle.value!,
|
|
readerLines: readerLines.value!,
|
|
readerOcr: readerOcr.value!,
|
|
memoryWindow: memoryWindow.value!,
|
|
memoryList: memoryList.value!,
|
|
memoryStatus: memoryStatus.value!,
|
|
memoryCount: memoryCount.value!,
|
|
memoryTime: memoryTime.value!,
|
|
todoWindow: todoWindow.value!,
|
|
todoSummary: todoSummary.value!,
|
|
todoList: todoList.value!,
|
|
waitWindow: waitWindow.value!,
|
|
waitDisplay: waitDisplay.value!,
|
|
waitOverlay: waitOverlay.value!,
|
|
waitCountdown: waitCountdown.value!,
|
|
desktopMenu: desktopMenu.value!,
|
|
folderMenu: folderMenu.value!,
|
|
fileMenu: fileMenu.value!,
|
|
focusMenu: focusMenu.value!,
|
|
browserMenu: browserMenu.value!,
|
|
terminalMenu: terminalMenu.value!,
|
|
speechBubble: bubbleEl.value!,
|
|
bubbleIconSlot: bubbleIconSlot.value!,
|
|
bubbleTextSlot: bubbleTextSlot.value!,
|
|
mousePointer: mousePointer.value!
|
|
};
|
|
const instance = new MonitorDirector(elements, assets);
|
|
director.value = instance;
|
|
monitorStore.registerDriver(instance);
|
|
};
|
|
|
|
onMounted(() => {
|
|
mountDirector();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
monitorStore.unregisterDriver();
|
|
director.value?.destroy();
|
|
director.value = null;
|
|
});
|
|
</script>
|