11"use client" ;
22
33import { useMemo , useState } from "react" ;
4- import { MockAgentAdapter } from "@/lib/agent/mockAdapter" ;
4+ import { createAgentAdapter } from "@/lib/agent/createAdapter" ;
5+ import { getAgentAdapterMode , getAgentApiBaseUrl , getAgentToolName } from "@/lib/agent/config" ;
56import { runAgent } from "@/lib/agent/runner" ;
67import type { AgentRunState } from "@/lib/agent/types" ;
78
8- type AgentScenario = "success" | "timeout" | "retry_exhausted" ;
9+ type AgentScenario = "success" | "timeout" | "retry_exhausted" | "remote_call" ;
910
1011type ScenarioConfig = {
1112 label : string ;
1213 description : string ;
13- run : ( ) => Promise < AgentRunState > ;
14+ maxRetries : number ;
15+ timeoutMs : number ;
16+ retryDelayMs : number ;
17+ mockMode ?: "success" | "timeout" | "fail" ;
1418} ;
1519
20+ type ScenarioMap = Partial < Record < AgentScenario , ScenarioConfig > > ;
21+
1622/**
1723 * 将状态值映射为统一徽标样式,避免页面里散落大量条件类名判断。
1824 */
@@ -33,93 +39,156 @@ const getStatusBadgeClass = (status: string) => {
3339 * Agent MVP 演示面板:展示本地状态机、mock adapter 和三条核心测试路径。
3440 */
3541export default function AgentMvpPanel ( ) {
36- const [ activeScenario , setActiveScenario ] = useState < AgentScenario > ( "success" ) ;
42+ const adapterMode = useMemo ( ( ) => getAgentAdapterMode ( ) , [ ] ) ;
43+ const configuredApiBaseUrl = useMemo ( ( ) => getAgentApiBaseUrl ( ) , [ ] ) ;
44+ const configuredToolName = useMemo ( ( ) => getAgentToolName ( ) , [ ] ) ;
45+ const [ activeScenario , setActiveScenario ] = useState < AgentScenario > (
46+ adapterMode === "http" ? "remote_call" : "success"
47+ ) ;
3748 const [ isRunning , setIsRunning ] = useState ( false ) ;
3849 const [ runState , setRunState ] = useState < AgentRunState | null > ( null ) ;
50+ const [ runDurationMs , setRunDurationMs ] = useState < number | null > ( null ) ;
3951 const [ actionError , setActionError ] = useState ( "" ) ;
52+ const [ runtimeMetadata , setRuntimeMetadata ] = useState < {
53+ mode : "mock" | "http" ;
54+ baseUrl ?: string ;
55+ toolName ?: string ;
56+ } | null > ( null ) ;
4057
41- const scenarioConfig = useMemo < Record < AgentScenario , ScenarioConfig > > (
42- ( ) => ( {
43- success : {
44- label : "成功路径" ,
45- description : "工具调用一次成功,run 最终为 succeeded。" ,
46- run : ( ) =>
47- runAgent ( {
48- runId : `run_success_${ Date . now ( ) } ` ,
49- sessionId : "ui_demo" ,
50- input : "演示成功路径" ,
51- adapter : new MockAgentAdapter ( { mode : "success" , delayMs : 0 } ) ,
52- maxRetries : 2 ,
53- timeoutMs : 120 ,
54- retryDelayMs : 0 ,
55- } ) ,
56- } ,
57- timeout : {
58- label : "工具超时" ,
59- description : "工具调用超过超时阈值,run 直接失败并返回 TOOL_TIMEOUT。" ,
60- run : ( ) =>
61- runAgent ( {
62- runId : `run_timeout_${ Date . now ( ) } ` ,
63- sessionId : "ui_demo" ,
64- input : "演示超时路径" ,
65- adapter : new MockAgentAdapter ( { mode : "timeout" } ) ,
66- maxRetries : 0 ,
67- timeoutMs : 20 ,
68- retryDelayMs : 0 ,
69- } ) ,
70- } ,
71- retry_exhausted : {
72- label : "重试失败" ,
73- description : "连续上游失败直至重试耗尽,run 返回 TOOL_RETRY_EXHAUSTED。" ,
74- run : ( ) =>
75- runAgent ( {
76- runId : `run_retry_${ Date . now ( ) } ` ,
77- sessionId : "ui_demo" ,
78- input : "演示重试耗尽路径" ,
79- adapter : new MockAgentAdapter ( { mode : "fail" , failMessage : "mock upstream error" } ) ,
80- maxRetries : 2 ,
81- timeoutMs : 120 ,
82- retryDelayMs : 0 ,
83- } ) ,
84- } ,
85- } ) ,
86- [ ]
58+ const scenarioConfig = useMemo < ScenarioMap > (
59+ ( ) =>
60+ adapterMode === "http"
61+ ? {
62+ remote_call : {
63+ label : "远端联调" ,
64+ description : "调用后端 MCP 工具接口,验证真实链路可用性。" ,
65+ maxRetries : 1 ,
66+ timeoutMs : 10_000 ,
67+ retryDelayMs : 0 ,
68+ } ,
69+ }
70+ : {
71+ success : {
72+ label : "成功路径" ,
73+ description : "工具调用一次成功,run 最终为 succeeded。" ,
74+ maxRetries : 2 ,
75+ timeoutMs : 120 ,
76+ retryDelayMs : 0 ,
77+ mockMode : "success" ,
78+ } ,
79+ timeout : {
80+ label : "工具超时" ,
81+ description : "工具调用超过超时阈值,run 直接失败并返回 TOOL_TIMEOUT。" ,
82+ maxRetries : 0 ,
83+ timeoutMs : 20 ,
84+ retryDelayMs : 0 ,
85+ mockMode : "timeout" ,
86+ } ,
87+ retry_exhausted : {
88+ label : "重试失败" ,
89+ description : "连续上游失败直至重试耗尽,run 返回 TOOL_RETRY_EXHAUSTED。" ,
90+ maxRetries : 2 ,
91+ timeoutMs : 120 ,
92+ retryDelayMs : 0 ,
93+ mockMode : "fail" ,
94+ } ,
95+ } ,
96+ [ adapterMode ]
8797 ) ;
8898
8999 const runScenario = async ( scenario : AgentScenario ) => {
100+ const selectedScenario = scenarioConfig [ scenario ] ;
101+ if ( ! selectedScenario ) return ;
102+
90103 setActionError ( "" ) ;
91104 setActiveScenario ( scenario ) ;
105+ setRunDurationMs ( null ) ;
92106 setIsRunning ( true ) ;
107+ const startedAt = performance . now ( ) ;
93108 try {
94- const result = await scenarioConfig [ scenario ] . run ( ) ;
109+ const resolvedAdapter =
110+ adapterMode === "http"
111+ ? createAgentAdapter ( {
112+ httpBaseUrl : configuredApiBaseUrl ,
113+ toolName : configuredToolName ,
114+ } )
115+ : createAgentAdapter ( {
116+ mockMode : selectedScenario . mockMode ,
117+ mockDelayMs : 0 ,
118+ } ) ;
119+
120+ setRuntimeMetadata ( {
121+ mode : resolvedAdapter . mode ,
122+ baseUrl : resolvedAdapter . metadata . baseUrl ,
123+ toolName : resolvedAdapter . metadata . toolName ,
124+ } ) ;
125+
126+ const result = await runAgent ( {
127+ runId : `run_${ scenario } _${ Date . now ( ) } ` ,
128+ sessionId : "ui_demo" ,
129+ input : `演示场景:${ scenario } ` ,
130+ adapter : resolvedAdapter . adapter ,
131+ maxRetries : selectedScenario . maxRetries ,
132+ timeoutMs : selectedScenario . timeoutMs ,
133+ retryDelayMs : selectedScenario . retryDelayMs ,
134+ } ) ;
95135 setRunState ( result ) ;
136+ setRunDurationMs ( Math . round ( performance . now ( ) - startedAt ) ) ;
96137 } catch ( error ) {
97138 setActionError ( error instanceof Error ? error . message : "Agent 演示运行失败" ) ;
98139 setRunState ( null ) ;
140+ setRunDurationMs ( Math . round ( performance . now ( ) - startedAt ) ) ;
99141 } finally {
100142 setIsRunning ( false ) ;
101143 }
102144 } ;
103145
104146 const stepState = runState ?. steps [ 0 ] ?? null ;
147+ const scenarioList = useMemo (
148+ ( ) => Object . entries ( scenarioConfig ) as Array < [ AgentScenario , ScenarioConfig ] > ,
149+ [ scenarioConfig ]
150+ ) ;
151+ const isHttpMode = adapterMode === "http" ;
152+ const isHttpConfigured = ! isHttpMode || Boolean ( configuredApiBaseUrl ) ;
105153
106154 return (
107155 < section className = "rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-900" >
108156 < div className = "flex flex-wrap items-center justify-between gap-3" >
109157 < div >
110158 < h2 className = "text-sm font-semibold text-slate-800 dark:text-slate-100" > Agent MVP 演示面板</ h2 >
111159 < p className = "mt-1 text-xs text-slate-500 dark:text-slate-400" >
112- 本地状态机 + Mock Adapter,验证成功/超时/重试失败三条核心路径。
160+ { isHttpMode
161+ ? "当前为 HTTP 联调模式:将调用后端 MCP 工具接口。"
162+ : "当前为 Mock 模式:验证成功/超时/重试失败三条核心路径。" }
113163 </ p >
114164 </ div >
115- < div className = "rounded-lg border border-amber-200 bg-amber-50 px-2.5 py-1 text-[11px] text-amber-700 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300" >
116- MVP:单 run / 单 step / 单工具调用
165+ < div className = "flex items-center gap-2" >
166+ < div className = "rounded-lg border border-slate-200 bg-slate-50 px-2.5 py-1 text-[11px] text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300" >
167+ 模式:{ isHttpMode ? "HTTP" : "Mock" }
168+ </ div >
169+ < div className = "rounded-lg border border-amber-200 bg-amber-50 px-2.5 py-1 text-[11px] text-amber-700 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300" >
170+ MVP:单 run / 单 step / 单工具调用
171+ </ div >
117172 </ div >
118173 </ div >
119174
175+ { isHttpMode ? (
176+ < div className = "mt-3 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-800/70 dark:text-slate-300" >
177+ < p >
178+ baseUrl:{ configuredApiBaseUrl || "未配置(请设置 NEXT_PUBLIC_AGENT_API_BASE_URL)" }
179+ </ p >
180+ < p className = "mt-1" > toolName:{ configuredToolName } </ p >
181+ </ div >
182+ ) : null }
183+
184+ { isHttpMode && ! isHttpConfigured ? (
185+ < div className = "mt-3 rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-700/60 dark:bg-red-900/20 dark:text-red-300" >
186+ 缺少 HTTP 联调配置:请设置 NEXT_PUBLIC_AGENT_API_BASE_URL。
187+ </ div >
188+ ) : null }
189+
120190 < div className = "mt-4 grid gap-2 md:grid-cols-3" >
121- { ( Object . keys ( scenarioConfig ) as AgentScenario [ ] ) . map ( ( scenario ) => {
122- const config = scenarioConfig [ scenario ] ;
191+ { scenarioList . map ( ( [ scenario , config ] ) => {
123192 const isActive = activeScenario === scenario ;
124193
125194 return (
@@ -170,6 +239,9 @@ export default function AgentMvpPanel() {
170239 events:{ runState ?. events . length ?? 0 }
171240 </ span >
172241 </ div >
242+ < p className = "mt-2 text-xs text-slate-600 dark:text-slate-300" >
243+ 耗时:{ runDurationMs === null ? "暂无" : `${ runDurationMs } ms` }
244+ </ p >
173245 < p className = "mt-2 text-xs text-slate-600 dark:text-slate-300" >
174246 错误码:{ runState ?. lastError ?. code ?? "无" }
175247 </ p >
@@ -194,6 +266,12 @@ export default function AgentMvpPanel() {
194266 < p className = "mt-2 text-xs text-slate-600 dark:text-slate-300" >
195267 summary:{ stepState ?. summary ?? "暂无" }
196268 </ p >
269+ { runtimeMetadata ? (
270+ < p className = "mt-2 text-xs text-slate-600 dark:text-slate-300" >
271+ adapter:{ runtimeMetadata . mode }
272+ { runtimeMetadata . toolName ? ` / ${ runtimeMetadata . toolName } ` : "" }
273+ </ p >
274+ ) : null }
197275 </ div >
198276 </ div >
199277
0 commit comments