| "use client"; |
|
|
| import { useState } from "react"; |
|
|
| export default function ControlBar({ |
| onEnrich, |
| onApplyChanges, |
| onDownload, |
| enriched, |
| loading, |
| loadingMessage, |
| headingFontSize, |
| onFontSizeChange, |
| classifications, |
| }) { |
| const [showPromptEditor, setShowPromptEditor] = useState(false); |
| const [customPrompt, setCustomPrompt] = useState(""); |
| const [model, setModel] = useState("llama3"); |
|
|
| const headingCount = classifications.filter((c) => c.isHeading).length; |
|
|
| const handleEnrich = () => { |
| onEnrich(model, customPrompt || undefined); |
| }; |
|
|
| const handleApplyManualChanges = () => { |
| const headingIndices = classifications |
| .filter((c) => c.isHeading) |
| .map((c) => c.index); |
| onApplyChanges(headingIndices); |
| }; |
|
|
| return ( |
| <div |
| className="border-b px-5 py-3" |
| style={{ |
| background: "var(--bg-primary)", |
| borderColor: "var(--border-color)", |
| }} |
| > |
| {/* Main controls row */} |
| <div className="flex items-center justify-between gap-4"> |
| <div className="flex items-center gap-3"> |
| {/* Enrich / Re-enrich button */} |
| <button |
| onClick={handleEnrich} |
| disabled={loading} |
| className="text-sm font-medium px-4 py-2 rounded-lg transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed" |
| style={{ |
| background: "var(--accent)", |
| color: "var(--bg-secondary)", |
| }} |
| onMouseEnter={(e) => { |
| if (!loading) e.target.style.background = "var(--accent-hover)"; |
| }} |
| onMouseLeave={(e) => { |
| e.target.style.background = "var(--accent)"; |
| }} |
| > |
| {enriched ? "⟳ Re-Enrich with AI" : "✦ Enrich with AI"} |
| </button> |
| |
| {/* Apply manual changes */} |
| {enriched && ( |
| <button |
| onClick={handleApplyManualChanges} |
| disabled={loading} |
| className="text-sm px-4 py-2 rounded-lg transition-colors cursor-pointer disabled:opacity-50" |
| style={{ |
| background: "var(--bg-tertiary)", |
| color: "var(--text-primary)", |
| }} |
| onMouseEnter={(e) => { |
| if (!loading) e.target.style.background = "var(--bg-hover)"; |
| }} |
| onMouseLeave={(e) => { |
| e.target.style.background = "var(--bg-tertiary)"; |
| }} |
| > |
| Apply Changes |
| </button> |
| )} |
| |
| {/* Download button */} |
| {enriched && ( |
| <button |
| onClick={onDownload} |
| disabled={loading} |
| className="text-sm px-4 py-2 rounded-lg transition-colors cursor-pointer disabled:opacity-50" |
| style={{ |
| background: "var(--bg-tertiary)", |
| color: "var(--success)", |
| }} |
| onMouseEnter={(e) => { |
| if (!loading) e.target.style.background = "var(--bg-hover)"; |
| }} |
| onMouseLeave={(e) => { |
| e.target.style.background = "var(--bg-tertiary)"; |
| }} |
| > |
| ↓ Download Enriched |
| </button> |
| )} |
| </div> |
| |
| <div className="flex items-center gap-4"> |
| {/* Heading count badge */} |
| {enriched && ( |
| <span |
| className="text-xs px-2.5 py-1 rounded-full" |
| style={{ |
| background: "var(--heading-highlight)", |
| color: "var(--accent)", |
| }} |
| > |
| {headingCount} heading{headingCount !== 1 ? "s" : ""} detected |
| </span> |
| )} |
| |
| {/* Font size control */} |
| <div className="flex items-center gap-2"> |
| <label |
| className="text-xs" |
| style={{ color: "var(--text-secondary)" }} |
| > |
| Heading size: |
| </label> |
| <select |
| value={headingFontSize} |
| onChange={(e) => onFontSizeChange(Number(e.target.value))} |
| className="text-xs px-2 py-1 rounded border outline-none cursor-pointer" |
| style={{ |
| background: "var(--bg-tertiary)", |
| borderColor: "var(--border-color)", |
| color: "var(--text-primary)", |
| }} |
| > |
| <option value={14}>14pt</option> |
| <option value={16}>16pt</option> |
| <option value={18}>18pt</option> |
| <option value={20}>20pt</option> |
| <option value={22}>22pt</option> |
| <option value={24}>24pt</option> |
| <option value={28}>28pt</option> |
| </select> |
| </div> |
| |
| {/* Model selector */} |
| <div className="flex items-center gap-2"> |
| <label |
| className="text-xs" |
| style={{ color: "var(--text-secondary)" }} |
| > |
| Model: |
| </label> |
| <input |
| type="text" |
| value={model} |
| onChange={(e) => setModel(e.target.value)} |
| className="text-xs px-2 py-1 rounded border outline-none w-28" |
| style={{ |
| background: "var(--bg-tertiary)", |
| borderColor: "var(--border-color)", |
| color: "var(--text-primary)", |
| }} |
| placeholder="llama3" |
| /> |
| </div> |
| |
| {/* Custom prompt toggle */} |
| <button |
| onClick={() => setShowPromptEditor(!showPromptEditor)} |
| className="text-xs px-3 py-1.5 rounded-lg transition-colors cursor-pointer" |
| style={{ |
| background: showPromptEditor |
| ? "var(--accent)" |
| : "var(--bg-tertiary)", |
| color: showPromptEditor |
| ? "var(--bg-secondary)" |
| : "var(--text-secondary)", |
| }} |
| > |
| {showPromptEditor ? "Hide Prompt" : "Custom Prompt"} |
| </button> |
| </div> |
| </div> |
| |
| {/* Custom prompt editor (collapsible) */} |
| {showPromptEditor && ( |
| <div className="mt-3 pt-3 border-t" style={{ borderColor: "var(--border-color)" }}> |
| <label |
| className="text-xs block mb-2" |
| style={{ color: "var(--text-secondary)" }} |
| > |
| Custom system prompt for heading detection (leave empty for default): |
| </label> |
| <textarea |
| value={customPrompt} |
| onChange={(e) => setCustomPrompt(e.target.value)} |
| rows={4} |
| className="w-full text-xs px-3 py-2 rounded-lg border outline-none resize-y" |
| style={{ |
| background: "var(--bg-tertiary)", |
| borderColor: "var(--border-color)", |
| color: "var(--text-primary)", |
| }} |
| placeholder={`You are a document structure analyzer specializing in SOP documents.\n\nYour task: Given a list of numbered paragraphs, identify which ones are HEADINGS vs body text.\n\nRespond with ONLY a JSON object: {"headings": [array of index numbers]}`} |
| /> |
| <p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}> |
| The prompt must instruct the model to return JSON with a |
| "headings" array of paragraph index numbers. |
| </p> |
| </div> |
| )} |
| </div> |
| ); |
| } |
|
|