tori/web/src/components/PlanSection.vue
Fam Zheng 7edbbee471 Tori: AI agent workflow manager - initial implementation
Rust (Axum) + Vue 3 + SQLite. Features:
- Project CRUD REST API with proper error handling
- Per-project agent loop (mpsc + broadcast channels)
- LLM-driven plan generation and replan on user feedback
- SSH command execution with status streaming
- WebSocket real-time updates to frontend
- Four-zone UI: requirement, plan (left), execution (right), comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:36:50 +00:00

113 lines
2.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import type { PlanStep } from '../types'
defineProps<{
steps: PlanStep[]
}>()
function statusIcon(status: string) {
switch (status) {
case 'done': return '✓'
case 'running': return '⟳'
case 'failed': return '✗'
default: return '○'
}
}
</script>
<template>
<div class="plan-section">
<div class="section-header">
<h2>Plan</h2>
</div>
<div class="steps-list">
<div
v-for="step in steps"
:key="step.id"
class="step-item"
:class="step.status"
>
<span class="step-icon">{{ statusIcon(step.status) }}</span>
<span class="step-order">{{ step.step_order }}.</span>
<span class="step-desc">{{ step.description }}</span>
</div>
<div v-if="!steps.length" class="empty-state">
提交需求后AI 将在这里生成计划
</div>
</div>
</div>
</template>
<style scoped>
.plan-section {
flex: 1;
background: var(--bg-card);
border-radius: 8px;
padding: 12px 16px;
border: 1px solid var(--border);
overflow-y: auto;
min-width: 0;
}
.section-header {
margin-bottom: 12px;
}
.section-header h2 {
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.steps-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.step-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 10px;
border-radius: 6px;
font-size: 13px;
line-height: 1.5;
background: var(--bg-secondary);
}
.step-item.done { border-left: 3px solid var(--success); }
.step-item.running { border-left: 3px solid var(--accent); background: rgba(79, 195, 247, 0.08); }
.step-item.failed { border-left: 3px solid var(--error); }
.step-item.pending { border-left: 3px solid var(--pending); opacity: 0.7; }
.step-icon {
font-size: 14px;
flex-shrink: 0;
width: 18px;
text-align: center;
}
.step-item.done .step-icon { color: var(--success); }
.step-item.running .step-icon { color: var(--accent); }
.step-item.failed .step-icon { color: var(--error); }
.step-order {
color: var(--text-secondary);
flex-shrink: 0;
}
.step-desc {
color: var(--text-primary);
}
.empty-state {
color: var(--text-secondary);
font-size: 13px;
text-align: center;
padding: 24px;
}
</style>