WIP: All changes including session and toolcall improvements

This commit is contained in:
yiliang114
2025-12-06 16:53:40 +08:00
parent 541d0b22e5
commit 57a684ad97
18 changed files with 622 additions and 230 deletions

View File

@@ -8,12 +8,7 @@
import type React from 'react';
import type { BaseToolCallProps } from '../shared/types.js';
import {
ToolCallContainer,
ToolCallCard,
ToolCallRow,
LocationsList,
} from '../shared/LayoutComponents.js';
import { FileLink } from '../../ui/FileLink.js';
import {
safeTitle,
groupContent,
@@ -25,7 +20,122 @@ import {
* Optimized for displaying search operations and results
* Shows query + result count or file list
*/
export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
// Local, scoped inline container for compact search rows (single result/text-only)
const InlineContainer: React.FC<{
status: 'success' | 'error' | 'warning' | 'loading' | 'default';
labelSuffix?: string;
children?: React.ReactNode;
isFirst?: boolean;
isLast?: boolean;
}> = ({ status, labelSuffix, children, isFirst, isLast }) => {
const beforeStatusClass =
status === 'success'
? 'before:text-qwen-success'
: status === 'error'
? 'before:text-qwen-error'
: status === 'warning'
? 'before:text-qwen-warning'
: 'before:text-qwen-loading before:opacity-70 before:animate-pulse-slow';
const lineCropTop = isFirst ? 'top-[24px]' : 'top-0';
const lineCropBottom = isLast ? 'bottom-auto h-[calc(100%-24px)]' : 'bottom-0';
return (
<div
className={
`qwen-message message-item relative pl-[30px] py-2 select-text ` +
`before:absolute before:left-[8px] before:top-2 before:content-["\\25cf"] before:text-[10px] before:z-[1] ` +
beforeStatusClass
}
>
{/* timeline vertical line */}
<div
className={`absolute left-[12px] ${lineCropTop} ${lineCropBottom} w-px bg-[var(--app-primary-border-color)]`}
aria-hidden
/>
<div className="flex-1 min-w-0">
<div className="flex items-baseline gap-2 min-w-0">
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
Search
</span>
{labelSuffix ? (
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
{labelSuffix}
</span>
) : null}
</div>
{children ? (
<div className="mt-1 text-[var(--app-secondary-foreground)]">{children}</div>
) : null}
</div>
</div>
);
};
// Local card layout for multi-result or error display
const SearchCard: React.FC<{
status: 'success' | 'error' | 'warning' | 'loading' | 'default';
children: React.ReactNode;
isFirst?: boolean;
isLast?: boolean;
}> = ({ status, children, isFirst, isLast }) => {
const beforeStatusClass =
status === 'success'
? 'before:text-qwen-success'
: status === 'error'
? 'before:text-qwen-error'
: status === 'warning'
? 'before:text-qwen-warning'
: 'before:text-qwen-loading before:opacity-70 before:animate-pulse-slow';
const lineCropTop = isFirst ? 'top-[24px]' : 'top-0';
const lineCropBottom = isLast ? 'bottom-auto h-[calc(100%-24px)]' : 'bottom-0';
return (
<div
className={
`qwen-message message-item relative pl-[30px] py-2 select-text ` +
`before:absolute before:left-[8px] before:top-2 before:content-["\\25cf"] before:text-[10px] before:z-[1] ` +
beforeStatusClass
}
>
{/* timeline vertical line */}
<div
className={`absolute left-[12px] ${lineCropTop} ${lineCropBottom} w-px bg-[var(--app-primary-border-color)]`}
aria-hidden
/>
<div className="bg-[var(--app-input-background)] border border-[var(--app-input-border)] rounded-medium p-large my-medium">
<div className="flex flex-col gap-3 min-w-0">{children}</div>
</div>
</div>
);
};
const SearchRow: React.FC<{ label: string; children: React.ReactNode }> = ({
label,
children,
}) => (
<div className="grid grid-cols-[80px_1fr] gap-medium min-w-0">
<div className="text-xs text-[var(--app-secondary-foreground)] font-medium pt-[2px]">
{label}
</div>
<div className="text-[var(--app-primary-foreground)] min-w-0 break-words">
{children}
</div>
</div>
);
const LocationsListLocal: React.FC<{
locations: Array<{ path: string; line?: number | null }>;
}> = ({ locations }) => (
<div className="flex flex-col gap-1 max-w-full">
{locations.map((loc, idx) => (
<FileLink key={idx} path={loc.path} line={loc.line} showFullPath={true} />
))}
</div>
);
export const SearchToolCall: React.FC<BaseToolCallProps> = ({
toolCall,
isFirst,
isLast,
}) => {
const { title, content, locations } = toolCall;
const queryText = safeTitle(title);
@@ -35,14 +145,14 @@ export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
// Error case: show search query + error in card layout
if (errors.length > 0) {
return (
<ToolCallCard icon="🔍">
<ToolCallRow label="Search">
<SearchCard status="error" isFirst={isFirst} isLast={isLast}>
<SearchRow label="Search">
<div className="font-mono">{queryText}</div>
</ToolCallRow>
<ToolCallRow label="Error">
<div className="text-[#c74e39] font-medium">{errors.join('\n')}</div>
</ToolCallRow>
</ToolCallCard>
</SearchRow>
<SearchRow label="Error">
<div className="text-qwen-error font-medium">{errors.join('\n')}</div>
</SearchRow>
</SearchCard>
);
}
@@ -52,28 +162,27 @@ export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
// If multiple results, use card layout; otherwise use compact format
if (locations.length > 1) {
return (
<ToolCallCard icon="🔍">
<ToolCallRow label="Search">
<SearchCard status={containerStatus} isFirst={isFirst} isLast={isLast}>
<SearchRow label="Search">
<div className="font-mono">{queryText}</div>
</ToolCallRow>
<ToolCallRow label={`Found (${locations.length})`}>
<LocationsList locations={locations} />
</ToolCallRow>
</ToolCallCard>
</SearchRow>
<SearchRow label={`Found (${locations.length})`}>
<LocationsListLocal locations={locations} />
</SearchRow>
</SearchCard>
);
}
// Single result - compact format
return (
<ToolCallContainer
label="Search"
<InlineContainer
status={containerStatus}
className="search-toolcall"
labelSuffix={`(${queryText})`}
isFirst={isFirst}
isLast={isLast}
>
{/* <span className="font-mono">{queryText}</span> */}
<span className="mx-2 opacity-50"></span>
<LocationsList locations={locations} />
</ToolCallContainer>
<LocationsListLocal locations={locations} />
</InlineContainer>
);
}
@@ -81,11 +190,11 @@ export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
if (textOutputs.length > 0) {
const containerStatus = mapToolStatusToContainerStatus(toolCall.status);
return (
<ToolCallContainer
label="Search"
<InlineContainer
status={containerStatus}
className="search-toolcall"
labelSuffix={queryText ? `(${queryText})` : undefined}
isFirst={isFirst}
isLast={isLast}
>
<div className="flex flex-col">
{textOutputs.map((text, index) => (
@@ -98,7 +207,7 @@ export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
</div>
))}
</div>
</ToolCallContainer>
</InlineContainer>
);
}
@@ -106,13 +215,9 @@ export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
if (queryText) {
const containerStatus = mapToolStatusToContainerStatus(toolCall.status);
return (
<ToolCallContainer
label="Search"
status={containerStatus}
className="search-toolcall"
>
<InlineContainer status={containerStatus} isFirst={isFirst} isLast={isLast}>
<span className="font-mono">{queryText}</span>
</ToolCallContainer>
</InlineContainer>
);
}