Add demo badge to all pages for technical sharing event

This commit is contained in:
Ding Shuo
2026-03-15 00:36:42 +08:00
parent cdea59af92
commit 43f7cca0c2
2 changed files with 137 additions and 11 deletions

View File

@@ -19,7 +19,15 @@ export default function Layout() {
}, [pathname]);
return (
<div className="bg-[#f7f7f9] flex h-screen w-screen overflow-hidden text-txt font-sans">
<div className="bg-[#f7f7f9] flex h-screen w-screen overflow-hidden text-txt font-sans relative">
{/* Demo Badge */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none z-50 select-none opacity-[0.08] flex flex-col items-center">
<div className="border-[8px] border-magenta px-10 py-4 transform -rotate-12 flex flex-col items-center">
<span className="text-8xl font-black text-magenta tracking-widest">DEMO ONLY</span>
<span className="text-3xl font-bold text-magenta mt-2">TECHNICAL SHARING 2026</span>
</div>
</div>
{/* 侧边栏导航 */}
<aside className="w-64 bg-white border-r border-border flex flex-col z-10 shrink-0">
<div className="h-16 flex items-center px-6 border-b border-border">
@@ -102,6 +110,12 @@ export default function Layout() {
</p>
</div>
</div>
{/* Sidebar Demo Badge */}
<div className="mx-4 my-4 p-3 border-2 border-dashed border-magenta/20 rounded-xl flex flex-col items-center justify-center text-center bg-magenta/5 rotate-1">
<p className="text-[10px] font-black text-magenta uppercase tracking-tighter opacity-70">Tech Sharing</p>
<p className="text-[13px] font-black text-magenta -mt-1 uppercase tracking-[0.1em] italic">DEMO ONLY</p>
</div>
</aside>
{/* 主内容区 */}

View File

@@ -14,10 +14,25 @@ type ChatMessage = {
};
type StreamEvent = {
type: "thought" | "tool_call" | "tool_result" | "final" | "error";
type: "thought" | "tool_call" | "tool_result" | "final" | "error" | "workspace_start" | "workspace_delta" | "workspace_end";
content: string;
step?: number;
tool_name?: string;
workspace_title?: string;
};
type WorkspaceState = {
isOpen: boolean;
title: string;
content: string;
isGenerating: boolean;
};
const WORKSPACE_INIT: WorkspaceState = {
isOpen: false,
title: "",
content: "",
isGenerating: false,
};
type HistorySession = {
@@ -34,6 +49,7 @@ type State = {
reasoning: Record<string, string>; // Map message ID to its reasoning/traces
chatting: boolean;
historySessions: HistorySession[];
workspace: WorkspaceState;
error?: string;
};
@@ -45,7 +61,11 @@ type Action =
| { type: "set-error"; v?: string }
| { type: "set-history"; sessions: HistorySession[] }
| { type: "load-session"; sessionId: string; messages: ChatMessage[] }
| { type: "reset"; sessionId: string; userId: string };
| { type: "reset"; sessionId: string; userId: string }
| { type: "ws-start"; title: string }
| { type: "ws-delta"; content: string }
| { type: "ws-end" }
| { type: "ws-toggle"; open: boolean };
/* ─── Helpers ─── */
function uid() {
@@ -129,9 +149,30 @@ function reducer(state: State, action: Action): State {
reasoning: {},
chatting: false,
historySessions: state.historySessions,
workspace: WORKSPACE_INIT,
error: undefined,
};
}
case "ws-start":
return {
...state,
workspace: { isOpen: true, title: action.title || "New Artifact", content: "", isGenerating: true },
};
case "ws-delta":
return {
...state,
workspace: { ...state.workspace, content: state.workspace.content + action.content },
};
case "ws-end":
return {
...state,
workspace: { ...state.workspace, isGenerating: false },
};
case "ws-toggle":
return {
...state,
workspace: { ...state.workspace, isOpen: action.open },
};
default:
return state;
}
@@ -212,6 +253,7 @@ export default function PlanningAgent() {
reasoning: {},
chatting: false,
historySessions: getStoredHistory(),
workspace: WORKSPACE_INIT,
});
const [input, setInput] = useState("");
@@ -285,7 +327,13 @@ export default function PlanningAgent() {
file_ids: files.map((f) => f.id),
},
(evt) => {
if (evt.type === "final") {
if (evt.type === "workspace_start") {
dispatch({ type: "ws-start", title: evt.workspace_title || "New Artifact" });
} else if (evt.type === "workspace_delta") {
dispatch({ type: "ws-delta", content: evt.content });
} else if (evt.type === "workspace_end") {
dispatch({ type: "ws-end" });
} else if (evt.type === "final") {
finalText += evt.content;
dispatch({
type: "update-msg",
@@ -367,8 +415,10 @@ export default function PlanningAgent() {
setFiles([]);
};
const wsHasContent = !state.workspace.isOpen && state.workspace.content.length > 0;
return (
<div className="h-full relative overflow-hidden">
<div className="h-full relative overflow-hidden flex">
{/* ─── Sidebar Overlay ─── */}
{isHistoryOpen && (
<div
@@ -434,8 +484,12 @@ export default function PlanningAgent() {
</div>
{/* ─── Main Chat ─── */}
<div className="h-full flex flex-col min-w-0">
<div className="flex-1 overflow-y-auto px-[72px] py-8">
<div className={`h-full flex flex-col min-w-0 transition-all duration-300 ease-in-out ${
state.workspace.isOpen ? 'w-[35%] border-r border-border' : 'w-full'
}`}>
<div className={`flex-1 overflow-y-auto py-8 ${
state.workspace.isOpen ? 'px-4' : 'px-[72px]'
}`}>
{/* History Toggle Button */}
<button
onClick={() => setIsHistoryOpen(true)}
@@ -450,7 +504,7 @@ export default function PlanningAgent() {
<div className="flex flex-col gap-6 w-full">
{state.messages.length === 0 && (
<div className="text-center py-20 card">
<h2 className="text-2xl font-extrabold mb-2"></h2>
<h2 className={`font-extrabold mb-2 ${state.workspace.isOpen ? 'text-lg' : 'text-2xl'}`}></h2>
<p className="text-txt-muted text-sm">
Epic RTE
</p>
@@ -460,7 +514,7 @@ export default function PlanningAgent() {
{state.messages.map((msg) => (
<div
key={msg.id}
className={`max-w-[78%] px-6 py-5 text-[0.95rem] leading-relaxed rounded-2xl shadow-[0_2px_14px_rgba(0,0,0,0.04)] ${
className={`${state.workspace.isOpen ? 'max-w-full' : 'max-w-[78%]'} px-6 py-5 text-[0.95rem] leading-relaxed rounded-2xl shadow-[0_2px_14px_rgba(0,0,0,0.04)] ${
msg.role === "user"
? "self-end mr-1 bg-surface-muted text-txt border border-border whitespace-pre-wrap"
: "self-start bg-white border border-border border-l-4 border-l-magenta"
@@ -504,9 +558,24 @@ export default function PlanningAgent() {
</div>
</div>
{/* Workspace recall button */}
{wsHasContent && (
<div className={`${state.workspace.isOpen ? 'mx-4' : 'mx-[72px]'} mb-2`}>
<button
onClick={() => dispatch({ type: "ws-toggle", open: true })}
className="flex items-center gap-2 px-4 py-2 bg-magenta/5 border border-magenta/30 rounded-xl text-sm text-magenta hover:bg-magenta/10 transition-colors w-full justify-center"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{state.workspace.title}
</button>
</div>
)}
{/* Error */}
{state.error && (
<div className="mx-[72px] mb-2 px-4 py-2 bg-red-50 text-red-700 text-sm flex justify-between items-center">
<div className={`${state.workspace.isOpen ? 'mx-4' : 'mx-[72px]'} mb-2 px-4 py-2 bg-red-50 text-red-700 text-sm flex justify-between items-center`}>
{state.error}
<button
className="text-red-400 hover:text-red-600 font-bold"
@@ -518,7 +587,7 @@ export default function PlanningAgent() {
)}
{/* Input area */}
<div className="px-10 pb-6 pt-4 border-t border-border">
<div className={`pb-6 pt-4 border-t border-border ${state.workspace.isOpen ? 'px-4' : 'px-10'}`}>
{/* File upload row */}
<div className="flex items-center gap-3 mb-3 text-sm">
<label className="btn-outline text-xs px-3 py-1.5 cursor-pointer">
@@ -556,6 +625,49 @@ export default function PlanningAgent() {
</div>
</div>
</div>
{/* ─── Right Panel: Workspace ─── */}
{state.workspace.isOpen && (
<div className="w-[65%] h-full flex flex-col bg-white border-l border-border">
{/* Workspace Header */}
<div className="h-14 border-b border-border flex items-center px-6 justify-between bg-white shrink-0">
<h3 className="font-semibold text-txt flex items-center gap-2 truncate">
<svg className="w-5 h-5 text-magenta shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{state.workspace.title}
{state.workspace.isGenerating && (
<span className="text-sm text-magenta animate-pulse flex items-center gap-1 ml-2">
<span className="w-1.5 h-1.5 rounded-full bg-magenta animate-pulse" />
...
</span>
)}
</h3>
<button
onClick={() => dispatch({ type: "ws-toggle", open: false })}
className="p-2 hover:bg-surface-muted rounded-full transition-colors shrink-0"
title="关闭面板"
>
<svg className="w-5 h-5 text-txt-muted hover:text-txt" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Workspace Content */}
<div className="flex-1 overflow-y-auto p-8">
<div className="prose prose-sm max-w-none prose-p:my-1 prose-pre:bg-gray-800 prose-pre:text-gray-100 prose-code:text-magenta prose-code:bg-magenta/10 prose-code:px-1 prose-code:rounded prose-li:my-0 prose-headings:text-txt">
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{state.workspace.content}
</ReactMarkdown>
</div>
{state.workspace.isGenerating && !state.workspace.content && (
<div className="flex items-center justify-center py-20 text-txt-muted">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-magenta" />
</div>
)}
</div>
</div>
)}
</div>
);
}