feat: redesign project AI agent page
This commit is contained in:
@@ -131,7 +131,7 @@ const channelListHoverMetrics = await collectMetrics()
|
||||
const channelPageChecks = []
|
||||
for (const channel of [
|
||||
{ label: '工作计划', pageClass: 'project-tasks-page', expectedHeading: '工作计划' },
|
||||
{ label: 'AI 会话', pageClass: 'project-ai-page', expectedHeading: 'AI 会话' },
|
||||
{ label: 'AI智能体', pageClass: 'project-ai-page', expectedHeading: 'AI智能体' },
|
||||
{ label: '笔记资料', pageClass: 'project-notes-page', expectedHeading: '笔记资料' },
|
||||
{ label: '计划任务', pageClass: 'project-cron-page', expectedHeading: '计划任务' },
|
||||
{ label: '新建频道', pageClass: 'project-new-channel-page', expectedHeading: '新建频道' },
|
||||
|
||||
@@ -1388,6 +1388,174 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.agent-chat-shell {
|
||||
min-height: calc(100vh - 170px);
|
||||
display: grid;
|
||||
grid-template-columns: 280px minmax(0, 1fr);
|
||||
gap: 0;
|
||||
margin: -12px -16px -16px;
|
||||
background: var(--senlin-panel);
|
||||
}
|
||||
|
||||
.agent-session-list.queue-section.arco-card,
|
||||
.agent-chat-panel.queue-section.arco-card {
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.agent-session-list.queue-section.arco-card {
|
||||
border-left: 0;
|
||||
background: color-mix(in srgb, var(--senlin-bg) 72%, var(--senlin-panel));
|
||||
}
|
||||
|
||||
.agent-session-list.queue-section .arco-card-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
.agent-new-chat.arco-btn {
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--senlin-border);
|
||||
border-radius: 999px;
|
||||
background: var(--senlin-panel);
|
||||
color: var(--senlin-text);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 14px rgba(29, 33, 41, 0.08);
|
||||
}
|
||||
|
||||
.agent-new-chat .arco-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.agent-session-groups {
|
||||
display: grid;
|
||||
gap: 28px;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.agent-session-groups:hover {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.agent-session-group {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-session-group > .arco-typography {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.agent-session {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--senlin-text);
|
||||
padding: 5px 0;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.agent-session:hover,
|
||||
.agent-session.active {
|
||||
color: var(--senlin-primary);
|
||||
}
|
||||
|
||||
.agent-chat-panel.queue-section .arco-card-body {
|
||||
position: relative;
|
||||
min-height: calc(100vh - 170px);
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.agent-chat-main {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 80px 32px 190px;
|
||||
}
|
||||
|
||||
.agent-chat-main h2 {
|
||||
margin: 0;
|
||||
color: var(--senlin-text);
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.agent-composer {
|
||||
width: min(860px, calc(100% - 80px));
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
justify-self: center;
|
||||
margin: 0 0 28px;
|
||||
border: 1px solid var(--senlin-border);
|
||||
border-radius: 24px;
|
||||
background: var(--senlin-panel);
|
||||
padding: 18px 16px 14px;
|
||||
box-shadow: 0 16px 40px rgba(29, 33, 41, 0.08);
|
||||
}
|
||||
|
||||
.agent-composer .arco-textarea-wrapper {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.agent-composer textarea {
|
||||
color: var(--senlin-text);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.agent-composer textarea::placeholder {
|
||||
color: #a9b0bc;
|
||||
}
|
||||
|
||||
.agent-composer-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-model-chip.arco-btn {
|
||||
height: 36px;
|
||||
border-color: color-mix(in srgb, var(--senlin-primary) 32%, var(--senlin-border));
|
||||
border-radius: 999px;
|
||||
background: var(--senlin-soft-blue);
|
||||
color: var(--senlin-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-send-button.arco-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: #aebeff;
|
||||
}
|
||||
|
||||
.theme-dark .agent-session-list.queue-section.arco-card {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
.theme-dark .agent-composer,
|
||||
.theme-dark .agent-new-chat.arco-btn {
|
||||
box-shadow: 0 16px 40px rgb(0 0 0 / 22%);
|
||||
}
|
||||
|
||||
.reply-box,
|
||||
.chat-input {
|
||||
display: grid;
|
||||
|
||||
@@ -95,7 +95,7 @@ function channelLabel(channel: BackendChannel) {
|
||||
case 'tasks':
|
||||
return '工作计划'
|
||||
case 'ai_sessions':
|
||||
return 'AI 会话'
|
||||
return 'AI智能体'
|
||||
case 'notes_sources':
|
||||
return '笔记资料'
|
||||
case 'cron':
|
||||
|
||||
@@ -1,70 +1,87 @@
|
||||
import { Avatar, Button, Card, Input, Space, Tag, Typography } from '@arco-design/web-react'
|
||||
import { IconPlus, IconRobot, IconSend } from '@arco-design/web-react/icon'
|
||||
import type { ProjectWorkspace } from './project-types'
|
||||
import { Button, Card, Input, Space, Typography } from '@arco-design/web-react'
|
||||
import {
|
||||
IconArrowUp,
|
||||
IconAttachment,
|
||||
IconPlusCircle,
|
||||
IconRobot,
|
||||
} from '@arco-design/web-react/icon'
|
||||
import type { AISession, ProjectWorkspace } from './project-types'
|
||||
|
||||
const { Title, Text, Paragraph } = Typography
|
||||
|
||||
const messages = [
|
||||
{ role: 'user', text: '请基于当前项目资料,整理本周风险和推进建议。' },
|
||||
{ role: 'assistant', text: '我建议优先处理导出问题、权限边界和 Q3 策略评审,并把客户反馈同步到工作计划。' },
|
||||
{ role: 'user', text: '把建议拆成可执行任务。' },
|
||||
]
|
||||
const { Title, Text } = Typography
|
||||
|
||||
export function ProjectAi({ activeWorkspace, onSelectItem }: { activeWorkspace: ProjectWorkspace; onSelectItem: (title: string) => void }) {
|
||||
const { aiSessions, project } = activeWorkspace
|
||||
const selected = aiSessions[0]
|
||||
const groupedSessions = groupSessions(aiSessions)
|
||||
|
||||
return (
|
||||
<div className="project-channel-page project-ai-page overview-page">
|
||||
<div className="overview-head">
|
||||
<div>
|
||||
<Title heading={4}>AI 会话</Title>
|
||||
<Text type="secondary">{project.name} 的项目内 AI session,左侧会话列表,右侧对话详情。</Text>
|
||||
<Title heading={4}>AI智能体</Title>
|
||||
<Text type="secondary">{project.name} 的项目内智能体工作台,选择专家后开始上下文对话。</Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<IconPlus />}>新建会话</Button>
|
||||
</div>
|
||||
|
||||
<div className="ai-workspace">
|
||||
<Card className="ai-session-list queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>Session</Title>
|
||||
<Tag color="purple">{aiSessions.length}</Tag>
|
||||
<div className="agent-chat-shell">
|
||||
<Card className="agent-session-list queue-section" bordered>
|
||||
<Button className="agent-new-chat" icon={<IconPlusCircle />}>
|
||||
开启新对话
|
||||
</Button>
|
||||
|
||||
<div className="agent-session-groups">
|
||||
{groupedSessions.length ? (
|
||||
groupedSessions.map((group) => (
|
||||
<section className="agent-session-group" key={group.label}>
|
||||
<Text type="secondary">{group.label}</Text>
|
||||
{group.items.map((session, index) => (
|
||||
<button
|
||||
className={index === 0 && group.label === groupedSessions[0]?.label ? 'agent-session active' : 'agent-session'}
|
||||
key={session.id}
|
||||
onClick={() => onSelectItem(session.title)}
|
||||
>
|
||||
{session.title}
|
||||
</button>
|
||||
))}
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">暂无对话</Text>
|
||||
)}
|
||||
</div>
|
||||
{aiSessions.map((session, index) => (
|
||||
<button
|
||||
className={index === 0 ? 'ai-session active' : 'ai-session'}
|
||||
key={session.title}
|
||||
onClick={() => onSelectItem(session.title)}
|
||||
>
|
||||
<Text>{session.title}</Text>
|
||||
<Text type="secondary">{session.summary}</Text>
|
||||
<time>{session.time}</time>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
|
||||
<Card className="ai-chat-panel queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>{selected?.title ?? '暂无会话'}</Title>
|
||||
<Tag color="green">队列空闲</Tag>
|
||||
<Card className="agent-chat-panel queue-section" bordered>
|
||||
<div className="agent-chat-main">
|
||||
<Title heading={2}>选择专家,开始对话</Title>
|
||||
</div>
|
||||
<div className="chat-thread">
|
||||
{messages.map((message, index) => (
|
||||
<div className={`chat-message ${message.role}`} key={`${message.role}-${index}`}>
|
||||
<Avatar size={30}>{message.role === 'assistant' ? <IconRobot /> : '张'}</Avatar>
|
||||
<Paragraph>{message.text}</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="chat-input">
|
||||
<Input.TextArea placeholder="输入你的问题,结合项目上下文继续追问" autoSize={{ minRows: 3, maxRows: 4 }} />
|
||||
<Space>
|
||||
<Button>引用资料</Button>
|
||||
<Button type="primary" icon={<IconSend />}>发送</Button>
|
||||
</Space>
|
||||
<div className="agent-composer">
|
||||
<Input.TextArea placeholder="给 DeepSeek 发送消息" autoSize={{ minRows: 3, maxRows: 6 }} />
|
||||
<div className="agent-composer-footer">
|
||||
<Button className="agent-model-chip" icon={<IconRobot />}>
|
||||
DeepSeek V4.0 Flash
|
||||
</Button>
|
||||
<Space>
|
||||
<Button type="text" icon={<IconAttachment />} />
|
||||
<Button className="agent-send-button" type="primary" shape="circle" icon={<IconArrowUp />} />
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function groupSessions(sessions: AISession[]) {
|
||||
const labels = ['今天', '昨天', '7 天内']
|
||||
return labels.map((label) => ({
|
||||
label,
|
||||
items: sessions.filter((session, index) => session.time.includes(label) || (!labels.some((item) => session.time.includes(item)) && labels.indexOf(label) === fallbackGroupIndex(index))),
|
||||
})).filter((group) => group.items.length > 0)
|
||||
}
|
||||
|
||||
function fallbackGroupIndex(index: number) {
|
||||
if (index === 0) return 0
|
||||
if (index === 1) return 1
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export function ProjectNewChannel({ activeWorkspace }: { activeWorkspace: Projec
|
||||
<Tag color="arcoblue">外部资料库</Tag>
|
||||
<Tag color="green">项目流程页</Tag>
|
||||
<Tag color="orange">RSS/探索结果</Tag>
|
||||
<Tag color="purple">AI 会话上下文</Tag>
|
||||
<Tag color="purple">AI智能体上下文</Tag>
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@ export function WorkspacePage({
|
||||
const workspaceMetrics = [
|
||||
{ title: '项目总数', value: projects.length, delta: `${urgentProjects} 个高优先级`, icon: <IconDashboard />, color: 'arcoblue' },
|
||||
{ title: '未完成计划', value: unfinishedTasks.length, delta: '跨项目聚合', icon: <IconCheckCircleFill />, color: 'green' },
|
||||
{ title: 'AI 会话', value: aiSessions.length, delta: '项目内上下文', icon: <IconRobot />, color: 'purple' },
|
||||
{ title: 'AI智能体', value: aiSessions.length, delta: '项目内上下文', icon: <IconRobot />, color: 'purple' },
|
||||
{ title: '知识资料', value: notes.length, delta: '笔记与资料合计', icon: <IconFile />, color: 'cyan' },
|
||||
]
|
||||
|
||||
@@ -89,7 +89,7 @@ export function WorkspacePage({
|
||||
<div className="overview-head">
|
||||
<div>
|
||||
<Title heading={4}>工作台</Title>
|
||||
<Text type="secondary">所有项目的统一工作台,汇总项目任务、AI 会话和知识资产。</Text>
|
||||
<Text type="secondary">所有项目的统一工作台,汇总项目任务、AI智能体和知识资产。</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user