Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
887 lines
22 KiB
Markdown
887 lines
22 KiB
Markdown
# AIA V2.1 <20>滨垢蝏<E59EA2>辣霈曇恣
|
||
|
||
> **<2A><>𧋦**嚗间2.1
|
||
> **<2A>𥕦遣<F0A595A6>交<EFBFBD>**嚗?026-01-11
|
||
> **<2A><><EFBFBD>舀<EFBFBD>**嚗鑹eact 19 + TypeScript 5 + Ant Design 6 + Ant Design X 2.1
|
||
|
||
---
|
||
|
||
## <20><> 璅∪<E79285>蝏𤘪<E89D8F>
|
||
|
||
```
|
||
frontend-v2/src/modules/aia/
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> pages/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Dashboard.tsx # <20>箄<EFBFBD>雿枏之<E69E8F><E4B98B><EFBFBD>擐㚚△嚗?
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> Workspace.tsx # 撖寡<E69296>撌乩<E6928C><E4B9A9>?
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> components/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> AgentPipeline/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> index.tsx # 5<>嗆挾瘚<E68CBE>偌蝥踹捆<E8B8B9>?
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> StageColumn.tsx # <20>閖𧫴畾萄<E795BE>
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> AgentCard.tsx # <20>箄<EFBFBD>雿枏㨃<E69E8F>?
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> IntentSearch/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> index.tsx # <20>誩㦛<E8AAA9>𦦵揣獢?
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> SuggestionDropdown.tsx # 撱箄悅銝𧢲<E98A9D>獢?
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> ConversationList/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> index.tsx # <20><>蟮隡朞<E99AA1><E69C9E>𡑒”
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> ConversationItem.tsx # 隡朞<E99AA1>憿?
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> MessageList/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> index.tsx # 瘨<><E798A8><EFBFBD>𡑒”
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> UserMessage.tsx # <20>冽<EFBFBD>瘨<EFBFBD><E798A8>
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> AssistantMessage.tsx # AI<41>𧼮<EFBFBD>
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> ThinkingBlock.tsx # 瘛勗漲<E58B97>肽<EFBFBD><E882BD><EFBFBD><EFBFBD>惩<EFBFBD>
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Attachment/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> AttachmentUpload.tsx # <20><>辣銝𠹺<E98A9D>
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> AttachmentCard.tsx # <20><>辣<EFBFBD>∠<EFBFBD>
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> AttachmentPreview.tsx # <20><>辣憸<E8BEA3><E686B8>
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> SlashCommands/
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> index.tsx # 敹急㭘<E680A5><E3AD98>誘<EFBFBD>𨅯<EFBFBD>
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> ActionBar/
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> index.tsx # 蝏𤘪<E89D8F><F0A498AA>滢<EFBFBD><E6BBA2>?
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> hooks/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> useConversation.ts # 撖寡<E69296>蝞∠<E89D9E>
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> useAgents.ts # <20>箄<EFBFBD>雿𤘪㺭<F0A498AA>?
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> useIntentRouter.ts # <20>誩㦛頝舐眏
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> useStreamMessage.ts # 瘚<><E7989A>瘨<EFBFBD><E798A8>
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> useAttachment.ts # <20><>辣銝𠹺<E98A9D>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> api/
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> index.ts # API 撠<><E692A0>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> types/
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> index.ts # TypeScript 蝐餃<E89D90>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> styles/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> dashboard.module.css # Dashboard <20>瑕<EFBFBD>
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> workspace.module.css # Workspace <20>瑕<EFBFBD>
|
||
<EFBFBD>婙<EFBFBD><EFBFBD><EFBFBD> index.tsx # 璅∪<E79285><E288AA>亙藁 + 頝舐眏
|
||
```
|
||
|
||
---
|
||
|
||
## <20>綫 霈曇恣閫<E681A3><E996AB>
|
||
|
||
### <20>脣蔗蝟餌<E89D9F>
|
||
|
||
```css
|
||
:root {
|
||
/* 5<>嗆挾瘚<E68CBE>偌蝥蹂蜓憸䁅𠧧 */
|
||
--stage-design: #3B82F6; /* <20>肽𠧧 - <20>𠉛弦霈曇恣 */
|
||
--stage-data: #8B5CF6; /* 蝝怨𠧧 - <20>唳旿<E594B3><E697BF><EFBFBD> */
|
||
--stage-analysis: #10B981; /* 蝏輯𠧧 - 蝏蠘恣<E8A098><E681A3><EFBFBD> */
|
||
--stage-write: #F59E0B; /* 璈躰𠧧 - 霈箸<E99C88><E7AEB8>啣<EFBFBD> */
|
||
--stage-publish: #EF4444; /* 蝥Z𠧧 - <20>鞉<EFBFBD><E99E89>穃<EFBFBD> */
|
||
|
||
/* <20>蠘<EFBFBD><E8A098>?*/
|
||
--ai-assistant: #6366F1; /* AI<41>拇<EFBFBD>銝餉𠧧 */
|
||
--thinking-bg: #F3F4F6; /* <20>肽<EFBFBD><E882BD><EFBFBD><EFBFBD>峕艶 */
|
||
--thinking-border: #E5E7EB; /* <20>肽<EFBFBD><E882BD><EFBFBD>颲寞<E9A2B2> */
|
||
|
||
/* Gemini 憌擧聢 */
|
||
--bg-primary: #FFFFFF;
|
||
--bg-secondary: #F9FAFB;
|
||
--text-primary: #111827;
|
||
--text-secondary: #6B7280;
|
||
--border-light: #E5E7EB;
|
||
}
|
||
```
|
||
|
||
### <20>渲<EFBFBD>蝟餌<E89D9F>
|
||
|
||
```css
|
||
/* <20>萄儐 8px 蝵烐聢 */
|
||
--spacing-xs: 4px;
|
||
--spacing-sm: 8px;
|
||
--spacing-md: 16px;
|
||
--spacing-lg: 24px;
|
||
--spacing-xl: 32px;
|
||
--spacing-2xl: 48px;
|
||
```
|
||
|
||
### <20>剔<EFBFBD>
|
||
|
||
```css
|
||
/* 蝘餃𢆡隡睃<E99AA1> */
|
||
--breakpoint-sm: 640px; /* <20>𧢲㦤 */
|
||
--breakpoint-md: 768px; /* 撟單踎 */
|
||
--breakpoint-lg: 1024px; /* 獢屸𢒰 */
|
||
--breakpoint-xl: 1280px; /* 憭批<E686AD> */
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> 憿菟𢒰霈曇恣
|
||
|
||
### 1. Dashboard嚗<64>惣<EFBFBD>賭<EFBFBD>憭批<E686AD>嚗?
|
||
|
||
```tsx
|
||
// pages/Dashboard.tsx
|
||
|
||
import { IntentSearch } from '../components/IntentSearch';
|
||
import { AgentPipeline } from '../components/AgentPipeline';
|
||
|
||
export const Dashboard: React.FC = () => {
|
||
return (
|
||
<div className={styles.dashboard}>
|
||
{/* 憿園<E686BF><E59C92>誩㦛<E8AAA9>𦦵揣獢?*/}
|
||
<header className={styles.header}>
|
||
<h1>AI <EFBFBD>箄<EFBFBD><EFBFBD>拇<EFBFBD></h1>
|
||
<p><EFBFBD>劐<EFBFBD>銋<EFBFBD>虾隞亙葬<EFBFBD>拇<EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>
|
||
<IntentSearch />
|
||
</header>
|
||
|
||
{/* 5<>嗆挾<E59786>箄<EFBFBD>雿𤘪<E99BBF>瘞渡瑪 */}
|
||
<main className={styles.main}>
|
||
<AgentPipeline />
|
||
</main>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
**撣<><E692A3><EFBFBD>寧<EFBFBD>**嚗?
|
||
- 憿園<E686BF>撅<EFBFBD>葉憭扳<E686AD>蝝X<E89D9D>
|
||
- 5<>嗆挾瘚<E68CBE>偌蝥踵赤<E8B8B5>穃像<E7A983>綽<EFBFBD>獢屸𢒰嚗? 蝥萄<E89DA5>皛𡁜𢆡嚗<F0A286A1>宏<EFBFBD>剁<EFBFBD>
|
||
- Gemini 憌擧聢憭抒<E686AD><E68A92>?
|
||
|
||
---
|
||
|
||
### 2. Workspace嚗<65>笆霂嘥極雿𨅯蝱嚗?
|
||
|
||
```tsx
|
||
// pages/Workspace.tsx
|
||
|
||
import { ChatContainer } from '@/shared/components/Chat';
|
||
import { ConversationList } from '../components/ConversationList';
|
||
import { ThinkingBlock } from '../components/MessageList/ThinkingBlock';
|
||
import { AttachmentUpload } from '../components/Attachment/AttachmentUpload';
|
||
|
||
export const Workspace: React.FC = () => {
|
||
const { conversationId } = useParams();
|
||
const { conversation, messages, sendMessage } = useConversation(conversationId);
|
||
|
||
return (
|
||
<div className={styles.workspace}>
|
||
{/* 撌虫儒颲寞<E9A2B2> - <20><>蟮隡朞<E99AA1> */}
|
||
<aside className={styles.sidebar}>
|
||
<ConversationList />
|
||
</aside>
|
||
|
||
{/* 銝餃笆霂嘥躹 */}
|
||
<main className={styles.main}>
|
||
{/* 撖寡<E69296>憭湧<E686AD> */}
|
||
<header className={styles.header}>
|
||
<AgentAvatar agent={conversation?.agent} />
|
||
<h2>{conversation?.agent?.name}</h2>
|
||
</header>
|
||
|
||
{/* 瘨<><E798A8><EFBFBD>𡑒” - 憭滨鍂<E6BBA8>𡁶鍂 Chat 蝏<>辣 */}
|
||
<ChatContainer
|
||
messages={messages}
|
||
onSend={sendMessage}
|
||
renderMessage={(msg) => (
|
||
<div className={styles.message}>
|
||
{/* 瘛勗漲<E58B97>肽<EFBFBD><E882BD><EFBFBD> */}
|
||
{msg.thinkingContent && (
|
||
<ThinkingBlock content={msg.thinkingContent} />
|
||
)}
|
||
{/* 瘨<><E798A8><EFBFBD><EFBFBD>捆 */}
|
||
<MarkdownRenderer content={msg.content} />
|
||
{/* <20><>辣<EFBFBD>∠<EFBFBD> */}
|
||
{msg.attachments?.map(att => (
|
||
<AttachmentCard key={att.id} attachment={att} />
|
||
))}
|
||
</div>
|
||
)}
|
||
inputFooter={<AttachmentUpload />}
|
||
/>
|
||
</main>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## <20>妝 蝏<>辣霂衣<E99C82>霈曇恣
|
||
|
||
### 1. AgentPipeline嚗?<3F>嗆挾瘚<E68CBE>偌蝥選<E89DA5>
|
||
|
||
```tsx
|
||
// components/AgentPipeline/index.tsx
|
||
|
||
interface AgentPipelineProps {
|
||
onAgentClick: (agentId: string) => void;
|
||
}
|
||
|
||
export const AgentPipeline: React.FC<AgentPipelineProps> = ({ onAgentClick }) => {
|
||
const { agents } = useAgents();
|
||
|
||
const stages = [
|
||
{ key: 'design', title: '<27>𠉛弦霈曇恣', color: 'var(--stage-design)' },
|
||
{ key: 'data', title: '<27>唳旿<E594B3><E697BF><EFBFBD>', color: 'var(--stage-data)' },
|
||
{ key: 'analysis', title: '蝏蠘恣<E8A098><E681A3><EFBFBD>', color: 'var(--stage-analysis)' },
|
||
{ key: 'write', title: '霈箸<E99C88><E7AEB8>啣<EFBFBD>', color: 'var(--stage-write)' },
|
||
{ key: 'publish', title: '<27>鞉<EFBFBD><E99E89>穃<EFBFBD>', color: 'var(--stage-publish)' },
|
||
];
|
||
|
||
return (
|
||
<div className={styles.pipeline}>
|
||
{stages.map((stage, index) => (
|
||
<StageColumn
|
||
key={stage.key}
|
||
title={stage.title}
|
||
color={stage.color}
|
||
agents={agents.filter(a => a.stage === stage.key)}
|
||
onAgentClick={onAgentClick}
|
||
showConnector={index < stages.length - 1}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
**<EFBFBD>瑕<EFBFBD><EFBFBD>寧<EFBFBD>**嚗?
|
||
```css
|
||
.pipeline {
|
||
display: flex;
|
||
gap: var(--spacing-lg);
|
||
overflow-x: auto;
|
||
padding: var(--spacing-xl) 0;
|
||
}
|
||
|
||
/* 蝘餃𢆡蝡舐熊<E88890>穃<EFBFBD>撅<EFBFBD> */
|
||
@media (max-width: 768px) {
|
||
.pipeline {
|
||
flex-direction: column;
|
||
overflow-x: visible;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. IntentSearch嚗<68><E59A97><EFBFBD>暹<EFBFBD>蝝X<E89D9D>嚗?
|
||
|
||
```tsx
|
||
// components/IntentSearch/index.tsx
|
||
|
||
export const IntentSearch: React.FC = () => {
|
||
const [query, setQuery] = useState('');
|
||
const [suggestions, setSuggestions] = useState<IntentSuggestion[]>([]);
|
||
const { routeIntent, isLoading } = useIntentRouter();
|
||
const navigate = useNavigate();
|
||
|
||
// 500ms <20>脫<EFBFBD>
|
||
const debouncedQuery = useDebouncedValue(query, 500);
|
||
|
||
useEffect(() => {
|
||
if (debouncedQuery.length >= 2) {
|
||
routeIntent(debouncedQuery).then(setSuggestions);
|
||
}
|
||
}, [debouncedQuery]);
|
||
|
||
const handleSelect = (suggestion: IntentSuggestion) => {
|
||
// 頝唾蓮<E594BE>啣笆摨娍惣<E5A88D>賭<EFBFBD>
|
||
navigate(`/aia/workspace?agent=${suggestion.agentId}&prompt=${encodeURIComponent(suggestion.prefillPrompt)}`);
|
||
};
|
||
|
||
return (
|
||
<div className={styles.searchContainer}>
|
||
<Input.Search
|
||
size="large"
|
||
placeholder="<22>讛膩<E8AE9B>函<EFBFBD><E587BD><EFBFBD>瘙<EFBFBD><E79899>AI 撠<>蛹<EFBFBD>冽綫<E586BD>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD><EFBFBD>拇<EFBFBD>..."
|
||
value={query}
|
||
onChange={(e) => setQuery(e.target.value)}
|
||
loading={isLoading}
|
||
className={styles.searchInput}
|
||
/>
|
||
|
||
{suggestions.length > 0 && (
|
||
<SuggestionDropdown
|
||
suggestions={suggestions}
|
||
onSelect={handleSelect}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 3. ThinkingBlock嚗<6B>楛摨行<E691A8>肽<EFBFBD><E882BD><EFBFBD><EFBFBD>惩<EFBFBD>嚗?
|
||
|
||
```tsx
|
||
// components/MessageList/ThinkingBlock.tsx
|
||
|
||
interface ThinkingBlockProps {
|
||
content: string;
|
||
duration?: number; // <20>肽<EFBFBD><E882BD><EFBFBD>埈𧒄嚗<F0A79284><E59A97>嚗?
|
||
isStreaming?: boolean; // <20>臬炏甇<E7828F>銁<EFBFBD><E98A81><EFBFBD>
|
||
}
|
||
|
||
export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
|
||
content,
|
||
duration,
|
||
isStreaming = false,
|
||
}) => {
|
||
// <20><><EFBFBD>銝剖<E98A9D>撘<EFBFBD>嚗<EFBFBD><E59A97><EFBFBD>𣂼<EFBFBD><F0A382BC>芸𢆡<E88AB8>嗉絲
|
||
const [expanded, setExpanded] = useState(isStreaming);
|
||
|
||
useEffect(() => {
|
||
if (!isStreaming && expanded) {
|
||
// 摰峕<E691B0><E5B395>?1.5s <20>芸𢆡<E88AB8>嗉絲
|
||
const timer = setTimeout(() => setExpanded(false), 1500);
|
||
return () => clearTimeout(timer);
|
||
}
|
||
}, [isStreaming]);
|
||
|
||
return (
|
||
<div className={styles.thinkingBlock}>
|
||
<div
|
||
className={styles.header}
|
||
onClick={() => setExpanded(!expanded)}
|
||
>
|
||
<span className={styles.icon}>
|
||
{isStreaming ? <LoadingOutlined spin /> : <BulbOutlined />}
|
||
</span>
|
||
<span className={styles.title}>
|
||
{isStreaming ? '甇<>銁瘛勗漲<E58B97>肽<EFBFBD>?..' : `撌脫楛摨行<EFBFBD>肽<EFBFBD>?(<28>埈𧒄 ${duration?.toFixed(1)}s)`}
|
||
</span>
|
||
<span className={styles.expandIcon}>
|
||
{expanded ? <UpOutlined /> : <DownOutlined />}
|
||
</span>
|
||
</div>
|
||
|
||
{expanded && (
|
||
<div className={styles.content}>
|
||
<Typography.Text type="secondary">
|
||
{content}
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
**<EFBFBD>瑕<EFBFBD>**嚗?
|
||
```css
|
||
.thinkingBlock {
|
||
background: var(--thinking-bg);
|
||
border: 1px solid var(--thinking-border);
|
||
border-radius: 8px;
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.content {
|
||
padding: var(--spacing-md);
|
||
padding-top: 0;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
color: var(--text-secondary);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. AttachmentUpload嚗<64><E59A97>隞嗡<E99A9E>隡𩤃<E99AA1>
|
||
|
||
```tsx
|
||
// components/Attachment/AttachmentUpload.tsx
|
||
|
||
interface AttachmentUploadProps {
|
||
conversationId: string;
|
||
onUploadComplete: (attachment: Attachment) => void;
|
||
maxCount?: number; // 暺䁅恕 5
|
||
}
|
||
|
||
export const AttachmentUpload: React.FC<AttachmentUploadProps> = ({
|
||
conversationId,
|
||
onUploadComplete,
|
||
maxCount = 5,
|
||
}) => {
|
||
const { uploadFile, uploading, progress } = useAttachment();
|
||
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
||
|
||
const handleUpload = async (file: File) => {
|
||
if (attachments.length >= maxCount) {
|
||
message.error(`<EFBFBD><EFBFBD>憭帋<EFBFBD>隡?${maxCount} 銝芷<E98A9D>隞跆);
|
||
return false;
|
||
}
|
||
|
||
// <20><>辣蝐餃<E89D90><E9A483>⊿<EFBFBD>
|
||
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
message.error('隞<>𣈲<EFBFBD>?PDF<44><46>ord<72><64>XT<58><54>xcel <20><>辣');
|
||
return false;
|
||
}
|
||
|
||
// <20><>辣憭批<E686AD><E689B9>⊿<EFBFBD>嚗?0MB嚗?
|
||
if (file.size > 20 * 1024 * 1024) {
|
||
message.error('<27><>辣憭批<E686AD>銝滩<E98A9D>頞<EFBFBD><E9A09E> 20MB');
|
||
return false;
|
||
}
|
||
|
||
const attachment = await uploadFile(conversationId, file);
|
||
setAttachments([...attachments, attachment]);
|
||
onUploadComplete(attachment);
|
||
return false; // <20>餅迫暺䁅恕銝𠹺<E98A9D>
|
||
};
|
||
|
||
return (
|
||
<div className={styles.uploadContainer}>
|
||
<Upload
|
||
beforeUpload={handleUpload}
|
||
showUploadList={false}
|
||
accept=".pdf,.docx,.txt,.xlsx"
|
||
multiple
|
||
>
|
||
<Button icon={<PaperClipOutlined />} type="text">
|
||
瘛餃<E7989B><E9A483><EFBFBD>辣
|
||
</Button>
|
||
</Upload>
|
||
|
||
{/* 撌脖<E6928C>隡𣳇<E99AA1>隞嗅<E99A9E>銵?*/}
|
||
<div className={styles.attachmentList}>
|
||
{attachments.map(att => (
|
||
<AttachmentCard
|
||
key={att.id}
|
||
attachment={att}
|
||
onRemove={() => {
|
||
setAttachments(attachments.filter(a => a.id !== att.id));
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* 銝𠹺<E98A9D>餈𥕦漲 */}
|
||
{uploading && (
|
||
<Progress percent={progress} size="small" />
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 5. SlashCommands嚗<73>翰<EFBFBD>瑟<EFBFBD>隞歹<E99A9E>
|
||
|
||
```tsx
|
||
// components/SlashCommands/index.tsx
|
||
|
||
interface SlashCommandsProps {
|
||
onSelect: (command: SlashCommand) => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const commands: SlashCommand[] = [
|
||
{ key: 'polish', icon: '<27>?, label: '瘨西𠧧', description: '隡睃<EFBFBD><EFBFBD><EFBFBD>𧋦銵刻噢' },
|
||
{ key: 'expand', icon: '<EFBFBD><EFBFBD>', label: '<EFBFBD>拙<EFBFBD>', description: '<EFBFBD>拙<EFBFBD><EFBFBD><EFBFBD>捆蝏<EFBFBD><EFBFBD>' },
|
||
{ key: 'translate', icon: '<EFBFBD><EFBFBD>', label: '蝧餉<EFBFBD>', description: '銝剛㘚鈭坿<EFBFBD>' },
|
||
{ key: 'export', icon: '<EFBFBD><EFBFBD>', label: '撖澆枂Word', description: '撖澆枂銝?Word <EFBFBD><EFBFBD>﹝' },
|
||
];
|
||
|
||
export const SlashCommands: React.FC<SlashCommandsProps> = ({
|
||
onSelect,
|
||
onClose,
|
||
}) => {
|
||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||
|
||
// <20>桃<EFBFBD>撖潸⏛
|
||
useEffect(() => {
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
switch (e.key) {
|
||
case 'ArrowUp':
|
||
e.preventDefault();
|
||
setSelectedIndex(i => Math.max(0, i - 1));
|
||
break;
|
||
case 'ArrowDown':
|
||
e.preventDefault();
|
||
setSelectedIndex(i => Math.min(commands.length - 1, i + 1));
|
||
break;
|
||
case 'Enter':
|
||
e.preventDefault();
|
||
onSelect(commands[selectedIndex]);
|
||
break;
|
||
case 'Escape':
|
||
onClose();
|
||
break;
|
||
}
|
||
};
|
||
|
||
window.addEventListener('keydown', handleKeyDown);
|
||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
}, [selectedIndex]);
|
||
|
||
return (
|
||
<div className={styles.commandMenu}>
|
||
{commands.map((cmd, index) => (
|
||
<div
|
||
key={cmd.key}
|
||
className={`${styles.commandItem} ${index === selectedIndex ? styles.selected : ''}`}
|
||
onClick={() => onSelect(cmd)}
|
||
onMouseEnter={() => setSelectedIndex(index)}
|
||
>
|
||
<span className={styles.icon}>{cmd.icon}</span>
|
||
<div className={styles.content}>
|
||
<div className={styles.label}>{cmd.label}</div>
|
||
<div className={styles.description}>{cmd.description}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 6. ActionBar嚗<72><E59A97><EFBFBD>𨀣<EFBFBD>雿𨀣<E99BBF>嚗?
|
||
|
||
```tsx
|
||
// components/ActionBar/index.tsx
|
||
|
||
interface ActionBarProps {
|
||
message: Message;
|
||
onCopy: () => void;
|
||
onRegenerate: () => void;
|
||
onExport: () => void;
|
||
}
|
||
|
||
export const ActionBar: React.FC<ActionBarProps> = ({
|
||
message,
|
||
onCopy,
|
||
onRegenerate,
|
||
onExport,
|
||
}) => {
|
||
return (
|
||
<div className={styles.actionBar}>
|
||
<Tooltip title="憭滚<E686AD>">
|
||
<Button
|
||
type="text"
|
||
icon={<CopyOutlined />}
|
||
onClick={onCopy}
|
||
/>
|
||
</Tooltip>
|
||
<Tooltip title="<22>齿鰵<E9BDBF><E9B0B5><EFBFBD>">
|
||
<Button
|
||
type="text"
|
||
icon={<ReloadOutlined />}
|
||
onClick={onRegenerate}
|
||
/>
|
||
</Tooltip>
|
||
<Tooltip title="撖澆枂 Word">
|
||
<Button
|
||
type="text"
|
||
icon={<FileWordOutlined />}
|
||
onClick={onExport}
|
||
/>
|
||
</Tooltip>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## <20>𦹄 Hooks 霈曇恣
|
||
|
||
### useConversation
|
||
|
||
```typescript
|
||
// hooks/useConversation.ts
|
||
|
||
interface UseConversationReturn {
|
||
conversation: Conversation | null;
|
||
messages: Message[];
|
||
isLoading: boolean;
|
||
sendMessage: (content: string, attachmentIds?: string[]) => Promise<void>;
|
||
regenerate: (messageId: string) => Promise<void>;
|
||
deleteConversation: () => Promise<void>;
|
||
}
|
||
|
||
export function useConversation(conversationId?: string): UseConversationReturn {
|
||
const [conversation, setConversation] = useState<Conversation | null>(null);
|
||
const [messages, setMessages] = useState<Message[]>([]);
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
|
||
// <20>㰘蝸撖寡<E69296>
|
||
useEffect(() => {
|
||
if (conversationId) {
|
||
api.getConversation(conversationId).then(data => {
|
||
setConversation(data);
|
||
setMessages(data.messages);
|
||
});
|
||
}
|
||
}, [conversationId]);
|
||
|
||
// <20>煾<EFBFBD><E785BE><EFBFBD><EFBFBD>荔<EFBFBD>瘚<EFBFBD><E7989A>嚗?
|
||
const sendMessage = async (content: string, attachmentIds?: string[]) => {
|
||
setIsLoading(true);
|
||
|
||
// 瘛餃<E7989B><E9A483>冽<EFBFBD>瘨<EFBFBD><E798A8>
|
||
const userMessage: Message = {
|
||
id: `temp-${Date.now()}`,
|
||
role: 'user',
|
||
content,
|
||
attachments: [],
|
||
createdAt: new Date().toISOString(),
|
||
};
|
||
setMessages(prev => [...prev, userMessage]);
|
||
|
||
// <20>嘥<EFBFBD><E598A5>?AI 瘨<><E798A8>
|
||
const aiMessage: Message = {
|
||
id: `temp-ai-${Date.now()}`,
|
||
role: 'assistant',
|
||
content: '',
|
||
thinkingContent: '',
|
||
createdAt: new Date().toISOString(),
|
||
};
|
||
setMessages(prev => [...prev, aiMessage]);
|
||
|
||
// 瘚<><E7989A><EFBFBD>交𤣰
|
||
await api.sendMessageStream(conversationId!, content, attachmentIds, {
|
||
onThinkingDelta: (delta) => {
|
||
setMessages(prev => {
|
||
const last = prev[prev.length - 1];
|
||
return [...prev.slice(0, -1), {
|
||
...last,
|
||
thinkingContent: (last.thinkingContent || '') + delta,
|
||
}];
|
||
});
|
||
},
|
||
onDelta: (delta) => {
|
||
setMessages(prev => {
|
||
const last = prev[prev.length - 1];
|
||
return [...prev.slice(0, -1), {
|
||
...last,
|
||
content: last.content + delta,
|
||
}];
|
||
});
|
||
},
|
||
onComplete: (finalMessage) => {
|
||
setMessages(prev => {
|
||
return [...prev.slice(0, -1), finalMessage];
|
||
});
|
||
setIsLoading(false);
|
||
},
|
||
onError: (error) => {
|
||
message.error(error.message);
|
||
setIsLoading(false);
|
||
},
|
||
});
|
||
};
|
||
|
||
return {
|
||
conversation,
|
||
messages,
|
||
isLoading,
|
||
sendMessage,
|
||
regenerate: async () => {},
|
||
deleteConversation: async () => {},
|
||
};
|
||
}
|
||
```
|
||
|
||
### useStreamMessage
|
||
|
||
```typescript
|
||
// hooks/useStreamMessage.ts
|
||
|
||
interface StreamCallbacks {
|
||
onThinkingStart?: () => void;
|
||
onThinkingDelta?: (content: string) => void;
|
||
onThinkingEnd?: (duration: number) => void;
|
||
onMessageStart?: (id: string) => void;
|
||
onDelta?: (content: string) => void;
|
||
onMessageEnd?: (message: Message) => void;
|
||
onComplete?: (message: Message) => void;
|
||
onError?: (error: Error) => void;
|
||
}
|
||
|
||
export function useStreamMessage() {
|
||
const streamMessage = async (
|
||
url: string,
|
||
body: object,
|
||
callbacks: StreamCallbacks
|
||
) => {
|
||
const response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'text/event-stream',
|
||
},
|
||
body: JSON.stringify(body),
|
||
});
|
||
|
||
const reader = response.body?.getReader();
|
||
const decoder = new TextDecoder();
|
||
|
||
while (true) {
|
||
const { done, value } = await reader!.read();
|
||
if (done) break;
|
||
|
||
const chunk = decoder.decode(value);
|
||
const events = parseSSE(chunk);
|
||
|
||
for (const event of events) {
|
||
switch (event.type) {
|
||
case 'thinking_start':
|
||
callbacks.onThinkingStart?.();
|
||
break;
|
||
case 'thinking_delta':
|
||
callbacks.onThinkingDelta?.(event.data.content);
|
||
break;
|
||
case 'thinking_end':
|
||
callbacks.onThinkingEnd?.(event.data.duration);
|
||
break;
|
||
case 'message_start':
|
||
callbacks.onMessageStart?.(event.data.id);
|
||
break;
|
||
case 'delta':
|
||
callbacks.onDelta?.(event.data.content);
|
||
break;
|
||
case 'message_end':
|
||
callbacks.onMessageEnd?.(event.data);
|
||
break;
|
||
case 'done':
|
||
callbacks.onComplete?.(event.data);
|
||
break;
|
||
case 'error':
|
||
callbacks.onError?.(new Error(event.data.message));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
return { streamMessage };
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>𢲡 <20>滚<EFBFBD>撘讛挽霈?
|
||
|
||
### <20>剔<EFBFBD>蝑𣇉裦
|
||
|
||
```typescript
|
||
// <20>剔<EFBFBD>摰帋<E691B0>
|
||
const breakpoints = {
|
||
sm: 640, // <20>𧢲㦤
|
||
md: 768, // 撟單踎嚗<E8B88E>蜓閬<E89C93>鱏<EFBFBD>對<EFBFBD>
|
||
lg: 1024, // 獢屸𢒰
|
||
xl: 1280, // 憭批<E686AD>
|
||
};
|
||
```
|
||
|
||
### Dashboard <20>滚<EFBFBD>撘?
|
||
|
||
| <20>剔<EFBFBD> | 撣<><E692A3> |
|
||
|------|------|
|
||
| `< 768px` | 瘚<>偌蝥輻熊<E8BCBB>烐<EFBFBD><E78390>剁<EFBFBD><E58981>∠<EFBFBD><E288A0>訫<EFBFBD> |
|
||
| `<60>?768px` | 瘚<>偌蝥踵赤<E8B8B5>?5 <20>?|
|
||
|
||
### Workspace <20>滚<EFBFBD>撘?
|
||
|
||
| <20>剔<EFBFBD> | 撣<><E692A3> |
|
||
|------|------|
|
||
| `< 768px` | 靘扯器<E689AF>誯<EFBFBD><E8AAAF>𧶏<EFBFBD><F0A7B68F>賢<EFBFBD>皛穃枂嚗㚁<E59A97>颲枏<E9A2B2>獢<EFBFBD>睸<EFBFBD>㗛<EFBFBD><E3979B><EFBFBD> |
|
||
| `<60>?768px` | 靘扯器<E689AF>誩𤐄摰𡁏遬蝷?240px |
|
||
|
||
---
|
||
|
||
## <20><> 蝐餃<E89D90>摰帋<E691B0>
|
||
|
||
```typescript
|
||
// types/index.ts
|
||
|
||
export interface Agent {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
icon: string;
|
||
stage: 'design' | 'data' | 'analysis' | 'write' | 'publish';
|
||
color: string;
|
||
knowledgeBaseId?: string;
|
||
isTool?: boolean;
|
||
targetModule?: string;
|
||
welcomeMessage?: string;
|
||
suggestedQuestions?: string[];
|
||
}
|
||
|
||
export interface Conversation {
|
||
id: string;
|
||
title: string;
|
||
agentId: string;
|
||
agent?: Agent;
|
||
projectId?: string;
|
||
messageCount: number;
|
||
lastMessage?: string;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
export interface Message {
|
||
id: string;
|
||
conversationId?: string;
|
||
role: 'user' | 'assistant';
|
||
content: string;
|
||
thinkingContent?: string;
|
||
attachments?: Attachment[];
|
||
model?: string;
|
||
tokens?: number;
|
||
isPinned?: boolean;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface Attachment {
|
||
id: string;
|
||
filename: string;
|
||
mimeType: string;
|
||
size: number;
|
||
ossUrl: string;
|
||
textExtracted: boolean;
|
||
tokenCount: number;
|
||
truncated: boolean;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface IntentSuggestion {
|
||
agentId: string;
|
||
agentName: string;
|
||
confidence: number;
|
||
prefillPrompt: string;
|
||
}
|
||
|
||
export interface SlashCommand {
|
||
key: string;
|
||
icon: string;
|
||
label: string;
|
||
description: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20>湔鰵<E6B994>亙<EFBFBD>
|
||
|
||
| <20>交<EFBFBD> | <20><>𧋦 | <20><>捆 |
|
||
|------|------|------|
|
||
| 2026-01-11 | V1.0 | <20>𥕦遣<F0A595A6>滨垢蝏<E59EA2>辣霈曇恣<E69B87><E681A3>﹝ |
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|