main
mula.liu 2026-03-02 04:04:42 +08:00
parent 3cb25ba795
commit a2ac5c4fb5
4 changed files with 80 additions and 13 deletions

View File

@ -1,3 +1,4 @@
import json
import os
from typing import Any, Dict, List

View File

@ -147,14 +147,30 @@ export function useBotsSync() {
if (data.type === 'AGENT_STATE') {
const state = String(payload.state || data.state || 'INFO');
const messageRaw = String(payload.action_msg || payload.msg || data.action_msg || data.msg || '');
const message = summarizeProgressText(messageRaw, isZh);
const normalizedState = normalizeState(state);
const fullMessage = normalizeAssistantMessageText(messageRaw);
const message = fullMessage || summarizeProgressText(messageRaw, isZh) || t.stateUpdated;
updateBotState(bot.id, state, message);
addBotEvent(bot.id, {
state: normalizeState(state),
state: normalizedState,
text: message || t.stateUpdated,
ts: Date.now(),
channel: sourceChannel || undefined,
});
if (isDashboardChannel && fullMessage && (normalizedState === 'THINKING' || normalizedState === 'TOOL_CALL')) {
const chatText = normalizedState === 'TOOL_CALL' ? `${isZh ? '工具调用' : 'Tool Call'}\n${fullMessage}` : fullMessage;
const now = Date.now();
const prev = lastProgressRef.current[bot.id];
if (!prev || prev.text !== chatText || now - prev.ts > 1200) {
addBotMessage(bot.id, {
role: 'assistant',
text: chatText,
ts: now,
kind: 'progress',
});
lastProgressRef.current[bot.id] = { text: chatText, ts: now };
}
}
return;
}
if (data.type === 'ASSISTANT_MESSAGE') {
@ -178,10 +194,10 @@ export function useBotsSync() {
if (isProgress) {
const state = normalizeBusState(isTool);
const progressText = summarizeProgressText(content, isZh);
updateBotState(bot.id, state, progressText);
addBotEvent(bot.id, { state, text: progressText || t.progress, ts: Date.now(), channel: sourceChannel || undefined });
const fullProgress = content || progressText || (isZh ? '处理中...' : 'Processing...');
updateBotState(bot.id, state, fullProgress);
addBotEvent(bot.id, { state, text: fullProgress || t.progress, ts: Date.now(), channel: sourceChannel || undefined });
if (isDashboardChannel) {
const fullProgress = content || progressText || (isZh ? '处理中...' : 'Processing...');
const chatText = isTool ? `${isZh ? '工具调用' : 'Tool Call'}\n${fullProgress}` : fullProgress;
const now = Date.now();
const prev = lastProgressRef.current[bot.id];

View File

@ -1074,6 +1074,26 @@
font-weight: 700;
}
.ops-runtime-action {
min-width: 0;
display: grid;
gap: 6px;
}
.ops-runtime-action-text {
display: block;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
line-height: 1.54;
max-height: 82px;
overflow: auto;
}
.ops-runtime-action-text.expanded {
max-height: 180px;
}
.ops-preview {
border: 1px solid var(--line);
border-radius: 10px;

View File

@ -391,6 +391,7 @@ export function BotDashboardModule({
const [compactPanelTab, setCompactPanelTab] = useState<CompactPanelTab>('chat');
const [isCompactMobile, setIsCompactMobile] = useState(false);
const [expandedProgressByKey, setExpandedProgressByKey] = useState<Record<string, boolean>>({});
const [runtimeActionExpanded, setRuntimeActionExpanded] = useState(false);
const runtimeMenuRef = useRef<HTMLDivElement | null>(null);
const openWorkspacePathFromChat = (path: string) => {
const normalized = String(path || '').trim();
@ -607,12 +608,23 @@ export function BotDashboardModule({
return 'IDLE';
}, [selectedBot, isThinking, latestEvent]);
const runtimeAction = useMemo(() => {
const action = summarizeProgressText(selectedBot?.last_action || '', isZh);
if (action && action !== t.processing) return action;
const eventText = summarizeProgressText(latestEvent?.text || '', isZh);
if (eventText && eventText !== t.processing) return eventText;
const action = normalizeAssistantMessageText(selectedBot?.last_action || '').trim();
if (action) return action;
const eventText = normalizeAssistantMessageText(latestEvent?.text || '').trim();
if (eventText) return eventText;
return '-';
}, [selectedBot, latestEvent, isZh, t.processing]);
}, [selectedBot, latestEvent]);
const runtimeActionSummary = useMemo(() => {
const full = String(runtimeAction || '').trim();
if (!full || full === '-') return '-';
return summarizeProgressText(full, isZh);
}, [runtimeAction, isZh]);
const runtimeActionHasMore = useMemo(() => {
const full = String(runtimeAction || '').trim();
const summary = String(runtimeActionSummary || '').trim();
return Boolean(full && full !== '-' && summary && full !== summary);
}, [runtimeAction, runtimeActionSummary]);
const runtimeActionDisplay = runtimeActionExpanded || !runtimeActionHasMore ? runtimeAction : runtimeActionSummary;
const shouldCollapseProgress = (text: string) => {
const normalized = String(text || '').trim();
@ -626,8 +638,12 @@ export function BotDashboardModule({
conversation.map((item, idx) => {
const itemKey = `${item.ts}-${idx}`;
const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress';
const collapsible = isProgressBubble && shouldCollapseProgress(item.text);
const fullText = String(item.text || '');
const summaryText = isProgressBubble ? summarizeProgressText(fullText, isZh) : fullText;
const hasSummary = isProgressBubble && summaryText.trim().length > 0 && summaryText.trim() !== fullText.trim();
const collapsible = isProgressBubble && (hasSummary || shouldCollapseProgress(fullText));
const expanded = Boolean(expandedProgressByKey[itemKey]);
const displayText = isProgressBubble && !expanded ? summaryText : fullText;
return (
<div key={itemKey} className={`ops-chat-row ${item.role === 'user' ? 'is-user' : 'is-assistant'}`}>
<div className={`ops-chat-item ${item.role === 'user' ? 'is-user' : 'is-assistant'}`}>
@ -648,7 +664,7 @@ export function BotDashboardModule({
<div className="whitespace-pre-wrap">{normalizeUserMessageText(item.text)}</div>
) : (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{decorateWorkspacePathsForMarkdown(item.text)}
{decorateWorkspacePathsForMarkdown(displayText)}
</ReactMarkdown>
)
) : null}
@ -736,6 +752,7 @@ export function BotDashboardModule({
useEffect(() => {
setExpandedProgressByKey({});
setRuntimeActionExpanded(false);
}, [selectedBotId]);
useEffect(() => {
@ -1932,7 +1949,20 @@ export function BotDashboardModule({
<>
<div className="ops-runtime-row"><span>{t.container}</span><strong className="mono">{selectedBot.docker_status}</strong></div>
<div className="ops-runtime-row"><span>{t.current}</span><strong className="mono">{displayState}</strong></div>
<div className="ops-runtime-row"><span>{t.lastAction}</span><strong>{runtimeAction}</strong></div>
<div className="ops-runtime-row">
<span>{t.lastAction}</span>
<div className="ops-runtime-action">
<strong className={`ops-runtime-action-text ${runtimeActionExpanded ? 'expanded' : ''}`}>{runtimeActionDisplay}</strong>
{runtimeActionHasMore ? (
<button
className="ops-chat-more-btn"
onClick={() => setRuntimeActionExpanded((prev) => !prev)}
>
{runtimeActionExpanded ? (isZh ? '收起' : 'Less') : (isZh ? '更多' : 'More')}
</button>
) : null}
</div>
</div>
<div className="ops-runtime-row"><span>Provider</span><strong className="mono">{selectedBot.llm_provider || '-'}</strong></div>
<div className="ops-runtime-row"><span>Model</span><strong className="mono">{selectedBot.llm_model || '-'}</strong></div>
</>