krz/brand-bench

A dynamic brand documentation generator with Ollama integration.

clone: git clone https://gitbay.org/krz/brand-bench.git

main: src/App.tsx · raw

  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}