feat(dashboard): AgentModal live working feed & thinking stream
CI - Build & Test / Backend (.NET) (push) Failing after 24s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 15s
CI - Build & Test / Security Check (push) Successful in 2s

This commit is contained in:
2026-06-11 16:13:28 +02:00
parent c29740a466
commit b1888bd8ef
4 changed files with 180 additions and 25 deletions
+120 -25
View File
@@ -24,11 +24,19 @@ interface ModelOption {
provider: string
}
interface ActivityEntry {
time: string
text: string
}
const availableModels = ref<ModelOption[]>([])
const selectedModel = ref('')
const currentModel = ref('')
const saving = ref(false)
const activityEntries = ref<ActivityEntry[]>([])
const activityLoaded = ref(false)
async function loadModels() {
try {
const res = await fetch('/api/dashboard/models', { credentials: 'include' })
@@ -75,9 +83,23 @@ async function saveModel() {
}
}
async function loadActivity() {
try {
const res = await fetch(`/api/dashboard/agents/${props.agent.id}/activity?limit=5`, { credentials: 'include' })
if (res.ok) {
activityEntries.value = await res.json()
}
} catch {
// silent — fallback to empty
} finally {
activityLoaded.value = true
}
}
onMounted(async () => {
await loadModels()
await loadCurrentModel()
await loadActivity()
})
</script>
@@ -139,16 +161,35 @@ onMounted(async () => {
<!-- Current Task -->
<section class="modal-section">
<h3 class="section-label">Current Task</h3>
<h3 class="section-label">
Current Task
<span v-if="agent.active" class="thinking-indicator">
<span class="thinking-dots-inline">
<span class="tdot"></span>
<span class="tdot"></span>
<span class="tdot"></span>
</span>
Thinking
</span>
</h3>
<p class="section-value">
{{ agent.currentTask }}
<span v-if="agent.active" class="thinking-dots">
<span class="thinking-dot blue"></span>
<span class="thinking-dot violet"></span>
</span>
</p>
</section>
<!-- Recent Activity -->
<section class="modal-section">
<h3 class="section-label">Recent Activity</h3>
<div v-if="!activityLoaded" class="activity-placeholder">Loading</div>
<div v-else-if="activityEntries.length === 0" class="activity-placeholder">No recent activity</div>
<div v-else class="activity-list">
<div v-for="(entry, idx) in activityEntries" :key="idx" class="activity-item">
<span class="activity-time">{{ entry.time }}</span>
<span class="activity-text">{{ entry.text.length > 100 ? entry.text.slice(0, 100) + '…' : entry.text }}</span>
</div>
</div>
</section>
<!-- Goal + Progress -->
<section class="modal-section">
<h3 class="section-label">Goal</h3>
@@ -384,34 +425,88 @@ onMounted(async () => {
transition: width 0.5s ease;
}
/* Thinking Dots */
.thinking-dots {
/* Thinking Indicator */
.thinking-indicator {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
font-size: 9px;
font-weight: 500;
text-transform: none;
letter-spacing: normal;
color: #a78bfa;
margin-left: 8px;
vertical-align: middle;
}
.thinking-dot {
width: 7px;
height: 7px;
.thinking-dots-inline {
display: inline-flex;
gap: 4px;
}
.thinking-dots-inline .tdot {
width: 5px;
height: 5px;
border-radius: 50%;
background: #a78bfa;
animation: thinking-bounce 1.2s ease-in-out infinite;
}
.thinking-dot.blue {
background: #3b82f6;
box-shadow: 0 0 8px #3b82f6;
animation: pulse-dot-blue 1.2s ease-in-out infinite;
.thinking-dots-inline .tdot:nth-child(2) {
animation-delay: 0.2s;
}
.thinking-dot.violet {
background: #8b7cf6;
box-shadow: 0 0 8px #8b7cf6;
animation: pulse-dot-violet 1.8s ease-in-out infinite 0.3s;
.thinking-dots-inline .tdot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes pulse-dot-blue {
0%, 100% { opacity: 0.4; transform: scale(0.7); }
50% { opacity: 1; transform: scale(1.3); }
@keyframes thinking-bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
@keyframes pulse-dot-violet {
0%, 100% { opacity: 0.3; transform: scale(0.6); }
50% { opacity: 1; transform: scale(1.4); }
/* Recent Activity */
.activity-placeholder {
font-size: 10px;
color: #6b7385;
font-style: italic;
padding: 4px 0;
}
.activity-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 160px;
overflow-y: auto;
}
.activity-list::-webkit-scrollbar {
width: 4px;
}
.activity-list::-webkit-scrollbar-track {
background: transparent;
}
.activity-list::-webkit-scrollbar-thumb {
background: rgba(139, 124, 246, 0.15);
border-radius: 3px;
}
.activity-item {
display: flex;
flex-direction: column;
gap: 2px;
padding: 6px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.04);
}
.activity-time {
font-size: 9px;
font-weight: 600;
color: #6b7385;
font-variant-numeric: tabular-nums;
}
.activity-text {
font-size: 11px;
line-height: 1.45;
color: #c4c8d4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Footer */