feat(engine): 添加运行时上下文支持并重构工具迭代限制
添加 RuntimeContext 类用于捕获模型运行时的日期时间信息, 包括UTC时间、本地时间和时区信息,并在系统提示中显示这些信息。 同时增加最大上下文消息数和工具迭代次数的配置选项, 将验证服务从引擎加载器中移除,并更新相关的数据结构和接口。 BREAKING CHANGE: 移除了验证服务,相关字段被替换为证据状态和接受状态。 - 添加 RuntimeContext 类和相关渲染方法 - 增加 max_context_messages 和 max_tool_iterations 配置 - 移除 ValidationService 相关代码 - 更新消息记录中的验证状态字段 - 添加原始工具调用检测和回退处理
This commit is contained in:
@ -19,7 +19,7 @@ import {
|
||||
uploadFile,
|
||||
wsManager,
|
||||
} from '@/lib/api';
|
||||
import { mergeServerWithPendingUsers, shouldMergePendingUsers } from '@/lib/chat-messages';
|
||||
import { mergeServerWithPendingUsers, shouldDisplayChatMessage, shouldMergePendingUsers } from '@/lib/chat-messages';
|
||||
import { pickAppText } from '@/lib/i18n/core';
|
||||
import { useAppI18n } from '@/lib/i18n/provider';
|
||||
import { buildSessionProgressView } from '@/lib/session-progress';
|
||||
@ -32,7 +32,7 @@ function isSessionUpdatedEvent(data: WsEvent | Record<string, unknown>): data is
|
||||
|
||||
function activeTaskStatusLabel(status: string, locale: 'zh-CN' | 'en-US') {
|
||||
if (status === 'needs_revision') return pickAppText(locale, '待修改', 'Needs revision');
|
||||
if (status === 'awaiting_feedback') return pickAppText(locale, '待反馈', 'Awaiting feedback');
|
||||
if (status === 'awaiting_acceptance') return pickAppText(locale, '待验收', 'Awaiting acceptance');
|
||||
if (status === 'running') return pickAppText(locale, '进行中', 'Running');
|
||||
return pickAppText(locale, '进行中', 'Active');
|
||||
}
|
||||
@ -157,10 +157,11 @@ export default function ChatPage() {
|
||||
setSessionProcess(key, process);
|
||||
}
|
||||
void loadActiveTask(key);
|
||||
const shouldMergePending = shouldMergePendingUsers(detail.messages, localSnapshot, waitingForReply);
|
||||
const displayMessages = detail.messages.filter(shouldDisplayChatMessage);
|
||||
const shouldMergePending = shouldMergePendingUsers(displayMessages, localSnapshot, waitingForReply);
|
||||
const nextMessages = shouldMergePending
|
||||
? mergeServerWithPendingUsers(detail.messages, localSnapshot)
|
||||
: detail.messages;
|
||||
? mergeServerWithPendingUsers(displayMessages, localSnapshot)
|
||||
: displayMessages;
|
||||
setMessages(nextMessages);
|
||||
shouldSnapToLatestRef.current = true;
|
||||
const last = nextMessages[nextMessages.length - 1];
|
||||
@ -217,15 +218,11 @@ export default function ChatPage() {
|
||||
if (data.type === 'status' && data.status === 'thinking') {
|
||||
setIsThinking(true);
|
||||
} else if (data.type === 'message' && data.role === 'assistant') {
|
||||
const validationResult = data.validation_result ?? data.metadata?.validation_result;
|
||||
const validationStatus = data.validation_status
|
||||
? data.validation_status
|
||||
: validationResult
|
||||
? ((validationResult as Record<string, unknown>).accepted === true ? 'passed' : 'failed')
|
||||
: 'unknown';
|
||||
setIsThinking(false);
|
||||
setIsLoading(false);
|
||||
addMessage({
|
||||
const rawEvidenceStatus = data.evidence_status ?? data.metadata?.evidence_status;
|
||||
const evidenceStatus = rawEvidenceStatus === 'recorded' ? 'recorded' : undefined;
|
||||
const assistantMessage = {
|
||||
role: 'assistant',
|
||||
content: typeof data.content === 'string' ? data.content : '',
|
||||
timestamp: new Date().toISOString(),
|
||||
@ -233,8 +230,11 @@ export default function ChatPage() {
|
||||
run_id: typeof data.run_id === 'string' ? data.run_id : undefined,
|
||||
task_id: data.task_id ?? data.metadata?.task_id ?? null,
|
||||
task_status: data.task_status ?? data.metadata?.task_status ?? null,
|
||||
validation_status: validationStatus,
|
||||
});
|
||||
evidence_status: evidenceStatus,
|
||||
} as const;
|
||||
if (shouldDisplayChatMessage(assistantMessage)) {
|
||||
addMessage(assistantMessage);
|
||||
}
|
||||
void loadSessionMessages(typeof data.session_id === 'string' ? data.session_id : useChatStore.getState().sessionId);
|
||||
void loadActiveTask(typeof data.session_id === 'string' ? data.session_id : useChatStore.getState().sessionId);
|
||||
loadSessions();
|
||||
@ -359,17 +359,18 @@ export default function ChatPage() {
|
||||
await loadSessions();
|
||||
return;
|
||||
}
|
||||
addMessage({
|
||||
const assistantMessage = {
|
||||
role: 'assistant',
|
||||
content: result.response,
|
||||
timestamp: new Date().toISOString(),
|
||||
run_id: result.run_id,
|
||||
task_id: result.task_id,
|
||||
task_status: result.task_status,
|
||||
validation_status: result.validation_result
|
||||
? (result.validation_result.accepted === true ? 'passed' : 'failed')
|
||||
: 'unknown',
|
||||
});
|
||||
evidence_status: result.evidence_status === 'recorded' ? 'recorded' : undefined,
|
||||
} as const;
|
||||
if (shouldDisplayChatMessage(assistantMessage)) {
|
||||
addMessage(assistantMessage);
|
||||
}
|
||||
void getSessionProcess(sessionId).then((process) => setSessionProcess(sessionId, process)).catch(() => null);
|
||||
void loadActiveTask(sessionId);
|
||||
loadSessions();
|
||||
@ -393,7 +394,7 @@ export default function ChatPage() {
|
||||
}
|
||||
}, [addMessage, clearInputDraft, input, isLoading, loadActiveTask, loadSessionMessages, loadSessions, locale, pendingFiles, revisionTargetRunId, sessionId, setIsLoading, setIsThinking, setSessionProcess, thinkingModeEnabled, updateMessageFeedback]);
|
||||
|
||||
const handleFeedback = useCallback(async (runId: string, feedbackType: 'satisfied' | 'revise' | 'abandon', comment?: string) => {
|
||||
const handleFeedback = useCallback(async (runId: string, feedbackType: 'accept' | 'revise' | 'abandon', comment?: string) => {
|
||||
updateMessageFeedback(runId, feedbackType);
|
||||
try {
|
||||
await submitChatFeedback({
|
||||
|
||||
@ -1238,7 +1238,7 @@ function riskLabel(risk: string, t: (zh: string, en: string) => string): string
|
||||
|
||||
function triggerReasonLabel(reason: string, t: (zh: string, en: string) => string): string {
|
||||
const labels: Record<string, string> = {
|
||||
validation_accepted_and_user_satisfied: t('任务验证通过且用户满意', 'Validation accepted and user satisfied'),
|
||||
task_accepted: t('任务已接受', 'Task accepted'),
|
||||
};
|
||||
return labels[reason] || reason;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, HelpCircle, Loader2, MessageSquare, RefreshCw, ThumbsUp, Trash2, User, XCircle } from 'lucide-react';
|
||||
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, Loader2, MessageSquare, RefreshCw, ThumbsUp, Trash2, User, XCircle } from 'lucide-react';
|
||||
|
||||
import { TaskRuntimeStatusBadge, formatTaskRuntimeDuration, formatTaskRuntimeTime, progressPercent } from '@/components/task-runtime/TaskRuntimeShared';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@ -17,8 +17,9 @@ import { buildTaskRuntimeView, type TaskRuntimeNodeView } from '@/lib/task-runti
|
||||
import { useChatStore } from '@/lib/store';
|
||||
import type { BackendTask, BackendTaskRun, ProcessArtifact, ProcessEvent, ProcessRun } from '@/types';
|
||||
|
||||
type TaskFeedbackType = 'satisfied' | 'revise' | 'abandon';
|
||||
type TaskFeedbackType = 'accept' | 'revise' | 'abandon';
|
||||
type TaskFeedbackItem = {
|
||||
acceptance_type?: unknown;
|
||||
feedback_type?: unknown;
|
||||
comment?: unknown;
|
||||
created_at?: unknown;
|
||||
@ -151,12 +152,6 @@ export default function TaskDetailPage() {
|
||||
const backendFeedbackRunId = backendTask ? pickFeedbackRunId(backendTask) : null;
|
||||
|
||||
if (!task && backendTask) {
|
||||
const validation = backendTask.validation_result;
|
||||
const accepted = Boolean(validation?.accepted);
|
||||
const validationIssues = [
|
||||
...arrayOfStrings(validation?.issues),
|
||||
...arrayOfStrings(validation?.missing_requirements),
|
||||
];
|
||||
const feedbackItems = backendTask.feedback || [];
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl space-y-6 p-6">
|
||||
@ -232,57 +227,6 @@ export default function TaskDetailPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">{pickAppText(locale, '验证和反馈', 'Validation and feedback')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 text-sm">
|
||||
<div className="rounded-lg border border-border bg-muted/25 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{validation ? (
|
||||
accepted ? <CheckCircle2 className="h-5 w-5 text-[#657162]" /> : <XCircle className="h-5 w-5 text-destructive" />
|
||||
) : (
|
||||
<HelpCircle className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
<div className="font-medium">
|
||||
{validation
|
||||
? accepted
|
||||
? pickAppText(locale, '验证通过', 'Validation passed')
|
||||
: pickAppText(locale, '需要继续修改', 'Needs revision')
|
||||
: pickAppText(locale, '尚未验证', 'Not validated yet')}
|
||||
</div>
|
||||
</div>
|
||||
{validation ? (
|
||||
<div className="mt-2 text-muted-foreground">
|
||||
{pickAppText(locale, '评分', 'Score')}: {String(validation.score ?? '-')} · {pickAppText(locale, '验证器', 'Validator')}: {String(validation.validator ?? '-')}
|
||||
</div>
|
||||
) : null}
|
||||
{validationIssues.length > 0 && (
|
||||
<ul className="mt-3 list-disc space-y-1 pl-5 text-muted-foreground">
|
||||
{validationIssues.map((item, index) => <li key={`${item}:${index}`}>{item}</li>)}
|
||||
</ul>
|
||||
)}
|
||||
{typeof validation?.recommended_revision_prompt === 'string' && validation.recommended_revision_prompt && (
|
||||
<p className="mt-3 rounded-md bg-background p-3 text-muted-foreground">{validation.recommended_revision_prompt}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium">{pickAppText(locale, '用户反馈', 'User feedback')}</div>
|
||||
{feedbackItems.length === 0 ? (
|
||||
<p className="text-muted-foreground">{pickAppText(locale, '还没有用户反馈。', 'No user feedback yet.')}</p>
|
||||
) : (
|
||||
feedbackItems.map((item, index) => (
|
||||
<div key={index} className="rounded-md border border-border p-3">
|
||||
<div className="font-medium">{humanFeedback(String(item.feedback_type || ''), locale)}</div>
|
||||
{item.comment ? <p className="mt-1 text-muted-foreground">{String(item.comment)}</p> : null}
|
||||
{item.created_at ? <p className="mt-1 text-xs text-muted-foreground">{formatTaskRuntimeTime(String(item.created_at), locale)}</p> : null}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -476,6 +420,7 @@ export default function TaskDetailPage() {
|
||||
comment,
|
||||
});
|
||||
setRuntimeFeedback({
|
||||
acceptance_type: feedbackType,
|
||||
feedback_type: feedbackType,
|
||||
comment: comment || '',
|
||||
created_at: new Date().toISOString(),
|
||||
@ -660,14 +605,14 @@ function TaskFeedbackPanel({
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">{pickAppText(locale, '任务反馈', 'Task feedback')}</CardTitle>
|
||||
<CardTitle className="text-base">{pickAppText(locale, '任务验收', 'Task acceptance')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{recordedFeedback ? (
|
||||
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
<CheckCircle2 className="h-4 w-4 text-[#657162]" />
|
||||
{pickAppText(locale, '已提交反馈', 'Feedback submitted')}: {humanFeedback(String(recordedFeedback.feedback_type || ''), locale)}
|
||||
{pickAppText(locale, '已提交验收', 'Acceptance submitted')}: {humanFeedback(String(recordedFeedback.acceptance_type || recordedFeedback.feedback_type || ''), locale)}
|
||||
</div>
|
||||
{recordedFeedback.comment ? (
|
||||
<p className="mt-2 text-muted-foreground">{String(recordedFeedback.comment)}</p>
|
||||
@ -678,22 +623,22 @@ function TaskFeedbackPanel({
|
||||
</div>
|
||||
) : isFinalized ? (
|
||||
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm text-muted-foreground">
|
||||
{pickAppText(locale, '任务已结束,不能再提交新的反馈。', 'This task is finalized and cannot accept new feedback.')}
|
||||
{pickAppText(locale, '任务已结束,不能再提交新的验收。', 'This task is finalized and cannot accept new acceptance.')}
|
||||
</div>
|
||||
) : !runId ? (
|
||||
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm text-muted-foreground">
|
||||
{pickAppText(locale, '暂无可反馈的运行记录。', 'No run is available for feedback yet.')}
|
||||
{pickAppText(locale, '暂无可验收的运行记录。', 'No run is available for acceptance yet.')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-3">
|
||||
<FeedbackButton
|
||||
type="satisfied"
|
||||
type="accept"
|
||||
icon={<ThumbsUp className="mr-2 h-4 w-4" />}
|
||||
label={pickAppText(locale, '满意', 'Satisfied')}
|
||||
label={pickAppText(locale, '接受', 'Accept')}
|
||||
actionBusy={actionBusy}
|
||||
disabled={!canSubmit}
|
||||
onClick={() => submit('satisfied', comment.trim() || undefined)}
|
||||
onClick={() => submit('accept', comment.trim() || undefined)}
|
||||
/>
|
||||
<FeedbackButton
|
||||
type="revise"
|
||||
@ -717,10 +662,10 @@ function TaskFeedbackPanel({
|
||||
value={comment}
|
||||
onChange={(event) => setComment(event.target.value)}
|
||||
disabled={Boolean(recordedFeedback) || isFinalized || Boolean(actionBusy)}
|
||||
placeholder={pickAppText(locale, '需要修改时写下具体要求;满意或放弃可选填说明。', 'Describe requested changes; notes are optional for satisfied or abandon.')}
|
||||
placeholder={pickAppText(locale, '需要修改时写下具体要求;接受或放弃可选填说明。', 'Describe requested changes; notes are optional for accept or abandon.')}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{pickAppText(locale, '反馈将记录到当前任务运行:', 'Feedback will be recorded on run: ')}
|
||||
{pickAppText(locale, '验收将记录到当前任务运行:', 'Acceptance will be recorded on run: ')}
|
||||
<span className="font-mono">{runId || '-'}</span>
|
||||
<span className="mx-1">·</span>
|
||||
{pickAppText(locale, '会话:', 'Session: ')}
|
||||
@ -807,8 +752,7 @@ function humanTaskStatus(status: string, locale: 'zh-CN' | 'en-US') {
|
||||
const map: Record<string, [string, string]> = {
|
||||
open: ['已创建', 'Open'],
|
||||
running: ['执行中', 'Running'],
|
||||
validating: ['验证中', 'Validating'],
|
||||
awaiting_feedback: ['等待反馈', 'Awaiting feedback'],
|
||||
awaiting_acceptance: ['等待验收', 'Awaiting acceptance'],
|
||||
needs_revision: ['需要修改', 'Needs revision'],
|
||||
closed: ['已完成', 'Closed'],
|
||||
abandoned: ['已放弃', 'Abandoned'],
|
||||
@ -818,10 +762,10 @@ function humanTaskStatus(status: string, locale: 'zh-CN' | 'en-US') {
|
||||
}
|
||||
|
||||
function humanFeedback(type: string, locale: 'zh-CN' | 'en-US') {
|
||||
if (type === 'satisfied') return pickAppText(locale, '满意', 'Satisfied');
|
||||
if (type === 'accept' || type === 'satisfied') return pickAppText(locale, '接受', 'Accepted');
|
||||
if (type === 'revise') return pickAppText(locale, '请求修改', 'Revision requested');
|
||||
if (type === 'abandon') return pickAppText(locale, '放弃任务', 'Abandoned');
|
||||
return type || pickAppText(locale, '反馈', 'Feedback');
|
||||
return type || pickAppText(locale, '验收', 'Acceptance');
|
||||
}
|
||||
|
||||
function humanFinishReason(reason: string, locale: 'zh-CN' | 'en-US') {
|
||||
@ -848,7 +792,3 @@ function feedbackForRun(items: TaskFeedbackItem[], runId: string | null): TaskFe
|
||||
function latestFeedback(items: TaskFeedbackItem[]): TaskFeedbackItem | null {
|
||||
return [...items].reverse()[0] ?? null;
|
||||
}
|
||||
|
||||
function arrayOfStrings(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ function OrdinaryTasks() {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={task.status === 'awaiting_feedback' || task.status === 'closed' ? 'default' : 'secondary'}>
|
||||
<Badge variant={task.status === 'awaiting_acceptance' || task.status === 'closed' ? 'default' : 'secondary'}>
|
||||
{taskStatusLabel(task.status, locale)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@ -185,8 +185,7 @@ function taskStatusLabel(status: string, locale: 'zh-CN' | 'en-US') {
|
||||
const labels: Record<string, [string, string]> = {
|
||||
open: ['已创建', 'Open'],
|
||||
running: ['执行中', 'Running'],
|
||||
validating: ['验证中', 'Validating'],
|
||||
awaiting_feedback: ['等待反馈', 'Awaiting feedback'],
|
||||
awaiting_acceptance: ['等待验收', 'Awaiting acceptance'],
|
||||
needs_revision: ['需要修改', 'Needs revision'],
|
||||
closed: ['已完成', 'Closed'],
|
||||
abandoned: ['已放弃', 'Abandoned'],
|
||||
|
||||
Reference in New Issue
Block a user