feat: refine business chat workflow

This commit is contained in:
inman
2026-05-13 23:52:11 +08:00
parent 043d0f0bfe
commit 6b503dcbe9
30 changed files with 4609 additions and 126 deletions

View File

@@ -460,7 +460,7 @@ export function ChatInput({
<div
className={cn(
"w-full shrink-0 px-0 pb-0 pt-3 mx-auto transition-all duration-300",
isEmpty ? "max-w-5xl" : "max-w-5xl"
isEmpty ? "max-w-4xl" : "max-w-4xl"
)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
@@ -493,21 +493,21 @@ export function ChatInput({
disabled={disabled || sending}
data-testid={`chat-quick-task-${task.id}`}
className={cn(
'inline-flex max-w-[220px] items-center gap-1.5 rounded-lg border px-2.5 py-1 text-[12px] font-medium shadow-sm transition-colors',
'inline-flex max-w-[220px] items-center gap-1.5 rounded-lg border px-2.5 py-1 text-[12px] font-medium transition-colors',
selected
? 'border-[#7DBADB] bg-white text-[#075985]'
: 'border-slate-200/80 bg-white/70 text-muted-foreground hover:bg-white dark:border-white/10 dark:bg-card dark:hover:bg-white/10',
? 'border-[#7DBADB] bg-[#F4FAFD] text-[#075985]'
: 'border-slate-200/80 bg-white text-muted-foreground hover:bg-[#F8FBFE] dark:border-white/10 dark:bg-card dark:hover:bg-white/10',
)}
>
<span className="truncate">{task.name}</span>
</button>
<div className="pointer-events-none absolute bottom-full left-0 z-30 mb-2 hidden w-72 rounded-xl border border-black/10 bg-white p-3 text-left shadow-xl group-hover:block dark:border-white/10 dark:bg-card">
<div className="pointer-events-none absolute bottom-full left-0 z-30 mb-2 hidden w-72 rounded-lg border border-slate-200/80 bg-white p-3 text-left shadow-[0_18px_48px_rgba(15,23,42,0.14)] group-hover:block dark:border-white/10 dark:bg-card">
<div className="text-[13px] font-semibold text-foreground">{task.name}</div>
<div className="mt-1 text-[12px] leading-5 text-muted-foreground">
{task.description || t('composer.quickTaskHelp')}
</div>
{skillNames && (
<div className="mt-2 rounded-lg bg-slate-50 px-2 py-1.5 text-[11px] leading-4 text-slate-600 dark:bg-white/5 dark:text-slate-300">
<div className="mt-2 rounded-md bg-slate-50 px-2 py-1.5 text-[11px] leading-4 text-slate-600 dark:bg-white/5 dark:text-slate-300">
{skillNames}
</div>
)}
@@ -519,7 +519,7 @@ export function ChatInput({
)}
{/* Input Container */}
<div className={`relative rounded-lg border bg-white/80 px-3 pb-1.5 pt-2.5 shadow-[0_16px_36px_rgba(15,23,42,0.06)] backdrop-blur transition-all dark:bg-card ${dragOver ? 'border-[#0369A1] ring-1 ring-[#0369A1]' : 'border-slate-200/80 dark:border-white/10'}`}>
<div className={`relative rounded-lg border bg-white px-3 pb-2 pt-3 shadow-none transition-colors dark:bg-card ${dragOver ? 'border-[#0369A1] ring-1 ring-[#0369A1]' : 'border-slate-200/80 dark:border-white/10'}`}>
{selectedKnowledgeDocuments.length > 0 && (
<div className="flex flex-wrap gap-1.5 pb-1.5">
{selectedKnowledgeDocuments.map((doc) => (
@@ -527,7 +527,7 @@ export function ChatInput({
key={doc.id}
type="button"
onClick={() => toggleKnowledgeDocument(doc.id)}
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-lg border border-[#7DBADB]/70 bg-[#EAF5FA] px-2.5 py-1 text-[13px] font-medium text-foreground transition-colors hover:bg-[#DDF0F8]"
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-lg border border-[#7DBADB]/70 bg-[#F4FAFD] px-2.5 py-1 text-[13px] font-medium text-foreground transition-colors hover:bg-[#EAF5FA]"
title={doc.name}
>
<BookOpen className="h-3 w-3 shrink-0 text-[#1E3A8A]" />
@@ -539,11 +539,11 @@ export function ChatInput({
)}
{/* Text Row — flush-left */}
<div className="flex min-h-[48px] items-start text-[15px] font-normal leading-6">
<div className="flex min-h-[48px] items-start text-[14px] font-normal leading-6">
{selectedQuickTaskPrompt && (
<span
onClick={() => textareaRef.current?.focus()}
className="mt-0 select-none whitespace-nowrap pr-1.5 font-sans text-[15px] font-normal leading-6 text-muted-foreground/60 md:text-[15px]"
className="mt-0 select-none whitespace-nowrap pr-1.5 font-sans text-[14px] font-normal leading-6 text-muted-foreground/60"
>
{selectedQuickTaskPrompt}
</span>
@@ -563,7 +563,7 @@ export function ChatInput({
placeholder={disabled ? t('composer.gatewayDisconnectedPlaceholder') : ''}
disabled={disabled}
data-testid="chat-composer-input"
className="block min-h-[48px] max-h-[240px] min-w-0 flex-1 resize-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0 shadow-none bg-transparent p-0 font-sans text-[15px] font-normal leading-6 placeholder:text-muted-foreground/60 md:text-[15px]"
className="block min-h-[48px] max-h-[240px] min-w-0 flex-1 resize-none border-0 bg-transparent p-0 font-sans text-[14px] font-normal leading-6 shadow-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0"
rows={1}
/>
</div>
@@ -587,7 +587,7 @@ export function ChatInput({
variant="ghost"
className={cn(
'h-8 rounded-lg px-2 text-[12px] text-muted-foreground transition-colors hover:bg-[#EAF5FA] hover:text-[#075985] dark:hover:bg-white/10',
selectedKnowledgeIds.length > 0 && 'bg-[#EAF5FA] text-[#075985] hover:bg-[#DDF0F8]',
selectedKnowledgeIds.length > 0 && 'bg-[#F4FAFD] text-[#075985] hover:bg-[#EAF5FA]',
)}
onClick={() => setKnowledgePickerOpen((open) => !open)}
disabled={!knowledgeBaseAvailable || disabled || sending}
@@ -629,7 +629,7 @@ export function ChatInput({
onClick={() => toggleKnowledgeDocument(doc.id)}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left transition-colors',
selected ? 'bg-[#EAF5FA] text-foreground' : 'hover:bg-black/5 dark:hover:bg-white/5',
selected ? 'bg-[#EAF5FA] text-foreground' : 'hover:bg-[#F8FBFE] dark:hover:bg-white/5',
)}
>
<span className={cn(
@@ -674,7 +674,7 @@ export function ChatInput({
</Button>
</div>
</div>
<div className="mt-2.5 flex items-center justify-between gap-2 text-[11px] text-muted-foreground/60 px-4">
<div className="mt-2.5 flex items-center justify-between gap-2 px-1 text-[11px] text-muted-foreground/60">
<div className="flex items-center gap-1.5">
<div className={cn("w-1.5 h-1.5 rounded-full", gatewayStatus.state === 'running' ? "bg-green-500/80" : "bg-red-500/80")} />
<span>
@@ -718,7 +718,7 @@ function AttachmentPreview({
const isImage = attachment.mimeType.startsWith('image/') && attachment.preview;
return (
<div className="relative group rounded-lg overflow-hidden border border-border">
<div className="group relative overflow-hidden rounded-lg border border-slate-200/80 dark:border-white/10">
{isImage ? (
// Image thumbnail
<div className="w-16 h-16">
@@ -730,7 +730,7 @@ function AttachmentPreview({
</div>
) : (
// Generic file card
<div className="flex items-center gap-2 px-3 py-2 bg-muted/50 max-w-[200px]">
<div className="flex max-w-[200px] items-center gap-2 bg-slate-50 px-3 py-2 dark:bg-white/5">
<FileIcon mimeType={attachment.mimeType} className="h-5 w-5 shrink-0 text-muted-foreground" />
<div className="min-w-0 overflow-hidden">
<p className="text-xs font-medium truncate">{attachment.fileName}</p>