krz/brand-bench
A dynamic brand documentation generator with Ollama integration.
clone: git clone https://gitbay.org/krz/brand-bench.git
1import { useState, useEffect } from 'react';
2import { useWorkspace } from './hooks/useWorkspace';
3import { usePackages } from './hooks/usePackages';
4import { useSettings } from './hooks/useSettings';
5import type { PackageSlot } from './hooks/usePackages';
6import { InputPanel } from './components/InputPanel';
7import { PreviewPanel } from './components/PreviewPanel';
8import { PackageSwitcher } from './components/PackageSwitcher';
9import { SettingsPanel } from './components/SettingsPanel';
10import type { BrandOutputs } from './types';
11
12interface WorkspaceShellProps {
13 storageKey: string;
14 slots: PackageSlot[];
15 activeId: string;
16 onSwitch: (id: string) => void;
17 onCreate: () => void;
18 onRemove: (id: string) => void;
19 onRename: (id: string, name: string) => void;
20 onDuplicate: (id: string) => void;
21 onOpenSettings: () => void;
22 aiEnabled: boolean;
23}
24
25function WorkspaceShell({
26 storageKey, slots, activeId,
27 onSwitch, onCreate, onRemove, onRename, onDuplicate,
28 onOpenSettings, aiEnabled,
29}: WorkspaceShellProps) {
30 const {
31 inputs, setInputs, outputs, isGenerating, generateMode, generateError,
32 locked, runGenerate, cancelGenerate, updateEdit, toggleLock,
33 undo, redo, canUndo, canRedo,
34 } = useWorkspace(storageKey);
35
36 useEffect(() => {
37 const handler = (e: KeyboardEvent) => {
38 if (!(e.metaKey || e.ctrlKey) || e.key !== 'z') return;
39 e.preventDefault();
40 if (e.shiftKey) redo();
41 else undo();
42 };
43 window.addEventListener('keydown', handler);
44 return () => window.removeEventListener('keydown', handler);
45 }, [undo, redo]);
46
47 const handleEdit = <K extends keyof BrandOutputs>(key: K, value: BrandOutputs[K]) =>
48 updateEdit(key, value);
49
50 const generateLabel = isGenerating
51 ? 'Cancel'
52 : outputs
53 ? (aiEnabled ? `Regenerate` : 'Regenerate')
54 : (aiEnabled ? 'Generate with AI' : 'Generate');
55
56 return (
57 <>
58 <header className="app-header">
59 <div className="app-header-left">
60 <span className="app-wordmark">Brand<span>bench</span></span>
61 <PackageSwitcher
62 slots={slots} activeId={activeId}
63 onSwitch={onSwitch} onCreate={onCreate}
64 onRemove={onRemove} onRename={onRename} onDuplicate={onDuplicate}
65 />
66 </div>
67 <div className="app-header-right">
68 {outputs && (
69 <>
70 <button className="btn btn-ghost" onClick={undo} disabled={!canUndo} title="Undo (⌘Z)">Undo</button>
71 <button className="btn btn-ghost" onClick={redo} disabled={!canRedo} title="Redo (⌘⇧Z)">Redo</button>
72 </>
73 )}
74 {aiEnabled && (
75 <span className="ai-badge" title={`AI mode: ${generateMode}`}>AI</span>
76 )}
77 <button className="btn btn-ghost settings-open-btn" onClick={onOpenSettings} title="Settings">⚙</button>
78 </div>
79 </header>
80
81 <div className="app-shell">
82 <InputPanel
83 inputs={inputs}
84 onChange={setInputs}
85 onGenerate={isGenerating ? cancelGenerate : runGenerate}
86 isGenerating={isGenerating}
87 generateLabel={generateLabel}
88 />
89 <PreviewPanel
90 inputs={inputs}
91 outputs={outputs}
92 locked={locked}
93 onToggleLock={toggleLock}
94 onEdit={handleEdit}
95 isGenerating={isGenerating}
96 generateMode={generateMode}
97 generateError={generateError}
98 onGenerate={runGenerate}
99 />
100 </div>
101 </>
102 );
103}
104
105export default function App() {
106 const { slots, activeId, activeSlot, switchTo, createNew, remove, rename, duplicate } = usePackages();
107 const { settings, setSettings } = useSettings();
108 const [settingsOpen, setSettingsOpen] = useState(false);
109
110 return (
111 <>
112 <WorkspaceShell
113 key={activeId}
114 storageKey={activeSlot.storageKey}
115 slots={slots} activeId={activeId}
116 onSwitch={switchTo} onCreate={createNew}
117 onRemove={remove} onRename={rename} onDuplicate={duplicate}
118 onOpenSettings={() => setSettingsOpen(true)}
119 aiEnabled={settings.enabled}
120 />
121 {settingsOpen && (
122 <SettingsPanel
123 settings={settings}
124 onChange={setSettings}
125 onClose={() => setSettingsOpen(false)}
126 />
127 )}
128 </>
129 );
130}