Merge remote-tracking branch 'origin/master' into ji
This commit is contained in:
commit
5daa4b3e20
62 files changed
+4230
-1025
No files matched your search
@@ -122,7 +122,7 @@ export default function LightAppNoteListPage() {
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-base font-medium">新建笔记本</span>
|
||||
<span className="text-base font-medium">新建空间</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export default function LightAppNoteListPage() {
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-base font-medium">新建笔记本</span>
|
||||
<span className="text-base font-medium">新建空间</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -230,7 +230,7 @@ export default function LightAppNoteListPage() {
|
||||
isDark ? "text-slate-100" : "text-slate-950",
|
||||
)}
|
||||
>
|
||||
我的笔记本
|
||||
我的空间
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
|
||||
import { getNotebookApiBaseURL } from '@/core/config';
|
||||
|
||||
interface NotebookTokenData {
|
||||
doc_ids: number[];
|
||||
search_space_id: number;
|
||||
user_id: string;
|
||||
user_token: string;
|
||||
}
|
||||
|
||||
export default function NotebookAIWritingPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get('token');
|
||||
|
||||
const [data, setData] = useState<NotebookTokenData | null>(null);
|
||||
const [spaceName, setSpaceName] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setError('缺少 token 参数');
|
||||
return;
|
||||
}
|
||||
const base = getNotebookApiBaseURL();
|
||||
fetch(`${base}/api/v1/ai-writing/notebook-token/${token}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`请求失败 ${res.status}`);
|
||||
return res.json() as Promise<NotebookTokenData>;
|
||||
})
|
||||
.then((d) => {
|
||||
setData(d);
|
||||
return fetch(`${base}/api/v1/searchspaces/${d.search_space_id}`, {
|
||||
headers: { Authorization: `Bearer ${d.user_token}` },
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) return null;
|
||||
return res.json() as Promise<{ name: string }>;
|
||||
})
|
||||
.then((space) => {
|
||||
if (space?.name) setSpaceName(space.name);
|
||||
})
|
||||
.catch((e: Error) => setError(e.message));
|
||||
}, [token]);
|
||||
|
||||
// Keep-alive:每 50 秒 ping 一次 token 接口,防止 Redis TTL 过期
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
const base = getNotebookApiBaseURL();
|
||||
const id = setInterval(() => {
|
||||
fetch(`${base}/api/v1/ai-writing/notebook-token/${token}`).catch(() => {});
|
||||
}, 50_000);
|
||||
return () => clearInterval(id);
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden">
|
||||
{/* 索财笔记 Header —— 只承载空间信息(品牌 / 右侧标签已移除) */}
|
||||
<div className="relative flex items-center gap-3 px-5 h-[52px] shrink-0 bg-gradient-to-r from-[#1e3a5f] to-[#1a3255] border-b border-[#2a4a70]">
|
||||
{/* 顶部极细品牌高光线 */}
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-[#7fb3e8]/55 to-transparent" />
|
||||
|
||||
{/* 空间信息:精致玻璃胶囊 */}
|
||||
{data?.search_space_id && (
|
||||
<div
|
||||
className="inline-flex h-7 items-center gap-2 rounded-full border border-[#7fb3e8]/30 bg-gradient-to-b from-[#2d5080]/55 to-[#1a3a60]/55 pl-2 pr-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.06),0_1px_2px_rgba(0,0,0,0.18)] backdrop-blur-sm transition-all hover:border-[#7fb3e8]/55 hover:-translate-y-px hover:shadow-[inset_0_1px_0_rgba(255,255,255,0.08),0_2px_10px_-2px_rgba(59,130,246,0.45)]"
|
||||
title={`当前空间:${spaceName ?? data.search_space_id}`}
|
||||
>
|
||||
{/* 图标徽标 */}
|
||||
<span className="flex size-[18px] items-center justify-center rounded-[5px] bg-gradient-to-br from-[#3b82f6] to-[#1d4ed8] shadow-[0_0_0_1px_rgba(127,179,232,0.25),0_2px_6px_-1px_rgba(29,78,216,0.6)]">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="size-[11px] text-[#e0f0ff]"
|
||||
>
|
||||
<polygon points="12 2 2 7 12 12 22 7 12 2" />
|
||||
<polyline points="2 17 12 22 22 17" />
|
||||
<polyline points="2 12 12 17 22 12" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-[11px] font-medium tracking-wide text-[#7fb3e8]">空间</span>
|
||||
<span className="h-3 w-px bg-[#7fb3e8]/25" />
|
||||
<span className="max-w-[200px] truncate text-[12.5px] font-semibold text-[#d6e8fb]">
|
||||
{spaceName ?? data.search_space_id}
|
||||
</span>
|
||||
{/* 已连接状态点 */}
|
||||
<span className="size-1.5 shrink-0 rounded-full bg-[#4ade80] shadow-[0_0_0_2px_rgba(74,222,128,0.18)]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 弹性占位 */}
|
||||
<div className="flex-1" />
|
||||
</div>
|
||||
|
||||
{/* 主体 */}
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
{error ? (
|
||||
<div className="flex items-center justify-center h-full text-red-400 text-sm">{error}</div>
|
||||
) : !data ? (
|
||||
<div className="flex items-center justify-center h-full text-[#7fb3e8] text-sm">加载中…</div>
|
||||
) : (
|
||||
<AIWritingPage
|
||||
hideAdvancedFields
|
||||
initialNotebookId={data.search_space_id}
|
||||
initialDocumentIds={data.doc_ids}
|
||||
studioToken={data.user_token}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import NotebookAIWritingPage from './NotebookAIWritingPage';
|
||||
|
||||
export default function NotebookRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="ai-writing" element={<NotebookAIWritingPage />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import StrategyRoutes from "./StrategyRoutes";
|
||||
import Header from "@/strategy-components/Header";
|
||||
import OpenCanvasRoutes from "@/open-canvas/routes/OpenCanvasRoutes";
|
||||
import RoundtableRoutes from "./RoundtableRoutes";
|
||||
import NotebookRoutes from "./NotebookRoutes";
|
||||
|
||||
function PageLayout() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
@@ -68,6 +69,7 @@ function PageLayout() {
|
||||
<Route path="strategy/*" element={<StrategyRoutes />} />
|
||||
<Route path="canvas/*" element={<OpenCanvasRoutes />} />
|
||||
<Route path="roundtable/*" element={<RoundtableRoutes />} />
|
||||
<Route path="notebook/*" element={<NotebookRoutes />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -180,18 +180,18 @@ export default function StudioNotebooksPage() {
|
||||
<div className="min-h-full bg-background p-8">
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold text-foreground">笔记本</h1>
|
||||
<h1 className="text-2xl font-semibold text-foreground">空间</h1>
|
||||
<button
|
||||
onClick={() => { setCreateOpen(true); setCreateError(""); setCreateName(""); }}
|
||||
className="flex items-center gap-1.5 rounded-full bg-primary px-5 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
新建笔记本
|
||||
新建空间
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isError && (
|
||||
<p className="text-sm text-muted-foreground">加载笔记本失败,请稍后重试。</p>
|
||||
<p className="text-sm text-muted-foreground">加载空间失败,请稍后重试。</p>
|
||||
)}
|
||||
|
||||
{/* Grid */}
|
||||
@@ -199,20 +199,20 @@ export default function StudioNotebooksPage() {
|
||||
{isLoading
|
||||
? Array.from({ length: 8 }).map((_, i) => <NotebookCardSkeleton key={i} />)
|
||||
: notebooks.map((nb) => (
|
||||
<NotebookCard
|
||||
key={nb.id}
|
||||
nb={nb}
|
||||
onRename={(nb) => { setRenameTarget(nb); setRenameName(nb.name); setRenameError(""); }}
|
||||
onDelete={(nb) => { setDeleteTarget(nb); setDeleteError(""); }}
|
||||
/>
|
||||
))}
|
||||
<NotebookCard
|
||||
key={nb.id}
|
||||
nb={nb}
|
||||
onRename={(nb) => { setRenameTarget(nb); setRenameName(nb.name); setRenameError(""); }}
|
||||
onDelete={(nb) => { setDeleteTarget(nb); setDeleteError(""); }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── 新建 Dialog ── */}
|
||||
<Dialog open={createOpen} onOpenChange={(v) => !creating && setCreateOpen(v)}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新建笔记本</DialogTitle>
|
||||
<DialogTitle>新建空间</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 pt-2">
|
||||
<input
|
||||
@@ -220,7 +220,7 @@ export default function StudioNotebooksPage() {
|
||||
value={createName}
|
||||
onChange={(e) => setCreateName(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") void handleCreate(); }}
|
||||
placeholder="笔记本名称"
|
||||
placeholder="空间名称"
|
||||
disabled={creating}
|
||||
className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary disabled:opacity-50"
|
||||
/>
|
||||
@@ -239,7 +239,7 @@ export default function StudioNotebooksPage() {
|
||||
<Dialog open={!!renameTarget} onOpenChange={(v) => !renaming && !v && setRenameTarget(null)}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>重命名笔记本</DialogTitle>
|
||||
<DialogTitle>重命名空间</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 pt-2">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -272,12 +272,12 @@ export default function StudioNotebooksPage() {
|
||||
<Dialog open={!!deleteTarget} onOpenChange={(v) => !deleting && !v && setDeleteTarget(null)}>
|
||||
<DialogContent className="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>删除笔记本</DialogTitle>
|
||||
<DialogTitle>删除空间</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 pt-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
确定要删除{deleteTarget ? <span className="font-medium text-foreground">「{deleteTarget.name}」</span> : ""}吗?
|
||||
删除后笔记本内所有来源数据将一并移除,且无法恢复。
|
||||
删除后空间内所有来源数据将一并移除,且无法恢复。
|
||||
</p>
|
||||
{deleteError && <p className="text-xs text-destructive">{deleteError}</p>}
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
Reference in new issue
Block a user