main
mula.liu 2026-03-11 22:25:31 +08:00
parent 753e1053c2
commit 590eae9f0c
4 changed files with 44 additions and 12 deletions

View File

@ -141,7 +141,7 @@ export const dashboardEn = {
toolsLoadFail: 'Failed to load tool skills.', toolsLoadFail: 'Failed to load tool skills.',
toolsAddFail: 'Failed to add tool.', toolsAddFail: 'Failed to add tool.',
toolsRemoveFail: 'Failed to remove tool.', toolsRemoveFail: 'Failed to remove tool.',
toolsRemoveConfirm: (name: string) => `Remove skill ${name}?`, toolsRemoveConfirm: (name: string) => `Remove skill ${name}? This action cannot be undone.`,
agent: 'Agent', agent: 'Agent',
container: 'Container', container: 'Container',
current: 'Current', current: 'Current',

View File

@ -141,7 +141,7 @@ export const dashboardZhCn = {
toolsLoadFail: '读取工具技能失败。', toolsLoadFail: '读取工具技能失败。',
toolsAddFail: '新增工具失败。', toolsAddFail: '新增工具失败。',
toolsRemoveFail: '移除工具失败。', toolsRemoveFail: '移除工具失败。',
toolsRemoveConfirm: (name: string) => `确认移除技能 ${name}`, toolsRemoveConfirm: (name: string) => `确认移除技能 ${name}该操作不可撤销。`,
agent: '代理', agent: '代理',
container: '容器状态', container: '容器状态',
current: '当前状态', current: '当前状态',

View File

@ -818,6 +818,12 @@
position: relative; position: relative;
} }
.ops-chat-text.is-collapsed-user {
max-height: calc(1.58em * 5);
overflow: hidden;
position: relative;
}
.ops-chat-text.is-collapsed::after { .ops-chat-text.is-collapsed::after {
content: ''; content: '';
position: absolute; position: absolute;
@ -829,6 +835,17 @@
background: linear-gradient(to bottom, transparent, color-mix(in oklab, var(--panel-soft) 88%, var(--panel) 12%)); background: linear-gradient(to bottom, transparent, color-mix(in oklab, var(--panel-soft) 88%, var(--panel) 12%));
} }
.ops-chat-text.is-collapsed-user::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 42px;
pointer-events: none;
background: linear-gradient(to bottom, transparent, color-mix(in oklab, var(--panel-soft) 88%, var(--panel) 12%));
}
.ops-chat-text > *:first-child { .ops-chat-text > *:first-child {
margin-top: 0; margin-top: 0;
} }

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, type AnchorHTMLAttributes, type ChangeEvent, type KeyboardEvent, type ReactNode } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState, type AnchorHTMLAttributes, type ChangeEvent, type KeyboardEvent, type ReactNode } from 'react';
import axios from 'axios'; import axios from 'axios';
import { Activity, ArrowUp, Boxes, Check, ChevronLeft, ChevronRight, Clock3, Copy, Download, EllipsisVertical, ExternalLink, Eye, EyeOff, FileText, FolderOpen, Gauge, Hammer, Lock, Maximize2, MessageSquareText, Mic, Minimize2, Paperclip, Pencil, Plus, Power, PowerOff, RefreshCw, Repeat2, Reply, Save, Search, Settings2, SlidersHorizontal, Square, ThumbsDown, ThumbsUp, TriangleAlert, Trash2, UserRound, Waypoints, X } from 'lucide-react'; import { Activity, ArrowUp, Boxes, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Clock3, Copy, Download, EllipsisVertical, ExternalLink, Eye, EyeOff, FileText, FolderOpen, Gauge, Hammer, Lock, Maximize2, MessageSquareText, Mic, Minimize2, Paperclip, Pencil, Plus, Power, PowerOff, RefreshCw, Repeat2, Reply, Save, Search, Settings2, SlidersHorizontal, Square, ThumbsDown, ThumbsUp, TriangleAlert, Trash2, UserRound, Waypoints, X } from 'lucide-react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw'; import rehypeRaw from 'rehype-raw';
@ -794,6 +794,7 @@ export function BotDashboardModule({
const [botListQuery, setBotListQuery] = useState(''); const [botListQuery, setBotListQuery] = useState('');
const [botListPage, setBotListPage] = useState(1); const [botListPage, setBotListPage] = useState(1);
const [expandedProgressByKey, setExpandedProgressByKey] = useState<Record<string, boolean>>({}); const [expandedProgressByKey, setExpandedProgressByKey] = useState<Record<string, boolean>>({});
const [expandedUserByKey, setExpandedUserByKey] = useState<Record<string, boolean>>({});
const [feedbackSavingByMessageId, setFeedbackSavingByMessageId] = useState<Record<number, boolean>>({}); const [feedbackSavingByMessageId, setFeedbackSavingByMessageId] = useState<Record<number, boolean>>({});
const [showRuntimeActionModal, setShowRuntimeActionModal] = useState(false); const [showRuntimeActionModal, setShowRuntimeActionModal] = useState(false);
const [workspaceHoverCard, setWorkspaceHoverCard] = useState<WorkspaceHoverCardState | null>(null); const [workspaceHoverCard, setWorkspaceHoverCard] = useState<WorkspaceHoverCardState | null>(null);
@ -1304,11 +1305,16 @@ export function BotDashboardModule({
conversation.map((item, idx) => { conversation.map((item, idx) => {
const itemKey = `${item.id || item.ts}-${idx}`; const itemKey = `${item.id || item.ts}-${idx}`;
const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress'; const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress';
const isUserBubble = item.role === 'user';
const fullText = String(item.text || ''); const fullText = String(item.text || '');
const summaryText = isProgressBubble ? summarizeProgressText(fullText, isZh) : fullText; const summaryText = isProgressBubble ? summarizeProgressText(fullText, isZh) : fullText;
const hasSummary = isProgressBubble && summaryText.trim().length > 0 && summaryText.trim() !== fullText.trim(); const hasSummary = isProgressBubble && summaryText.trim().length > 0 && summaryText.trim() !== fullText.trim();
const collapsible = isProgressBubble && (hasSummary || shouldCollapseProgress(fullText)); const progressCollapsible = isProgressBubble && (hasSummary || shouldCollapseProgress(fullText));
const expanded = Boolean(expandedProgressByKey[itemKey]); const normalizedUserText = isUserBubble ? normalizeUserMessageText(fullText) : '';
const userLineCount = isUserBubble ? normalizedUserText.split('\n').length : 0;
const userCollapsible = isUserBubble && userLineCount > 5;
const collapsible = isProgressBubble ? progressCollapsible : userCollapsible;
const expanded = isProgressBubble ? Boolean(expandedProgressByKey[itemKey]) : Boolean(expandedUserByKey[itemKey]);
const displayText = isProgressBubble && !expanded ? summaryText : fullText; const displayText = isProgressBubble && !expanded ? summaryText : fullText;
const currentDayKey = new Date(item.ts).toDateString(); const currentDayKey = new Date(item.ts).toDateString();
const prevDayKey = idx > 0 ? new Date(conversation[idx - 1].ts).toDateString() : ''; const prevDayKey = idx > 0 ? new Date(conversation[idx - 1].ts).toDateString() : '';
@ -1356,21 +1362,28 @@ export function BotDashboardModule({
{collapsible ? ( {collapsible ? (
<LucentIconButton <LucentIconButton
className="ops-chat-expand-icon-btn" className="ops-chat-expand-icon-btn"
onClick={() => onClick={() => {
if (isProgressBubble) {
setExpandedProgressByKey((prev) => ({ setExpandedProgressByKey((prev) => ({
...prev, ...prev,
[itemKey]: !prev[itemKey], [itemKey]: !prev[itemKey],
})) }));
return;
} }
setExpandedUserByKey((prev) => ({
...prev,
[itemKey]: !prev[itemKey],
}));
}}
tooltip={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')} tooltip={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')}
aria-label={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')} aria-label={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')}
> >
{expanded ? '×' : '…'} {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</LucentIconButton> </LucentIconButton>
) : null} ) : null}
</div> </div>
</div> </div>
<div className={`ops-chat-text ${collapsible && !expanded ? 'is-collapsed' : ''}`}> <div className={`ops-chat-text ${collapsible && !expanded ? (isUserBubble ? 'is-collapsed-user' : 'is-collapsed') : ''}`}>
{item.text ? ( {item.text ? (
item.role === 'user' ? ( item.role === 'user' ? (
<> <>
@ -1380,7 +1393,7 @@ export function BotDashboardModule({
<div className="ops-user-quoted-text">{normalizeAssistantMessageText(item.quoted_reply)}</div> <div className="ops-user-quoted-text">{normalizeAssistantMessageText(item.quoted_reply)}</div>
</div> </div>
) : null} ) : null}
<div className="whitespace-pre-wrap">{normalizeUserMessageText(item.text)}</div> <div className="whitespace-pre-wrap">{normalizeUserMessageText(displayText)}</div>
</> </>
) : ( ) : (
<ReactMarkdown <ReactMarkdown
@ -1475,6 +1488,7 @@ export function BotDashboardModule({
[ [
conversation, conversation,
expandedProgressByKey, expandedProgressByKey,
expandedUserByKey,
feedbackSavingByMessageId, feedbackSavingByMessageId,
isZh, isZh,
selectedBotId, selectedBotId,
@ -1585,6 +1599,7 @@ export function BotDashboardModule({
useEffect(() => { useEffect(() => {
setExpandedProgressByKey({}); setExpandedProgressByKey({});
setExpandedUserByKey({});
setShowRuntimeActionModal(false); setShowRuntimeActionModal(false);
setWorkspaceHoverCard(null); setWorkspaceHoverCard(null);
}, [selectedBotId]); }, [selectedBotId]);