feat(desktop): align explore view with web reader
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Alert, Avatar, Button, Dropdown, Empty, Form, Input, Menu, Modal, Select, Spin, Switch, Tag, Typography } from '@arco-design/web-react'
|
||||
import {
|
||||
IconApps, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
||||
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRight, IconRobot,
|
||||
IconApps, IconBook, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
||||
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRefresh, IconRight, IconRobot,
|
||||
IconSun, IconUser,
|
||||
} from '@arco-design/web-react/icon'
|
||||
import {
|
||||
ApiError, createProject, fetchProjectWorkspace, fetchProjects, getCurrentUser,
|
||||
listAIExperts, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
||||
type ApiSession, type CurrentUser, type Expert, type Project, type Workspace, type WorkspaceTask,
|
||||
listAIExperts, listDatasetItems, listDatasetSources, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
||||
type ApiSession, type CurrentUser, type DatasetItem, type DatasetSource, type Expert, type Project, type Workspace, type WorkspaceTask,
|
||||
} from './api'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
@@ -113,7 +113,7 @@ export function App() {
|
||||
</section>
|
||||
|
||||
{error ? <Alert className="app-alert" type="error" content={error} closable onClose={() => setError('')} /> : null}
|
||||
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel projects={projects} onOpen={(id) => { setProjectId(id); setWorkspaceMode('workbench') }} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
||||
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel session={session} projectName={activeProject?.name ?? ''} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
||||
|
||||
{projectId && workspaceMode === 'workbench' ? <aside className="channel-rail" aria-label="项目子菜单">
|
||||
{navItems.map((item) => <button key={item.id} title={item.label} className={view === item.id ? 'active' : ''} onClick={() => { setView(item.id); if (item.id === 'ai') void loadAI() }}>{item.icon}<span>{item.label}</span></button>)}
|
||||
@@ -177,8 +177,30 @@ function AllProjectsHome({ projects, workspaces, tasks, onEditTask, onCreateProj
|
||||
return <section className="content-page all-projects-home"><div className="content-heading"><div><Title heading={4}>全部项目</Title></div><Button type="primary" icon={<IconPlus />} onClick={onCreateProject}>创建项目</Button></div><div className="stat-grid">{statItems.map((item) => <div className={`stat-card ${item.color}`} key={item.label}><strong>{item.value}</strong><span>{item.label}</span></div>)}</div><TaskBoard title="待办任务" subtitle="" tasks={tasks} onEditTask={onEditTask} showCompleted={false} /><section className="latest-files"><div className="section-label"><strong>最新文件</strong><span>{latestFiles.length}</span></div><div className="detail-list">{latestFiles.map((file) => <article className="detail-row" key={file.id}><IconFile /><div><strong>{file.name}</strong><p>{file.extension || file.mimeType || file.kind} · {formatDate(file.updatedAt)}</p></div><Tag>{file.projectName}</Tag></article>)}{!latestFiles.length && <Empty description="暂无文件" />}</div></section></section>
|
||||
}
|
||||
|
||||
function ExplorePanel({ projects, onOpen }: { projects: Project[]; onOpen: (id: string) => void }) {
|
||||
return <section className="content-page explore-page"><div className="content-heading"><div><Text type="secondary">探索</Text><Title heading={4}>项目空间</Title></div><Tag color="arcoblue">{projects.length} 个项目</Tag></div><div className="project-card-grid">{projects.map((project) => <button key={project.id} className="project-explore-card" onClick={() => onOpen(project.id)}><i style={{ background: project.background || '#165DFF' }}>{projectLabel(project)}</i><span><strong>{project.name}</strong><small>{project.description || project.identifier}</small></span><IconRight /></button>)}{!projects.length && <Empty description="还没有项目" />}</div></section>
|
||||
function ExplorePanel({ session, projectName }: { session: ApiSession; projectName: string }) {
|
||||
const [sources, setSources] = useState<DatasetSource[]>([])
|
||||
const [items, setItems] = useState<DatasetItem[]>([])
|
||||
const [sourceId, setSourceId] = useState('all')
|
||||
const [selectedId, setSelectedId] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
useEffect(() => { void refreshSources() }, [session])
|
||||
useEffect(() => { void refreshItems() }, [session, sourceId])
|
||||
|
||||
async function refreshSources() {
|
||||
setLoading(true); setError('')
|
||||
try { setSources(await listDatasetSources(session)) } catch (requestError) { setError(errorMessage(requestError)) } finally { setLoading(false) }
|
||||
}
|
||||
async function refreshItems() {
|
||||
setLoading(true); setError('')
|
||||
try { const page = await listDatasetItems(session, sourceId === 'all' ? undefined : sourceId); setItems(page.items); setSelectedId((current) => page.items.some((item) => item.id === current) ? current : page.items[0]?.id ?? '') } catch (requestError) { setError(errorMessage(requestError)) } finally { setLoading(false) }
|
||||
}
|
||||
|
||||
const allCount = sources.reduce((total, source) => total + source.itemCount, 0)
|
||||
const selected = items.find((item) => item.id === selectedId) ?? items[0]
|
||||
const selectedSource = sources.find((source) => source.id === selected?.sourceId)
|
||||
return <section className="content-page explore-page"><div className="content-heading"><div><Title heading={4}>探索</Title><Text type="secondary">集中阅读数据源内容,筛选后沉淀到项目资料。</Text></div><Button type="text" shape="circle" aria-label="刷新探索内容" loading={loading} icon={<IconRefresh />} onClick={() => { void refreshSources(); void refreshItems() }} /></div>{error ? <Alert type="error" content={error} /> : null}<div className="explore-source-strip"><button className={sourceId === 'all' ? 'active' : ''} onClick={() => setSourceId('all')}><IconCompass /><span>全部</span><small>{allCount}</small></button>{sources.map((source) => <button className={sourceId === source.id ? 'active' : ''} key={source.id} onClick={() => setSourceId(source.id)}>{source.iconUrl ? <img src={source.iconUrl} alt="" /> : <IconBook />}<span>{source.name}</span><small>{source.itemCount}</small></button>)}</div><div className="explore-reader">{items.length ? <><div className="explore-item-list">{items.map((item) => <button className={item.id === selected?.id ? 'active' : ''} key={item.id} onClick={() => setSelectedId(item.id)}><span><b>{item.title}</b><small>{item.summary || '暂无摘要'}</small></span><i>{item.starred ? '★' : formatDate(item.publishedAt || item.createdAt)}</i></button>)}</div><article className="explore-detail"><div className="explore-detail-meta"><Tag color="arcoblue">{selectedSource?.name || '全部'}</Tag><small>{formatDate(selected?.publishedAt || selected?.createdAt || '')}</small></div><Title heading={5}>{selected?.title}</Title>{selected?.imageUrl ? <img src={selected.imageUrl} alt="" /> : null}<p>{selected?.content || selected?.summary || '暂无正文'}</p><div className="explore-detail-actions"><Button type="text" icon={<IconBook />} disabled={!projectName}>沉淀到{projectName || '项目'}</Button>{selected?.url ? <Button type="text" href={selected.url} target="_blank">打开原文</Button> : null}</div></article></> : <Empty description="暂无探索内容,请先在 Web 工作台添加或同步数据源" />}</div></section>
|
||||
}
|
||||
|
||||
function DocumentPanel({ title, workspaces }: { title: string; workspaces: Workspace[] }) {
|
||||
|
||||
Reference in New Issue
Block a user