diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index a95698d..1be79c7 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -241,6 +241,26 @@ padding: 0; } + /* Resource list pages use a tighter content band below the shared topbar. */ + .app-content:has([data-app-content="list"]) { + padding-top: 1rem; + padding-bottom: 1.5rem; + } + + @media (min-width: 640px) { + .app-content:has([data-app-content="list"]) { + padding-top: 1.25rem; + padding-bottom: 2rem; + } + } + + @media (min-width: 1024px) { + .app-content:has([data-app-content="list"]) { + padding-top: 1.5rem; + padding-bottom: 2.5rem; + } + } + .scrollbar-subtle { scrollbar-width: thin; scrollbar-color: color-mix( diff --git a/frontend/src/components/assistant-editor/editor-controls.tsx b/frontend/src/components/assistant-editor/editor-controls.tsx index 5d645d6..563e2ce 100644 --- a/frontend/src/components/assistant-editor/editor-controls.tsx +++ b/frontend/src/components/assistant-editor/editor-controls.tsx @@ -50,14 +50,20 @@ import type { import type { RuntimeMode } from "./types"; -export function EditorBackButton({ onClick }: { onClick: () => void }) { +export function EditorBackButton({ + onClick, + ariaLabel = "返回助手列表", +}: { + onClick: () => void; + ariaLabel?: string; +}) { return ( diff --git a/frontend/src/components/layout/list-page-layout.tsx b/frontend/src/components/layout/list-page-layout.tsx new file mode 100644 index 0000000..8d2afbb --- /dev/null +++ b/frontend/src/components/layout/list-page-layout.tsx @@ -0,0 +1,80 @@ +"use client"; + +import type { ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +import { PageTopbar, TopbarPortal } from "./topbar-portal"; + +export function ListPageLayout({ + title, + description, + action, + children, + className, +}: { + title: string; + description?: ReactNode; + action?: ReactNode; + children: ReactNode; + className?: string; +}) { + const hasHeader = Boolean(description || action); + + return ( + <> + + + + +
+ {hasHeader && ( +
+ {description && ( +

+ {description} +

+ )} + {action} +
+ )} + + {children} +
+ + ); +} + +export function ListPageSection({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/src/components/pages/AssistantPage.tsx b/frontend/src/components/pages/AssistantPage.tsx index 94e2de8..33ad705 100644 --- a/frontend/src/components/pages/AssistantPage.tsx +++ b/frontend/src/components/pages/AssistantPage.tsx @@ -33,10 +33,9 @@ import { } from "@/components/ui/dropdown-menu"; import { DataList } from "@/components/ui/data-list"; import { - PageTopbar, - TopbarPortal, -} from "@/components/layout/topbar-portal"; -import { PageHeader } from "@/components/ui/page-header"; + ListPageLayout, + ListPageSection, +} from "@/components/layout/list-page-layout"; import { FilterPills } from "@/components/ui/filter-pills"; import { SearchInput } from "@/components/ui/search-input"; import { ListToolbar } from "@/components/ui/list-toolbar"; @@ -538,9 +537,10 @@ export function AssistantPage(props: AssistantPageProps) { } // 删除助手 - async function handleDelete(id: string) { + async function handleDelete(assistant: AssistantListItem) { + if (!window.confirm(`确认删除助手“${assistant.name}”?`)) return; try { - await assistantsApi.remove(id); + await assistantsApi.remove(assistant.id); await loadAssistants(); } catch (error) { setListError(error instanceof Error ? error.message : "删除失败"); @@ -997,23 +997,17 @@ export function AssistantPage(props: AssistantPageProps) { if (view === "list") { return ( -
- - - 创建助手 - - } - /> - -
+ + + 创建助手 + + } + > + void loadAssistants()} + onRowClick={handleEdit} empty={{ title: listItems.length === 0 ? "暂无助手" : "未找到匹配的助手", description: @@ -1116,12 +1111,19 @@ export function AssistantPage(props: AssistantPageProps) { align: "right", cellClassName: "flex justify-end gap-2", cell: (assistant) => ( - <> +
event.stopPropagation()} + onPointerDown={(event) => event.stopPropagation()} + > @@ -1154,7 +1157,10 @@ export function AssistantPage(props: AssistantPageProps) { className="rounded-lg" onSelect={(event) => { event.preventDefault(); - void handleDelete(assistant.id); + window.setTimeout( + () => void handleDelete(assistant), + 0, + ); }} > @@ -1162,59 +1168,52 @@ export function AssistantPage(props: AssistantPageProps) { - +
), }, ]} /> -
-
+ + ); } if (view === "choose") { return ( - <> - - router.push("/assistants")} - > - - 返回列表 - - } - /> - + router.push("/assistants")} + > + + 返回列表 + + } + > + + + -
-

- 先为助手取个名字,再选择构建方式。确认后将立即创建助手并进入编辑页。 -

+
+
构建方式
-
- -
- -
-
构建方式
- -
+
{assistantTypeOptions.map((option) => { const selected = draftType === option.type; @@ -1264,21 +1263,19 @@ export function AssistantPage(props: AssistantPageProps) {
-
- {createError && ( - {createError} - )} - +
+ {createError && ( + {createError} + )} + -
- + ); } diff --git a/frontend/src/components/pages/ComponentsKnowledgePage.tsx b/frontend/src/components/pages/ComponentsKnowledgePage.tsx index 07f17d8..efd0078 100644 --- a/frontend/src/components/pages/ComponentsKnowledgePage.tsx +++ b/frontend/src/components/pages/ComponentsKnowledgePage.tsx @@ -22,7 +22,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { DataList } from "@/components/ui/data-list"; import { Dialog, @@ -40,7 +39,17 @@ import { } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { ListToolbar } from "@/components/ui/list-toolbar"; -import { PageHeader } from "@/components/ui/page-header"; +import { + AssistantIdentity, + EditorBackButton, + EditableTitle, +} from "@/components/assistant-editor/editor-controls"; +import { SectionCard } from "@/components/editor/section-card"; +import { + ListPageLayout, + ListPageSection, +} from "@/components/layout/list-page-layout"; +import { TopbarPortal } from "@/components/layout/topbar-portal"; import { SearchInput } from "@/components/ui/search-input"; import { Select, @@ -196,23 +205,20 @@ function KnowledgeListView() { } return ( -
- router.push("/components/knowledge/new")} - > - - 添加知识库 - - } - /> - -
+ router.push("/components/knowledge/new")} + > + + 添加知识库 + + } + > + -
-
+ + ); } @@ -425,26 +431,21 @@ function KnowledgeCreateView() { } return ( -
-
-
-

添加知识库

-

- 填写名称、说明并选择 Embedding 模型。确认后将立即创建并进入编辑页。 -

-
- + router.push("/components/knowledge")} > 返回列表 -
- + } + > {loading ? (
@@ -452,57 +453,65 @@ function KnowledgeCreateView() {
) : ( <> -
-
- - -