init: initial commit
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { Hammer, Settings2, Loader2, RefreshCw, Maximize2, Trash2, Plus, Sparkles } from 'lucide-react';
|
||||
import { useAppStore, useCurrentProject } from '../../stores/useAppStore';
|
||||
import { useUIStore } from '../../stores/useUIStore';
|
||||
import { useChatStore } from '../../stores/useChatStore';
|
||||
|
||||
export function RightSidebar() {
|
||||
const currentProject = useCurrentProject();
|
||||
const currentProjectId = useAppStore(s => s.currentProjectId);
|
||||
const setProjects = useAppStore(s => s.setProjects);
|
||||
const { isEditingOutline, setEditingOutline, previewChapter, setPreviewChapter, setPreviewSource } = useUIStore();
|
||||
const { selectedFileIds, addMessage, updateMessage } = useChatStore();
|
||||
|
||||
const updateChapterTitle = (id: string, newTitle: string) => {
|
||||
setProjects(prev => prev.map(p => {
|
||||
if (p.id !== currentProjectId) return p;
|
||||
return { ...p, activeTemplate: { ...p.activeTemplate, chapters: p.activeTemplate.chapters.map(c => c.id === id ? { ...c, title: newTitle } : c) } };
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteChapter = (id: string) => {
|
||||
setProjects(prev => prev.map(p => {
|
||||
if (p.id !== currentProjectId) return p;
|
||||
return { ...p, activeTemplate: { ...p.activeTemplate, chapters: p.activeTemplate.chapters.filter(c => c.id !== id) } };
|
||||
}));
|
||||
};
|
||||
|
||||
const triggerGeneration = (e: React.MouseEvent, chapterId: string, isRegenerate = false) => {
|
||||
e.stopPropagation();
|
||||
if (selectedFileIds.size === 0) return;
|
||||
const chapter = currentProject.activeTemplate.chapters.find(c => c.id === chapterId);
|
||||
if (!chapter) return;
|
||||
const logId = Date.now();
|
||||
|
||||
addMessage({
|
||||
id: logId, role: 'assistant', content: '', type: 'generation-log',
|
||||
chapterTitle: chapter.title, isRegenerate, status: 'processing',
|
||||
steps: ['建立 RAG 安全链路...'],
|
||||
});
|
||||
|
||||
setProjects(prev => prev.map(p => {
|
||||
if (p.id !== currentProjectId) return p;
|
||||
return { ...p, activeTemplate: { ...p.activeTemplate, chapters: p.activeTemplate.chapters.map(c => c.id === chapterId ? { ...c, status: 'loading', progress: 15 } : c) } };
|
||||
}));
|
||||
|
||||
// TODO: 调用后端流式生成接口,实时触发步骤回调,并最终写入结果。
|
||||
// 在这里暂时直接重置状态为 idle 或者保留 loading 状态让后端回调接管
|
||||
// Mock removed
|
||||
setTimeout(() => {
|
||||
setProjects(prev => prev.map(p => {
|
||||
if (p.id !== currentProjectId) return p;
|
||||
return { ...p, activeTemplate: { ...p.activeTemplate, chapters: p.activeTemplate.chapters.map(c => c.id === chapterId ? { ...c, status: 'idle', progress: 0 } : c) } };
|
||||
}));
|
||||
updateMessage(logId, { status: 'success' });
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const addChapter = () => {
|
||||
const id = `c-${Date.now()}`;
|
||||
setProjects(prev => prev.map(p => {
|
||||
if (p.id !== currentProjectId) return p;
|
||||
return { ...p, activeTemplate: { ...p.activeTemplate, chapters: [...p.activeTemplate.chapters, { id, title: '新章节', status: 'idle' as const, progress: 0, content: '' }] } };
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<aside style={{ "--wails-draggable": "no-drag" } as React.CSSProperties} className="w-[320px] shrink-0 border-l border-[var(--color-border-subtle)] bg-[var(--color-surface-main)] p-6 pt-10 flex flex-col z-20 overflow-hidden relative transition-all duration-300">
|
||||
<div className="pb-4 mb-4 border-b border-[var(--color-border-subtle)] flex items-center justify-between text-[11px] font-medium uppercase tracking-[0.15em] text-[var(--color-text-tertiary)]">
|
||||
<div className="flex items-center gap-2"><Hammer size={16} className="text-[var(--color-text-muted)]" /> 质量大纲</div>
|
||||
<button
|
||||
onClick={() => setEditingOutline(!isEditingOutline)}
|
||||
className={`p-2 rounded-lg transition-all border ${isEditingOutline ? 'bg-[var(--color-surface-hover)] border-[var(--color-border-subtle)] text-[var(--color-text-primary)] shadow-sm' : 'border-transparent hover:bg-[var(--color-surface-hover)] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:border-[var(--color-border-subtle)]'}`}
|
||||
title="编辑大纲"
|
||||
>
|
||||
<Settings2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar relative pl-6 pr-2 py-2 w-full">
|
||||
<div className="absolute left-[13px] top-6 bottom-6 w-px bg-[var(--color-border-subtle)]" />
|
||||
<div className="space-y-6">
|
||||
{currentProject.activeTemplate.chapters.map(chap => (
|
||||
<div key={chap.id} className="relative">
|
||||
<div className="absolute -left-6 top-8 w-4 h-4 rounded-full flex items-center justify-center bg-[var(--color-surface-main)] border-[3px] border-[var(--color-surface-main)] z-10">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
chap.status === 'done' ? 'bg-[var(--color-success)]'
|
||||
: chap.status === 'loading' ? 'bg-[var(--color-accent-primary)] animate-pulse' : 'bg-[var(--color-border-strong)]'
|
||||
}`} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => { if (!isEditingOutline && chap.status === 'done') { setPreviewChapter(chap); setPreviewSource(null); } }}
|
||||
className={`group p-5 rounded-2xl border transition-all duration-300 ${
|
||||
isEditingOutline ? 'bg-[var(--color-surface-side)] border-[var(--color-border-subtle)]'
|
||||
: previewChapter?.id === chap.id ? 'bg-[var(--color-surface-hover)] border-[var(--color-border-strong)] shadow-sm'
|
||||
: chap.status === 'done' ? 'bg-[var(--color-surface-main)] border-[var(--color-border-subtle)] hover:border-[var(--color-border-strong)] hover:shadow-[0_2px_10px_-4px_rgba(0,0,0,0.05)] cursor-pointer'
|
||||
: 'bg-[var(--color-surface-main)] border-[var(--color-border-subtle)] opacity-60'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4 min-w-0">
|
||||
{isEditingOutline ? (
|
||||
<input
|
||||
value={chap.title}
|
||||
onChange={(e) => updateChapterTitle(chap.id, e.target.value)}
|
||||
className="bg-[var(--color-surface-main)] border border-[var(--color-border-subtle)] focus:border-[var(--color-border-strong)] focus:shadow-sm rounded-lg p-2 text-[var(--color-text-primary)] w-full outline-none text-[13px] font-sans transition-all"
|
||||
/>
|
||||
) : (
|
||||
<h4 className="text-[13px] font-medium text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)] transition-colors truncate text-left">{chap.title}</h4>
|
||||
)}
|
||||
{!isEditingOutline && chap.status === 'done' && <Maximize2 size={12} className="text-[var(--color-text-muted)] opacity-0 group-hover:opacity-100 transition-all shrink-0 ml-2 group-hover:text-[var(--color-accent-primary)]" />}
|
||||
{isEditingOutline && <button onClick={() => deleteChapter(chap.id)} className="text-[var(--color-text-muted)] hover:text-[var(--color-danger)] transition-colors shrink-0 ml-2"><Trash2 size={14} /></button>}
|
||||
</div>
|
||||
{!isEditingOutline && (
|
||||
<button
|
||||
onClick={(e) => triggerGeneration(e, chap.id, chap.status === 'done')}
|
||||
disabled={selectedFileIds.size === 0 || chap.status === 'loading'}
|
||||
className={`w-full py-2 rounded-xl transition-all flex items-center justify-center gap-2 border ${
|
||||
chap.status === 'done' ? 'bg-transparent border-[var(--color-border-subtle)] hover:bg-[var(--color-surface-hover)] hover:border-[var(--color-border-strong)] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
|
||||
: selectedFileIds.size > 0 ? 'bg-transparent border-[var(--color-border-strong)] text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] shadow-sm'
|
||||
: 'bg-transparent border-[var(--color-border-subtle)] text-[var(--color-text-muted)] cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
{chap.status === 'loading' ? <Loader2 size={14} className="animate-spin text-[var(--color-accent-primary)]" />
|
||||
: chap.status === 'done' ? <RefreshCw size={14} className="text-[var(--color-text-muted)] group-hover:text-[var(--color-text-secondary)]" /> : <Sparkles size={14} />}
|
||||
<span className={`text-[12px] tracking-wide ${chap.status === 'done' ? 'font-normal' : 'font-medium'}`}>
|
||||
{chap.status === 'loading' ? '生成中' : chap.status === 'done' ? '重新生成' : '开始生成'}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{isEditingOutline && (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={addChapter}
|
||||
className="w-full py-4 border-2 border-dashed border-[var(--color-border-subtle)] rounded-2xl text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:border-[var(--color-border-strong)] hover:bg-[var(--color-surface-hover)] transition-all text-[12px] font-medium flex items-center justify-center gap-2 animate-fade-in"
|
||||
>
|
||||
<Plus size={16} /> 添加章节
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-6 mt-4 border-t border-[var(--color-border-subtle)] bg-[var(--color-surface-main)] text-center shrink-0">
|
||||
<button className="w-full h-12 bg-[var(--color-surface-active)] hover:bg-[#DFDDD8] text-[var(--color-text-primary)] font-medium rounded-xl shadow-sm active:scale-[0.98] transition-all duration-300 text-[13px] flex items-center justify-center gap-2 uppercase tracking-[0.1em] whitespace-nowrap">
|
||||
Complete Workflow
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user