Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

100 lines
3.7 KiB
Vue

<script setup lang="ts">
defineProps<{ node: {
type: 'thinking'
content: string
loading?: boolean
} }>()
</script>
<template>
<div class="thinking-node p-4 my-4 bg-blue-50 dark:bg-blue-900/40 rounded-md border-l-4 border-blue-400 flex items-start gap-3">
<div class="flex-shrink-0 mt-1">
<!-- decorative thinking SVG icon -->
<div class="w-9 h-9 rounded-full bg-blue-200 dark:bg-blue-700 flex items-center justify-center text-blue-700 dark:text-blue-100">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M12 3C7.03 3 3 6.58 3 11c0 1.86.66 3.57 1.77 4.98L4 21l5.2-1.9C10.06 19.35 11 19.5 12 19.5c4.97 0 9-3.58 9-8.5S16.97 3 12 3z" stroke="currentColor" stroke-width="0.8" fill="currentColor" opacity="0.9" />
</svg>
</div>
</div>
<div class="flex-1">
<div class="flex items-baseline gap-3">
<strong class="text-sm">Thinking</strong>
<span class="text-xs text-slate-500 dark:text-slate-300">(assistant)</span>
<!-- keep dots in DOM to avoid layout shift; toggle visibility with classes -->
<span class="ml-2" aria-hidden="true">
<span class="thinking-dots" :class="[node.loading ? 'visible' : 'hidden']" aria-hidden="true">
<span class="dot dot-1" />
<span class="dot dot-2" />
<span class="dot dot-3" />
</span>
</span>
</div>
<div class="mt-1 text-sm leading-relaxed text-slate-800 dark:text-slate-100">
<!-- sr-only live region only present when loading to announce change -->
<span v-if="node.loading" class="sr-only" aria-live="polite">Thinking…</span>
<transition name="fade" mode="out-in">
<div key="{{ node.loading ? 'loading' : 'ready' }}" class="content-area">
<div v-if="node.loading" class="partial-content">
{{ node.content }}
</div>
<div v-else class="full-content">
{{ node.content }}
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
<style scoped>
.thinking-node {
color: #0f172a;
}
.dark .thinking-node {
color: #e6f0ff;
}
/* Animated dots for thinking state */
.thinking-dots {
display: inline-flex;
align-items: center;
gap: 6px;
width: 36px;
justify-content: flex-start;
height: 12px; /* reserve vertical space so toggling doesn't collapse layout */
transition: opacity 160ms linear, transform 160ms linear;
opacity: 0;
}
.thinking-dots .dot {
width: 6px;
height: 6px;
border-radius: 9999px;
background: #1e3a8a; /* blue-800 */
opacity: 0.25;
transform: translateY(0);
}
.thinking-dots.visible { opacity: 1; }
.thinking-dots.hidden { opacity: 0; transform: translateY(0); }
.thinking-dots.visible .dot-1 { animation: think-bounce 1s infinite ease-in-out; animation-delay: 0s }
.thinking-dots.visible .dot-2 { animation: think-bounce 1s infinite ease-in-out; animation-delay: 0.12s }
.thinking-dots.visible .dot-3 { animation: think-bounce 1s infinite ease-in-out; animation-delay: 0.24s }
.dark .thinking-dots .dot { background: #bfdbfe; opacity: 0.28 }
@keyframes think-bounce {
0%, 80%, 100% { transform: translateY(0); opacity: 0.25 }
40% { transform: translateY(-6px); opacity: 1 }
}
/* ensure content area doesn't shift when dots appear */
.content-area { min-height: 1.25rem; }
.partial-content, .full-content { transition: opacity 140ms ease; }
.partial-content { opacity: 0.9 }
.full-content { opacity: 1 }
/* Vue transition classes for fade */
.fade-enter-from, .fade-leave-to { opacity: 0 }
.fade-enter-active, .fade-leave-active { transition: opacity 160ms ease }
.fade-enter-to, .fade-leave-from { opacity: 1 }
</style>