Agent loop state machine refactor, unified LLM interface, and UI improvements
- Rewrite agent loop as Planning→Executing(N)→Completed state machine with per-step context isolation to prevent token explosion - Split tools and prompts by phase (planning vs execution) - Add advance_step/save_memo tools for step transitions and cross-step memory - Unify LLM interface: remove duplicate types, single chat_with_tools path - Add UTF-8 safe truncation (truncate_str) to prevent panics on Chinese text - Extract CreateForm component, add auto-scroll to execution log - Add report generation with app access URL, non-blocking title generation - Add timer system, file serving, app proxy, exec module - Update Dockerfile with uv, deployment config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,62 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
import WorkflowView from './WorkflowView.vue'
|
||||
import ReportView from './ReportView.vue'
|
||||
import CreateForm from './CreateForm.vue'
|
||||
import { api } from '../api'
|
||||
import type { Project } from '../types'
|
||||
|
||||
const projects = ref<Project[]>([])
|
||||
const selectedProjectId = ref('')
|
||||
const reportWorkflowId = ref('')
|
||||
const error = ref('')
|
||||
const creating = ref(false)
|
||||
|
||||
const isReportPage = computed(() => !!reportWorkflowId.value)
|
||||
|
||||
function parseUrl(): { projectId: string; reportId: string } {
|
||||
const reportMatch = location.pathname.match(/^\/report\/([^/]+)/)
|
||||
if (reportMatch) return { projectId: '', reportId: reportMatch[1] ?? '' }
|
||||
const projectMatch = location.pathname.match(/^\/projects\/([^/]+)/)
|
||||
return { projectId: projectMatch?.[1] ?? '', reportId: '' }
|
||||
}
|
||||
|
||||
function onPopState() {
|
||||
const { projectId, reportId } = parseUrl()
|
||||
selectedProjectId.value = projectId
|
||||
reportWorkflowId.value = reportId
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
projects.value = await api.listProjects()
|
||||
const first = projects.value[0]
|
||||
if (first) {
|
||||
selectedProjectId.value = first.id
|
||||
const { projectId, reportId } = parseUrl()
|
||||
if (reportId) {
|
||||
reportWorkflowId.value = reportId
|
||||
} else if (projectId && projects.value.some(p => p.id === projectId)) {
|
||||
selectedProjectId.value = projectId
|
||||
} else if (projects.value[0]) {
|
||||
selectedProjectId.value = projects.value[0].id
|
||||
history.replaceState(null, '', `/projects/${projects.value[0].id}`)
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
}
|
||||
window.addEventListener('popstate', onPopState)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('popstate', onPopState)
|
||||
})
|
||||
|
||||
function onSelectProject(id: string) {
|
||||
selectedProjectId.value = id
|
||||
reportWorkflowId.value = ''
|
||||
creating.value = false
|
||||
history.pushState(null, '', `/projects/${id}`)
|
||||
}
|
||||
|
||||
async function onCreateProject() {
|
||||
const name = prompt('项目名称')
|
||||
if (!name) return
|
||||
function onStartCreate() {
|
||||
creating.value = true
|
||||
selectedProjectId.value = ''
|
||||
history.pushState(null, '', '/')
|
||||
}
|
||||
|
||||
async function onConfirmCreate(req: string) {
|
||||
try {
|
||||
const project = await api.createProject(name)
|
||||
const project = await api.createProject('新项目')
|
||||
projects.value.unshift(project)
|
||||
await api.createWorkflow(project.id, req)
|
||||
creating.value = false
|
||||
selectedProjectId.value = project.id
|
||||
history.pushState(null, '', `/projects/${project.id}`)
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
}
|
||||
}
|
||||
|
||||
function onProjectUpdate(projectId: string, name: string) {
|
||||
const p = projects.value.find(p => p.id === projectId)
|
||||
if (p) p.name = name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<div v-if="isReportPage" class="report-fullpage">
|
||||
<ReportView :workflowId="reportWorkflowId" :key="reportWorkflowId" />
|
||||
</div>
|
||||
<div v-else class="app-layout">
|
||||
<Sidebar
|
||||
:projects="projects"
|
||||
:selectedId="selectedProjectId"
|
||||
@select="onSelectProject"
|
||||
@create="onCreateProject"
|
||||
@create="onStartCreate"
|
||||
/>
|
||||
<main class="main-content">
|
||||
<div v-if="error" class="error-banner">{{ error }}</div>
|
||||
<div v-if="!selectedProjectId" class="empty-state">
|
||||
<div v-if="error" class="error-banner" @click="error = ''">{{ error }}</div>
|
||||
<div v-if="creating" class="empty-state">
|
||||
<CreateForm @submit="onConfirmCreate" @cancel="creating = false" />
|
||||
</div>
|
||||
<div v-else-if="!selectedProjectId" class="empty-state">
|
||||
选择或创建一个项目开始
|
||||
</div>
|
||||
<WorkflowView v-else :projectId="selectedProjectId" :key="selectedProjectId" />
|
||||
<WorkflowView
|
||||
v-else
|
||||
:projectId="selectedProjectId"
|
||||
:key="selectedProjectId"
|
||||
@projectUpdate="onProjectUpdate"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.report-fullpage {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.app-layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
@@ -84,5 +144,6 @@ async function onCreateProject() {
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user