refactor: merge agent workbench into chat
This commit is contained in:
+2
-5
@@ -18,7 +18,7 @@ import {
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { invoke } from '@/lib/invoke';
|
||||
import { useConversationStore, useProviderStore, useSkillStore, useUIStore } from '@/stores';
|
||||
import { useConversationStore, useProviderStore, useSkillStore } from '@/stores';
|
||||
import type { AttachmentInput, Model } from '@/types';
|
||||
|
||||
type CreationMode = 'agent' | 'image' | 'video' | 'music' | 'voice' | 'digital-human' | 'action';
|
||||
@@ -120,7 +120,7 @@ function toAttachmentInput(item: AttachmentItem): AttachmentInput {
|
||||
};
|
||||
}
|
||||
|
||||
export function AgentWorkbenchPage() {
|
||||
export function AgentWorkbenchComposer() {
|
||||
const { token } = theme.useToken();
|
||||
const { message } = App.useApp();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -133,7 +133,6 @@ export function AgentWorkbenchPage() {
|
||||
const loadSkills = useSkillStore((s) => s.loadSkills);
|
||||
const installSkill = useSkillStore((s) => s.installSkill);
|
||||
const toggleSkill = useSkillStore((s) => s.toggleSkill);
|
||||
const setActivePage = useUIStore((s) => s.setActivePage);
|
||||
const [creationMode, setCreationMode] = useState<CreationMode>('agent');
|
||||
const [autoMode, setAutoMode] = useState(true);
|
||||
const [prompt, setPrompt] = useState('');
|
||||
@@ -292,7 +291,6 @@ export function AgentWorkbenchPage() {
|
||||
setResult(`Agent 已开始执行。模型:${modelId};参考素材:${attachments.length} 个。`);
|
||||
setPrompt('');
|
||||
setAttachments([]);
|
||||
setActivePage('chat');
|
||||
} catch (error) {
|
||||
message.error(String(error));
|
||||
setResult(`Agent 启动失败:${String(error)}`);
|
||||
@@ -312,7 +310,6 @@ export function AgentWorkbenchPage() {
|
||||
selectedChatModel,
|
||||
selectedModel,
|
||||
sendAgentMessage,
|
||||
setActivePage,
|
||||
submitting,
|
||||
updateConversation,
|
||||
]);
|
||||
@@ -2,16 +2,13 @@ import React, { useMemo, useCallback, useRef, useState, useEffect, useSyncExtern
|
||||
import { CloseCircleFilled, SyncOutlined } from '@ant-design/icons';
|
||||
import { Typography, Button, Dropdown, Input, App, Avatar, Alert, Popconfirm, Popover, theme, Tag, Image, Tooltip, Modal, Spin } from 'antd';
|
||||
import type { InputRef } from 'antd';
|
||||
import { Pencil, Share2, FileImage, FileCode, FileText, FileType, Bot, Brain, Lightbulb, Code, Languages, Copy, Check, RotateCcw, User, Trash2, ChevronLeft, ChevronRight, ChevronDown, Scissors, Paperclip, AlertCircle, X, ArrowDown, ArrowUp, ArrowLeftRight, Zap, Sparkles, TextCursorInput, GitBranch, ChartNoAxesColumn, MessageSquare, ArrowUpRight, ArrowDownRight, Coins, Clock, Timer, Download, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
||||
import { Pencil, Share2, FileImage, FileCode, FileText, FileType, Bot, Brain, Code, Copy, Check, RotateCcw, User, Trash2, ChevronLeft, ChevronRight, ChevronDown, Scissors, Paperclip, AlertCircle, X, ArrowDown, ArrowUp, ArrowLeftRight, Zap, Sparkles, TextCursorInput, GitBranch, ChartNoAxesColumn, MessageSquare, ArrowUpRight, ArrowDownRight, Coins, Clock, Timer, Download, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
||||
import { ModelIcon } from '@lobehub/icons';
|
||||
import { getConvIcon } from '@/lib/convIcon';
|
||||
import { normalizeAutoConversationTitle } from '@/lib/conversationTitle';
|
||||
import Bubble from '@ant-design/x/es/bubble';
|
||||
import Prompts from '@ant-design/x/es/prompts';
|
||||
import Actions from '@ant-design/x/es/actions';
|
||||
import Think from '@ant-design/x/es/think';
|
||||
import type { BubbleItemType, BubbleListRef, RoleType } from '@ant-design/x/es/bubble/interface';
|
||||
import type { PromptsItemType } from '@ant-design/x/es/prompts';
|
||||
import NodeRenderer, { setCustomComponents, withMarkstreamComponentDisplay, type NodeComponentProps, type CodeBlockActionContext, type CodeBlockPreviewPayload, type MermaidBlockActionContext, type InfographicBlockActionContext } from 'markstream-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CodeBlockHeaderActions } from './CodeBlockHeaderActions';
|
||||
@@ -33,6 +30,7 @@ import { useUserProfileStore } from '@/stores/userProfileStore';
|
||||
import { useResolvedDarkMode } from '@/hooks/useResolvedDarkMode';
|
||||
import { InputArea } from './InputArea';
|
||||
import { ModelSelector } from './ModelSelector';
|
||||
import { AgentWorkbenchComposer } from './AgentWorkbenchComposer';
|
||||
import { parseSearchContent } from '@/lib/searchUtils';
|
||||
import {
|
||||
CHAT_CUSTOM_HTML_TAGS,
|
||||
@@ -2111,7 +2109,6 @@ export function ChatView() {
|
||||
const [mermaidPreviewSvg, setMermaidPreviewSvg] = useState<string | null>(null);
|
||||
const [mermaidPreviewOpen, setMermaidPreviewOpen] = useState(false);
|
||||
const [streamStatusNow, setStreamStatusNow] = useState(() => Date.now());
|
||||
const createConversation = useConversationStore((s) => s.createConversation);
|
||||
const providers = useProviderStore((s) => s.providers);
|
||||
const settings = useSettingsStore((s) => s.settings);
|
||||
const saveSettings = useSettingsStore((s) => s.saveSettings);
|
||||
@@ -2780,62 +2777,6 @@ export function ChatView() {
|
||||
[messages, activeConversation, t, messageApi],
|
||||
);
|
||||
|
||||
// ── Welcome prompt items ───────────────────────────────────────────
|
||||
const greetingText = useMemo(() => {
|
||||
const hour = new Date().getHours();
|
||||
let key: string;
|
||||
if (hour >= 5 && hour < 12) key = 'chat.greetingMorning';
|
||||
else if (hour >= 12 && hour < 14) key = 'chat.greetingNoon';
|
||||
else if (hour >= 14 && hour < 18) key = 'chat.greetingAfternoon';
|
||||
else key = 'chat.greetingEvening';
|
||||
return `👋 ${t(key)}`;
|
||||
}, [t]);
|
||||
|
||||
const promptItems: PromptsItemType[] = useMemo(
|
||||
() => [
|
||||
{ key: '1', icon: <Lightbulb size={16} />, label: t('chat.welcomePrompt1') },
|
||||
{ key: '2', icon: <Languages size={16} />, label: t('chat.welcomePrompt2') },
|
||||
{ key: '3', icon: <Code size={16} />, label: t('chat.welcomePrompt3') },
|
||||
{ key: '4', icon: <Lightbulb size={16} />, label: t('chat.welcomePrompt4') },
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
const handlePromptClick = useCallback(
|
||||
async (info: { data: PromptsItemType }) => {
|
||||
const text = typeof info.data.label === 'string' ? info.data.label : '';
|
||||
if (!text) return;
|
||||
|
||||
try {
|
||||
if (!activeConversationId) {
|
||||
// Prefer settings default model, fall back to first enabled
|
||||
let provider = settings.default_provider_id
|
||||
? providers.find((p) => p.id === settings.default_provider_id && p.enabled)
|
||||
: undefined;
|
||||
let model = provider?.models.find(
|
||||
(m) => m.model_id === settings.default_model_id && m.enabled,
|
||||
);
|
||||
if (!provider || !model) {
|
||||
provider = providers.find((p) => p.enabled && p.models.some((m) => m.enabled));
|
||||
model = provider?.models.find((m) => m.enabled);
|
||||
}
|
||||
if (!provider || !model) {
|
||||
messageApi.warning(t('chat.noModel'));
|
||||
return;
|
||||
}
|
||||
await createConversation(normalizeAutoConversationTitle(text), model.model_id, provider.id);
|
||||
}
|
||||
|
||||
// Route through InputArea's send pipeline so companion models are respected
|
||||
useConversationStore.getState().setPendingPromptText(text);
|
||||
} catch (e) {
|
||||
console.error('[handlePromptClick] error:', e);
|
||||
messageApi.error(String(e));
|
||||
}
|
||||
},
|
||||
[activeConversationId, providers, settings, createConversation, messageApi, t],
|
||||
);
|
||||
|
||||
// ── Bubble items (only show active messages) ────────────────────────
|
||||
const activeMessages = useMemo(
|
||||
() => messages.filter((msg) => msg.is_active !== false),
|
||||
@@ -4201,15 +4142,7 @@ export function ChatView() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full" style={{ padding: '0 24px' }}>
|
||||
<Typography.Title level={3} style={{ marginBottom: 24, fontWeight: 500 }}>
|
||||
{greetingText}
|
||||
</Typography.Title>
|
||||
<Prompts
|
||||
items={promptItems}
|
||||
onItemClick={handlePromptClick}
|
||||
wrap
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
<AgentWorkbenchComposer />
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
@@ -4254,6 +4187,7 @@ export function ChatView() {
|
||||
)}
|
||||
|
||||
{/* Input Area */}
|
||||
{messages.length > 0 && (
|
||||
<div className="relative">
|
||||
{showScrollToBottom && (
|
||||
<Button
|
||||
@@ -4275,6 +4209,7 @@ export function ChatView() {
|
||||
)}
|
||||
<InputArea />
|
||||
</div>
|
||||
)}
|
||||
<Modal
|
||||
title={t('chat.compressionSummary')}
|
||||
open={summaryModalOpen}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { PageKey } from '@/types';
|
||||
import { AgentWorkbenchPage } from '@/pages/AgentWorkbenchPage';
|
||||
import { ChatPage } from '@/pages/ChatPage';
|
||||
import { DrawingPage } from '@/pages/DrawingPage';
|
||||
import { KnowledgePage } from '@/pages/KnowledgePage';
|
||||
@@ -17,8 +16,6 @@ export function ContentArea({ activePage }: ContentAreaProps) {
|
||||
switch (activePage) {
|
||||
case 'chat':
|
||||
return <ChatPage />;
|
||||
case 'agent-workbench':
|
||||
return <AgentWorkbenchPage />;
|
||||
case 'drawing':
|
||||
return <DrawingPage />;
|
||||
case 'knowledge':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Tooltip, Avatar, theme } from 'antd';
|
||||
import { MessageSquare, BookOpen, Brain, Router, FolderOpen, User, Sparkles, ImagePlus, WandSparkles } from 'lucide-react';
|
||||
import { MessageSquare, BookOpen, Brain, Router, FolderOpen, User, Sparkles, ImagePlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUIStore, useSettingsStore } from '@/stores';
|
||||
import { useUserProfileStore } from '@/stores/userProfileStore';
|
||||
@@ -12,7 +12,6 @@ import type { PageKey } from '@/types';
|
||||
|
||||
const mainNavItems: { key: PageKey; icon: React.ReactNode; labelKey: string }[] = [
|
||||
{ key: 'chat', icon: <MessageSquare size={18} />, labelKey: 'nav.chat' },
|
||||
{ key: 'agent-workbench', icon: <WandSparkles size={18} />, labelKey: 'nav.agentWorkbench' },
|
||||
{ key: 'drawing', icon: <ImagePlus size={18} />, labelKey: 'nav.drawing' },
|
||||
{ key: 'skills', icon: <Sparkles size={18} />, labelKey: 'nav.skills' },
|
||||
{ key: 'knowledge', icon: <BookOpen size={18} />, labelKey: 'nav.knowledge' },
|
||||
|
||||
+13
-19
@@ -31,25 +31,19 @@ body {
|
||||
|
||||
.agent-workbench-desktop {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 1380px;
|
||||
overflow: visible;
|
||||
color: var(--color-text);
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.agent-workbench-desktop-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(247, 249, 251, 0.96), rgba(248, 250, 252, 0.98)),
|
||||
radial-gradient(circle at 18% 18%, rgba(22, 119, 255, 0.08), transparent 28%),
|
||||
radial-gradient(circle at 82% 78%, rgba(20, 184, 166, 0.08), transparent 30%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .agent-workbench-desktop-bg {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(12, 18, 24, 0.98), rgba(14, 18, 22, 0.98)),
|
||||
radial-gradient(circle at 18% 18%, rgba(22, 119, 255, 0.12), transparent 28%),
|
||||
radial-gradient(circle at 82% 78%, rgba(20, 184, 166, 0.1), transparent 30%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.agent-workbench-desktop-hero {
|
||||
@@ -57,15 +51,15 @@ html[data-theme="dark"] .agent-workbench-desktop-bg {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
gap: 54px;
|
||||
height: 100%;
|
||||
padding: 48px 24px;
|
||||
gap: 34px;
|
||||
width: 100%;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.agent-workbench-desktop-hero h1 {
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
font-size: clamp(28px, 4vw, 42px);
|
||||
font-size: 32px;
|
||||
font-weight: 780;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@@ -76,13 +70,13 @@ html[data-theme="dark"] .agent-workbench-desktop-bg {
|
||||
grid-template-columns: 96px minmax(0, 1fr);
|
||||
grid-template-rows: minmax(118px, auto) auto auto;
|
||||
gap: 10px 18px;
|
||||
width: min(78vw, 1280px);
|
||||
width: min(72vw, 1180px);
|
||||
min-height: 232px;
|
||||
padding: 24px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 86%, transparent);
|
||||
border-radius: 26px;
|
||||
border-radius: 22px;
|
||||
background: color-mix(in srgb, var(--color-bg-elevated) 94%, transparent);
|
||||
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.1);
|
||||
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.agent-workbench-file-input {
|
||||
|
||||
@@ -658,7 +658,6 @@ export interface RealtimeConfig {
|
||||
// === UI State ===
|
||||
export type PageKey =
|
||||
| 'chat'
|
||||
| 'agent-workbench'
|
||||
| 'drawing'
|
||||
| 'knowledge'
|
||||
| 'memory'
|
||||
|
||||
Reference in New Issue
Block a user