File size: 4,676 Bytes
a414867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"use client";

import {
  CheckCircle2,
  Circle,
  Crosshair,
  Download,
  FileInput,
  GitBranchPlus,
  Radio,
  Save,
  Square,
  Upload
} from "lucide-react";
import { Button } from "@/components/ui/Button";
import { PlaybackControls } from "@/components/tree-visualizer/PlaybackControls";
import type { TreeNodeViewMode } from "@/components/tree-visualizer/viewMode";
import { cn } from "@/lib/utils";

interface TreeCommandBarProps {
  hasTree: boolean;
  isDirty: boolean;
  isPlaying: boolean;
  isSaving: boolean;
  isValidating: boolean;
  nodeViewMode: TreeNodeViewMode;
  onCreate: () => void;
  onImport: () => void;
  onModelStream: () => void;
  onSave: () => void;
  onExportRaw: () => void;
  onValidate: () => void;
  onReplay: () => void;
  onPause: () => void;
  onStep: () => void;
  onFitView: () => void;
  onNodeViewModeChange: (mode: TreeNodeViewMode) => void;
}

export function TreeCommandBar({
  hasTree,
  isDirty,
  isPlaying,
  isSaving,
  isValidating,
  nodeViewMode,
  onCreate,
  onImport,
  onModelStream,
  onSave,
  onExportRaw,
  onValidate,
  onReplay,
  onPause,
  onStep,
  onFitView,
  onNodeViewModeChange
}: TreeCommandBarProps) {
  const modeOptions = [
    { mode: "full" as const, label: "Full", icon: Square },
    { mode: "atom" as const, label: "Atom", icon: Circle }
  ];

  return (
    <header className="absolute left-4 right-4 top-4 z-20 flex min-h-14 items-center justify-between gap-3 rounded-xl border border-paper-300 bg-white/82 px-3 py-2 shadow-panel backdrop-blur-xl">
      <div className="flex min-w-0 items-center gap-2 overflow-x-auto scrollbar-thin">
        <Button variant="primary" onClick={onCreate} title="Create a new tree from pasted trajectory text">
          <GitBranchPlus className="h-4 w-4" />
          Create Tree
        </Button>
        <Button onClick={onImport} title="Import raw trajectory text">
          <Upload className="h-4 w-4" />
          Import
        </Button>
        <Button onClick={onModelStream} title="Create a new tree by streaming model trajectory nodes">
          <Radio className="h-4 w-4" />
          Create tree by model
        </Button>
        <Button onClick={onSave} disabled={!hasTree || isSaving || !isDirty} title="Save current tree edits">
          <Save className="h-4 w-4" />
          {isSaving ? "Saving" : "Save"}
        </Button>
        <Button
          className="min-w-[138px]"
          onClick={onExportRaw}
          disabled={!hasTree}
          title="Export the full original pasted trajectory message"
        >
          <Download className="h-4 w-4" />
          Export Raw
        </Button>
        <Button onClick={onValidate} disabled={!hasTree || isValidating} title="Validate current tree">
          <CheckCircle2 className="h-4 w-4" />
          {isValidating ? "Validating" : "Validate"}
        </Button>
      </div>
      <div className="flex shrink-0 items-center gap-2">
        <div
          role="radiogroup"
          aria-label="Node view mode"
          className="inline-flex h-9 items-center rounded-lg border border-paper-300 bg-paper-50 p-1 shadow-sm"
        >
          {modeOptions.map(({ mode, label, icon: Icon }) => {
            const active = nodeViewMode === mode;
            return (
              <button
                key={mode}
                type="button"
                role="radio"
                aria-checked={active}
                onClick={() => onNodeViewModeChange(mode)}
                title={`${label} node view`}
                className={cn(
                  "inline-flex h-7 min-w-8 items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold transition md:min-w-[68px]",
                  active ? "bg-white text-claude-700 shadow-sm" : "text-ink-500 hover:text-ink-900"
                )}
              >
                <Icon className="h-3.5 w-3.5 shrink-0" />
                <span className="hidden md:inline">{label}</span>
              </button>
            );
          })}
        </div>
        <PlaybackControls
          isPlaying={isPlaying}
          disabled={!hasTree}
          onReplay={onReplay}
          onPause={onPause}
          onStep={onStep}
        />
        <Button onClick={onFitView} disabled={!hasTree} title="Fit the tree into view">
          <Crosshair className="h-4 w-4" />
          Fit View
        </Button>
        <div className="hidden items-center gap-2 rounded-lg border border-paper-300 bg-paper-50 px-3 py-2 text-xs text-ink-500 lg:flex">
          <FileInput className="h-4 w-4 text-claude-500" />
          {isDirty ? "unsaved edits" : "workspace synced"}
        </div>
      </div>
    </header>
  );
}