File size: 3,374 Bytes
5710dd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
144
145
146
147
148
149
150
151
152
153
/**
 * Left icon rail — the app-level view switcher. Icon-only by design; view
 * names live in tooltips. Adding a view is one `VIEWS` entry here plus its
 * render branch in `App`.
 */

import type { ReactNode } from 'react';

export type AppView =
  | 'chat'
  | 'search'
  | 'models'
  | 'services'
  | 'workspace'
  | 'suggest'
  | 'bash'
  | 'di';

interface ViewDef {
  readonly id: AppView;
  readonly title: string;
  readonly icon: ReactNode;
}

const iconProps = {
  width: 18,
  height: 18,
  viewBox: '0 0 24 24',
  fill: 'none',
  stroke: 'currentColor',
  strokeWidth: 2,
  strokeLinecap: 'round',
  strokeLinejoin: 'round',
} as const;

const VIEWS: readonly ViewDef[] = [
  {
    id: 'chat',
    title: 'Chat',
    icon: (
      <svg {...iconProps}>
        <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
      </svg>
    ),
  },
  {
    id: 'search',
    title: 'Search',
    icon: (
      <svg {...iconProps}>
        <circle cx="11" cy="11" r="8" />
        <path d="m21 21-4.3-4.3" />
      </svg>
    ),
  },
  {
    id: 'models',
    title: 'Model Catalog',
    icon: (
      <svg {...iconProps}>
        <path d="M12 2 2 7l10 5 10-5-10-5z" />
        <path d="m2 17 10 5 10-5" />
        <path d="m2 12 10 5 10-5" />
      </svg>
    ),
  },
  {
    id: 'services',
    title: 'App Services',
    icon: (
      <svg {...iconProps}>
        <rect x="2" y="2" width="20" height="8" rx="2" />
        <rect x="2" y="14" width="20" height="8" rx="2" />
        <line x1="6" y1="6" x2="6.01" y2="6" />
        <line x1="6" y1="18" x2="6.01" y2="18" />
      </svg>
    ),
  },
  {
    id: 'workspace',
    title: 'Workspace Runtime',
    icon: (
      <svg {...iconProps}>
        <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
      </svg>
    ),
  },
  {
    id: 'suggest',
    title: 'Filesystem Suggest',
    icon: (
      <svg {...iconProps}>
        <path d="M4 4h6l2 2h8v14H4z" />
        <path d="m9 14 2 2 4-4" />
      </svg>
    ),
  },
  {
    id: 'bash',
    title: 'Bash Parser',
    icon: (
      <svg {...iconProps}>
        <polyline points="4 17 10 11 4 5" />
        <line x1="12" y1="19" x2="20" y2="19" />
      </svg>
    ),
  },
  {
    id: 'di',
    title: 'DI',
    icon: (
      <svg {...iconProps}>
        <circle cx="18" cy="5" r="3" />
        <circle cx="6" cy="12" r="3" />
        <circle cx="18" cy="19" r="3" />
        <line x1="8.6" y1="10.5" x2="15.4" y2="6.5" />
        <line x1="8.6" y1="13.5" x2="15.4" y2="17.5" />
      </svg>
    ),
  },
];

export function NavRail({
  view,
  onChange,
}: {
  readonly view: AppView;
  readonly onChange: (view: AppView) => void;
}) {
  return (
    <nav className="flex w-11 shrink-0 flex-col items-center gap-1 border-r border-neutral-800 py-2">
      {VIEWS.map((v) => (
        <button
          key={v.id}
          type="button"
          title={v.title}
          aria-label={v.title}
          onClick={() => {
            onChange(v.id);
          }}
          className={`rounded-md p-2 transition-colors ${
            view === v.id
              ? 'bg-neutral-800 text-sky-400'
              : 'text-neutral-500 hover:bg-neutral-800/60 hover:text-neutral-300'
          }`}
        >
          {v.icon}
        </button>
      ))}
    </nav>
  );
}