feat: Add FloatingResponse component with bubbleResponse MCP tool
Add a floating response panel that allows the agent to display messages directly in the UI instead of through the terminal. Includes support for info, success, warning, and error message types with auto-dismiss.
This commit is contained in:
@@ -4,6 +4,8 @@ import { Terminal } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { connectWithToken, stopTokenPolling } from '../services/webmcp'
|
||||
import { useCanvasStore } from '../stores/canvas'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -18,6 +20,8 @@ const isOpen = computed({
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const terminalContainer = ref<HTMLElement | null>(null)
|
||||
const terminalRef = ref<HTMLElement | null>(null)
|
||||
const connected = ref(false)
|
||||
@@ -38,6 +42,11 @@ let fitAddon: FitAddon | null = null
|
||||
let socket: WebSocket | null = null
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
// Buffer for detecting WebMCP token
|
||||
let tokenBuffer = ''
|
||||
let tokenTimeout: number | null = null
|
||||
const waitingForToken = ref(false)
|
||||
|
||||
const WS_URL = `ws://${window.location.hostname}:4103`
|
||||
|
||||
// Mouse position tracking for Ctrl+E
|
||||
@@ -282,6 +291,38 @@ async function connect() {
|
||||
} else if (msg.type === 'replay') {
|
||||
terminal?.write(msg.data)
|
||||
} else if (msg.type === 'output') {
|
||||
// Only detect token when waiting for it
|
||||
if (waitingForToken.value) {
|
||||
tokenBuffer += msg.data
|
||||
|
||||
// Debounce: process buffer after output stops (300ms)
|
||||
if (tokenTimeout) clearTimeout(tokenTimeout)
|
||||
tokenTimeout = window.setTimeout(() => {
|
||||
if (tokenBuffer.includes('Token copiado')) {
|
||||
// Clean ANSI codes and whitespace
|
||||
const clean = tokenBuffer.replace(/\x1b\[[0-9;]*m/g, '').replace(/[\r\n\s]/g, '')
|
||||
const match = clean.match(/eyJ[A-Za-z0-9_\-+/=]+/)
|
||||
if (match) {
|
||||
try {
|
||||
const decoded = atob(match[0])
|
||||
JSON.parse(decoded)
|
||||
console.log('[Terminal] WebMCP token detected:', match[0])
|
||||
waitingForToken.value = false
|
||||
tokenBuffer = ''
|
||||
stopTokenPolling()
|
||||
connectWithToken(match[0]).then(success => {
|
||||
if (success) {
|
||||
canvasStore.showNotification('WebMCP connected!', 'success')
|
||||
}
|
||||
}).catch(console.error)
|
||||
} catch {
|
||||
// Token incomplete, keep waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
terminal?.write(msg.data)
|
||||
} else if (msg.type === 'exit') {
|
||||
terminal?.write(msg.data)
|
||||
@@ -314,6 +355,14 @@ function runClaude() {
|
||||
}
|
||||
}
|
||||
|
||||
function requestToken() {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
tokenBuffer = ''
|
||||
waitingForToken.value = true
|
||||
socket.send(JSON.stringify({ type: 'input', data: 'genera token usando tu mcp\r' }))
|
||||
}
|
||||
}
|
||||
|
||||
watch(isOpen, async (open) => {
|
||||
if (open) {
|
||||
await nextTick()
|
||||
@@ -330,6 +379,9 @@ watch(isOpen, async (open) => {
|
||||
terminal?.dispose()
|
||||
terminal = null
|
||||
fitAddon = null
|
||||
waitingForToken.value = false
|
||||
tokenBuffer = ''
|
||||
if (tokenTimeout) clearTimeout(tokenTimeout)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -410,6 +462,7 @@ defineExpose({
|
||||
<a v-if="!connected && !connecting" class="link" @click.stop="connect">connect</a>
|
||||
</div>
|
||||
<div class="window-controls">
|
||||
<button @click="requestToken" :class="{ waiting: waitingForToken }" title="Connect MCP"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></button>
|
||||
<button @click="runClaude" title="Claude"><svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg></button>
|
||||
<button class="x" @click="close" title="Close"><svg width="8" height="8" viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.5"/><line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.5"/></svg></button>
|
||||
</div>
|
||||
@@ -514,6 +567,11 @@ defineExpose({
|
||||
border-color: #a22;
|
||||
color: #fff;
|
||||
}
|
||||
.window-controls button.waiting {
|
||||
background: rgba(16, 185, 129, 0.3);
|
||||
border-color: #10b981;
|
||||
animation: pulse 0.8s infinite;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user