iris-chatbot 4.1.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,10 +7,11 @@ import {
7
7
  ensureBuiltinConnections,
8
8
  normalizeBaseUrl,
9
9
  } from "../lib/connections";
10
+ import { DEFAULT_ACCENT_DARK, DEFAULT_ACCENT_LIGHT } from "../lib/data";
10
11
  import { db } from "../lib/db";
11
12
  import { useMemories } from "../lib/hooks";
12
13
  import { normalizeMemoryKey } from "../lib/memory";
13
- import { filterModelIdsForConnection, getConnectionModelPresets } from "../lib/model-presets";
14
+ import { filterModelIdsForConnection, getConnectionModelPresets, getModelDisplayLabel } from "../lib/model-presets";
14
15
  import {
15
16
  DEFAULT_MEMORY_SETTINGS,
16
17
  DEFAULT_LOCAL_TOOLS_SETTINGS,
@@ -24,7 +25,6 @@ import {
24
25
  type ModelConnection,
25
26
  type SafetyProfile,
26
27
  type Settings,
27
- type WebSearchBackend,
28
28
  } from "../lib/types";
29
29
 
30
30
  type TabId =
@@ -112,10 +112,10 @@ export default function SettingsModal({
112
112
  const [showOpenAIKey, setShowOpenAIKey] = useState(false);
113
113
  const [showAnthropicKey, setShowAnthropicKey] = useState(false);
114
114
  const [showGeminiKey, setShowGeminiKey] = useState(false);
115
- const [accentColor, setAccentColor] = useState(settings?.accentColor || "#66706e");
115
+ const [accentColor, setAccentColor] = useState(settings?.accentColor || DEFAULT_ACCENT_DARK);
116
116
  const [theme, setTheme] = useState<"dark" | "light">(settings?.theme || "dark");
117
117
  const [font, setFont] = useState<"ibm" | "manrope" | "sora" | "space" | "poppins">(
118
- settings?.font || "manrope",
118
+ settings?.font || "poppins",
119
119
  );
120
120
 
121
121
  const localTools = settings?.localTools ?? DEFAULT_LOCAL_TOOLS_SETTINGS;
@@ -132,8 +132,6 @@ export default function SettingsModal({
132
132
  const [enableMail, setEnableMail] = useState(localTools.enableMail);
133
133
  const [enableWorkflow, setEnableWorkflow] = useState(localTools.enableWorkflow);
134
134
  const [enableSystem, setEnableSystem] = useState(localTools.enableSystem);
135
- const [webSearchBackend, setWebSearchBackend] = useState<WebSearchBackend>(localTools.webSearchBackend);
136
- const [dryRun, setDryRun] = useState(localTools.dryRun);
137
135
  const memory = settings?.memory ?? DEFAULT_MEMORY_SETTINGS;
138
136
  const [memoryEnabled, setMemoryEnabled] = useState(memory.enabled);
139
137
  const [memoryAutoCapture, setMemoryAutoCapture] = useState(memory.autoCapture);
@@ -197,8 +195,8 @@ export default function SettingsModal({
197
195
 
198
196
  const selectedDefaultModel = selectedDefaultConnection
199
197
  ? defaultModelByConnection[selectedDefaultConnection.id] ||
200
- getConnectionModelPresets(selectedDefaultConnection)[0] ||
201
- ""
198
+ getConnectionModelPresets(selectedDefaultConnection)[0] ||
199
+ ""
202
200
  : "";
203
201
  const selectedPresets = selectedDefaultConnection ? getConnectionModelPresets(selectedDefaultConnection) : [];
204
202
  const selectedFetchedModels = selectedDefaultConnection ? fetchedModels[selectedDefaultConnection.id] ?? [] : [];
@@ -216,6 +214,49 @@ export default function SettingsModal({
216
214
  return text.includes(query);
217
215
  });
218
216
 
217
+ const memoryKindLabel: Record<MemoryKind, string> = {
218
+ profile: "Profile",
219
+ preference: "Preference",
220
+ person_alias: "Person alias",
221
+ music_alias: "Music alias",
222
+ note: "Note",
223
+ };
224
+ const memoryScopeLabel: Record<MemoryScope, string> = {
225
+ global: "All chats",
226
+ conversation: "This conversation",
227
+ };
228
+ const memorySourceLabel: Record<MemorySource, string> = {
229
+ auto: "Auto-captured",
230
+ explicit: "Explicit",
231
+ manual: "Manual",
232
+ };
233
+ function formatMemoryValue(value: string): string {
234
+ try {
235
+ const parsed = JSON.parse(value) as Record<string, unknown>;
236
+ if (parsed && typeof parsed === "object") {
237
+ const title = typeof parsed.title === "string" ? parsed.title : "";
238
+ const artist = typeof parsed.artist === "string" ? parsed.artist : "";
239
+ const query = typeof parsed.query === "string" ? parsed.query : "";
240
+ if (title && artist) return `${title} by ${artist}`;
241
+ if (query) return query;
242
+ if (title) return title;
243
+ }
244
+ } catch {
245
+ // not JSON, use as-is
246
+ }
247
+ return value;
248
+ }
249
+ function formatMemoryMeta(entry: MemoryEntry): string {
250
+ const parts = [
251
+ memoryKindLabel[entry.kind],
252
+ entry.scope === "conversation" && entry.conversationId
253
+ ? `${memoryScopeLabel[entry.scope]} (${entry.conversationId})`
254
+ : memoryScopeLabel[entry.scope],
255
+ memorySourceLabel[entry.source],
256
+ ];
257
+ return parts.join(" · ");
258
+ }
259
+
219
260
  const resetConnectionForm = () => {
220
261
  setEditingConnectionId(null);
221
262
  setConnectionFormName("");
@@ -486,7 +527,7 @@ export default function SettingsModal({
486
527
  defaultModelByConnection: resolvedDefaultModelMap,
487
528
  showExtendedOpenAIModels,
488
529
  enableWebSources,
489
- accentColor: accentColor || "#66706e",
530
+ accentColor: accentColor || DEFAULT_ACCENT_DARK,
490
531
  font,
491
532
  theme,
492
533
  localTools: {
@@ -504,8 +545,8 @@ export default function SettingsModal({
504
545
  enableMail,
505
546
  enableWorkflow,
506
547
  enableSystem,
507
- webSearchBackend,
508
- dryRun,
548
+ webSearchBackend: DEFAULT_LOCAL_TOOLS_SETTINGS.webSearchBackend,
549
+ dryRun: localTools.dryRun,
509
550
  },
510
551
  memory: {
511
552
  enabled: memoryEnabled,
@@ -518,7 +559,7 @@ export default function SettingsModal({
518
559
  };
519
560
 
520
561
  const accentPresets = [
521
- { id: "default", label: "Default", color: "#66706e" },
562
+ { id: "default", label: "Default", color: DEFAULT_ACCENT_DARK },
522
563
  { id: "blue", label: "Blue", color: "#2563eb" },
523
564
  { id: "green", label: "Green", color: "#16a34a" },
524
565
  { id: "yellow", label: "Yellow", color: "#f59e0b" },
@@ -545,11 +586,10 @@ export default function SettingsModal({
545
586
  <button
546
587
  key={tab.id}
547
588
  onClick={() => setActiveTab(tab.id)}
548
- className={`rounded-full px-3 py-1.5 text-xs ${
549
- activeTab === tab.id
550
- ? "bg-[var(--accent)] text-white"
589
+ className={`rounded-full px-3 py-1.5 text-xs settings-tab ${activeTab === tab.id
590
+ ? "settings-tab-active"
551
591
  : "border border-[var(--border)] bg-[var(--panel-2)] text-[var(--text-secondary)]"
552
- }`}
592
+ }`}
553
593
  >
554
594
  {tab.label}
555
595
  </button>
@@ -581,7 +621,7 @@ export default function SettingsModal({
581
621
  <div className="text-xs uppercase tracking-[0.2em] text-[var(--text-muted)]">
582
622
  Default model for {selectedDefaultConnection.name}
583
623
  </div>
584
- <input
624
+ <select
585
625
  className="w-full rounded-lg border border-[var(--border)] bg-[var(--panel)] px-3 py-2 text-sm"
586
626
  value={modelInput || selectedDefaultModel}
587
627
  onChange={(event) => {
@@ -592,10 +632,25 @@ export default function SettingsModal({
592
632
  [selectedDefaultConnection.id]: value,
593
633
  }));
594
634
  }}
595
- placeholder="Enter model id"
596
- />
635
+ >
636
+ {selectableModels.length === 0 ? (
637
+ <option value="">No models — fetch from Connections tab</option>
638
+ ) : (
639
+ (() => {
640
+ const current = modelInput || selectedDefaultModel;
641
+ const ids = current && !selectableModels.includes(current)
642
+ ? [current, ...selectableModels]
643
+ : selectableModels;
644
+ return ids.map((modelId) => (
645
+ <option key={modelId} value={modelId}>
646
+ {getModelDisplayLabel(modelId, selectedDefaultConnection)}
647
+ </option>
648
+ ));
649
+ })()
650
+ )}
651
+ </select>
597
652
  <div className="flex flex-wrap gap-2">
598
- {selectableModels.slice(0, 20).map((preset) => (
653
+ {selectableModels.map((preset) => (
599
654
  <button
600
655
  key={preset}
601
656
  className="rounded-full border border-[var(--border)] bg-[var(--panel)] px-3 py-1 text-xs text-[var(--text-secondary)]"
@@ -607,7 +662,7 @@ export default function SettingsModal({
607
662
  }));
608
663
  }}
609
664
  >
610
- {preset}
665
+ {getModelDisplayLabel(preset, selectedDefaultConnection)}
611
666
  </button>
612
667
  ))}
613
668
  {selectableModels.length === 0 ? (
@@ -617,8 +672,8 @@ export default function SettingsModal({
617
672
  ) : null}
618
673
  </div>
619
674
  {selectedDefaultConnection?.kind === "builtin" &&
620
- selectedDefaultConnection?.provider === "openai" ? (
621
- <label className="mt-3 flex items-center gap-2 text-sm text-[var(--text-secondary)]">
675
+ selectedDefaultConnection?.provider === "openai" ? (
676
+ <label className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
622
677
  <input
623
678
  type="checkbox"
624
679
  checked={showExtendedOpenAIModels}
@@ -766,37 +821,37 @@ export default function SettingsModal({
766
821
  />
767
822
  Enabled
768
823
  </label>
769
- <span>
770
- Tools: {(connection.supportsTools ?? true) ? "supported" : "disabled"}
771
- </span>
824
+ <span>
825
+ Tools: {(connection.supportsTools ?? true) ? "supported" : "disabled"}
826
+ </span>
772
827
  {connection.kind === "builtin" ? (
773
828
  <span>Keys configured in API Keys tab</span>
774
829
  ) : null}
775
- {connectionStatus[connection.id] ? <span>{connectionStatus[connection.id]}</span> : null}
776
- </div>
777
- </div>
778
- ))}
779
- </div>
830
+ {connectionStatus[connection.id] ? <span>{connectionStatus[connection.id]}</span> : null}
831
+ </div>
832
+ </div>
833
+ ))}
834
+ </div>
780
835
 
781
836
  <div className="rounded-xl border border-[var(--border)] bg-[var(--panel-2)] p-4">
782
837
  <div className="mb-3 text-xs uppercase tracking-[0.2em] text-[var(--text-muted)]">
783
838
  {editingConnectionId ? "Edit Connection" : "Add Connection"}
784
839
  </div>
785
- <div className="grid gap-3 md:grid-cols-2">
840
+ <div className="grid gap-3 md:grid-cols-2">
786
841
  <input
787
842
  className="rounded-lg border border-[var(--border)] bg-[var(--panel)] px-3 py-2 text-sm"
788
843
  placeholder="Connection name"
789
844
  value={connectionFormName}
790
845
  onChange={(event) => setConnectionFormName(event.target.value)}
791
846
  />
792
- <select
847
+ <select
793
848
  className="rounded-lg border border-[var(--border)] bg-[var(--panel)] px-3 py-2 text-sm"
794
849
  value={connectionFormKind}
795
- onChange={(event) => setConnectionFormKind(event.target.value as ConnectionKind)}
796
- >
797
- <option value="openai_compatible">OpenAI-compatible</option>
798
- <option value="ollama">Ollama</option>
799
- </select>
850
+ onChange={(event) => setConnectionFormKind(event.target.value as ConnectionKind)}
851
+ >
852
+ <option value="openai_compatible">OpenAI-compatible</option>
853
+ <option value="ollama">Ollama</option>
854
+ </select>
800
855
  <input
801
856
  className="rounded-lg border border-[var(--border)] bg-[var(--panel)] px-3 py-2 text-sm md:col-span-2"
802
857
  placeholder="Base URL (e.g. http://localhost:11434)"
@@ -815,11 +870,11 @@ export default function SettingsModal({
815
870
  value={connectionFormHeadersText}
816
871
  onChange={(event) => setConnectionFormHeadersText(event.target.value)}
817
872
  />
818
- </div>
873
+ </div>
819
874
  {connectionStatus.form ? (
820
875
  <div className="mt-2 text-xs text-[var(--danger)]">{connectionStatus.form}</div>
821
876
  ) : null}
822
- <div className="mt-3 flex flex-wrap items-center gap-3">
877
+ <div className="mt-3 flex flex-wrap items-center gap-3">
823
878
  <label className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
824
879
  <input
825
880
  type="checkbox"
@@ -852,7 +907,7 @@ export default function SettingsModal({
852
907
  </div>
853
908
  <div className="mt-3 flex gap-2">
854
909
  <button
855
- className="rounded-full bg-[var(--accent)] px-4 py-2 text-xs text-white"
910
+ className="rounded-full px-4 py-2 text-xs settings-btn-accent"
856
911
  onClick={upsertConnectionFromForm}
857
912
  >
858
913
  {editingConnectionId ? "Update Connection" : "Add Connection"}
@@ -919,41 +974,27 @@ export default function SettingsModal({
919
974
  placeholder="~/iris-project&#10;~/Desktop"
920
975
  />
921
976
  </div>
922
- <div className="grid gap-2 md:grid-cols-2">
923
- {[
924
- { label: "Enable Apple Notes tools", checked: enableNotes, setter: setEnableNotes },
925
- { label: "Enable app open/focus tools", checked: enableApps, setter: setEnableApps },
926
- { label: "Enable Numbers tools", checked: enableNumbers, setter: setEnableNumbers },
927
- { label: "Enable web search/open tools", checked: enableWeb, setter: setEnableWeb },
928
- { label: "Enable music controls", checked: enableMusic, setter: setEnableMusic },
929
- { label: "Enable calendar/reminders tools", checked: enableCalendar, setter: setEnableCalendar },
930
- { label: "Enable mail/messages tools", checked: enableMail, setter: setEnableMail },
931
- { label: "Enable workflow tool", checked: enableWorkflow, setter: setEnableWorkflow },
932
- { label: "Enable system controls", checked: enableSystem, setter: setEnableSystem },
933
- { label: "Dry-run only (no writes)", checked: dryRun, setter: setDryRun },
934
- ].map((item) => (
935
- <label key={item.label} className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
936
- <input
937
- type="checkbox"
938
- checked={item.checked}
939
- onChange={(event) => item.setter(event.target.checked)}
940
- />
941
- {item.label}
942
- </label>
943
- ))}
944
- </div>
945
- <div>
946
- <label className="mb-2 block text-xs uppercase tracking-[0.2em] text-[var(--text-muted)]">
947
- Web Search Backend
948
- </label>
949
- <select
950
- className="w-full rounded-lg border border-[var(--border)] bg-[var(--panel)] px-3 py-2 text-sm"
951
- value={webSearchBackend}
952
- onChange={(event) => setWebSearchBackend(event.target.value as WebSearchBackend)}
953
- >
954
- <option value="no_key">No-key (default)</option>
955
- <option value="hybrid">Hybrid</option>
956
- </select>
977
+ <div className="grid gap-2 md:grid-cols-2">
978
+ {[
979
+ { label: "Enable Apple Notes tools", checked: enableNotes, setter: setEnableNotes },
980
+ { label: "Enable app open/focus tools", checked: enableApps, setter: setEnableApps },
981
+ { label: "Enable Numbers tools", checked: enableNumbers, setter: setEnableNumbers },
982
+ { label: "Enable web search/open tools", checked: enableWeb, setter: setEnableWeb },
983
+ { label: "Enable music controls", checked: enableMusic, setter: setEnableMusic },
984
+ { label: "Enable calendar/reminders tools", checked: enableCalendar, setter: setEnableCalendar },
985
+ { label: "Enable mail/messages tools", checked: enableMail, setter: setEnableMail },
986
+ { label: "Enable workflow tool", checked: enableWorkflow, setter: setEnableWorkflow },
987
+ { label: "Enable system controls", checked: enableSystem, setter: setEnableSystem },
988
+ ].map((item) => (
989
+ <label key={item.label} className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
990
+ <input
991
+ type="checkbox"
992
+ checked={item.checked}
993
+ onChange={(event) => item.setter(event.target.checked)}
994
+ />
995
+ {item.label}
996
+ </label>
997
+ ))}
957
998
  </div>
958
999
  </div>
959
1000
  ) : null}
@@ -1020,13 +1061,11 @@ export default function SettingsModal({
1020
1061
  <div className="flex items-start justify-between gap-3">
1021
1062
  <div>
1022
1063
  <div className="text-sm font-medium text-[var(--text-primary)]">{entry.key}</div>
1023
- <div className="mt-0.5 text-xs text-[var(--text-secondary)]">{entry.value}</div>
1064
+ <div className="mt-0.5 text-xs text-[var(--text-secondary)]">
1065
+ {formatMemoryValue(entry.value)}
1066
+ </div>
1024
1067
  <div className="mt-1 text-[11px] text-[var(--text-muted)]">
1025
- {entry.kind} | {entry.scope}
1026
- {entry.scope === "conversation" && entry.conversationId
1027
- ? ` | ${entry.conversationId}`
1028
- : ""}
1029
- {` | ${entry.source}`}
1068
+ {formatMemoryMeta(entry)}
1030
1069
  </div>
1031
1070
  </div>
1032
1071
  <div className="flex shrink-0 gap-2">
@@ -1130,7 +1169,7 @@ export default function SettingsModal({
1130
1169
  ) : null}
1131
1170
  <div className="flex gap-2">
1132
1171
  <button
1133
- className="rounded-full bg-[var(--accent)] px-4 py-2 text-xs text-white"
1172
+ className="rounded-full px-4 py-2 text-xs settings-btn-accent"
1134
1173
  onClick={() => {
1135
1174
  void upsertMemoryEntry();
1136
1175
  }}
@@ -1157,7 +1196,11 @@ export default function SettingsModal({
1157
1196
  <select
1158
1197
  className="w-full rounded-lg border border-[var(--border)] bg-[var(--panel-2)] px-3 py-2 text-sm"
1159
1198
  value={theme}
1160
- onChange={(event) => setTheme(event.target.value as "dark" | "light")}
1199
+ onChange={(event) => {
1200
+ const value = event.target.value as "dark" | "light";
1201
+ setTheme(value);
1202
+ document.documentElement.dataset.theme = value;
1203
+ }}
1161
1204
  >
1162
1205
  <option value="dark">Dark</option>
1163
1206
  <option value="light">Light</option>
@@ -1170,13 +1213,26 @@ export default function SettingsModal({
1170
1213
  <select
1171
1214
  className="w-full rounded-lg border border-[var(--border)] bg-[var(--panel-2)] px-3 py-2 text-sm"
1172
1215
  value={font}
1173
- onChange={(event) =>
1174
- setFont(event.target.value as "ibm" | "manrope" | "sora" | "space" | "poppins")
1175
- }
1216
+ onChange={(event) => {
1217
+ const value = event.target.value as "ibm" | "manrope" | "sora" | "space" | "poppins";
1218
+ setFont(value);
1219
+ const fontVar =
1220
+ value === "manrope"
1221
+ ? "var(--font-manrope)"
1222
+ : value === "poppins"
1223
+ ? "var(--font-poppins)"
1224
+ : value === "sora"
1225
+ ? "var(--font-sora)"
1226
+ : value === "space"
1227
+ ? "var(--font-space)"
1228
+ : "var(--font-poppins)";
1229
+ document.documentElement.style.setProperty("--app-font", fontVar);
1230
+ document.body.style.fontFamily = fontVar;
1231
+ }}
1176
1232
  >
1177
- <option value="ibm">IBM Plex Sans (Default)</option>
1233
+ <option value="ibm">IBM Plex Sans</option>
1178
1234
  <option value="manrope">Manrope</option>
1179
- <option value="poppins">Poppins</option>
1235
+ <option value="poppins">Poppins (Default)</option>
1180
1236
  <option value="sora">Sora</option>
1181
1237
  <option value="space">Space Grotesk</option>
1182
1238
  </select>
@@ -1186,20 +1242,34 @@ export default function SettingsModal({
1186
1242
  Accent Color
1187
1243
  </label>
1188
1244
  <div className="grid grid-cols-4 gap-2">
1189
- {accentPresets.map((preset) => (
1190
- <button
1191
- key={preset.id}
1192
- className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-xs ${
1193
- accentColor === preset.color
1194
- ? "border-[var(--accent)] text-[var(--text-primary)]"
1195
- : "border-[var(--border)] text-[var(--text-secondary)]"
1196
- }`}
1197
- onClick={() => setAccentColor(preset.color)}
1198
- >
1199
- <span className="h-3 w-3 rounded-full" style={{ background: preset.color }} />
1200
- {preset.label}
1201
- </button>
1202
- ))}
1245
+ {accentPresets.map((preset) => {
1246
+ const isDefault = preset.id === "default";
1247
+ const displayColor =
1248
+ isDefault && theme === "light"
1249
+ ? DEFAULT_ACCENT_LIGHT
1250
+ : preset.color;
1251
+ const isSelected = isDefault
1252
+ ? accentColor === DEFAULT_ACCENT_DARK
1253
+ : accentColor === preset.color;
1254
+ return (
1255
+ <button
1256
+ key={preset.id}
1257
+ className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-xs ${isSelected
1258
+ ? "border-[var(--accent-ring)] text-[var(--text-primary)]"
1259
+ : "border-[var(--border)] text-[var(--text-secondary)]"
1260
+ }`}
1261
+ onClick={() =>
1262
+ setAccentColor(isDefault ? DEFAULT_ACCENT_DARK : preset.color)
1263
+ }
1264
+ >
1265
+ <span
1266
+ className="h-3 w-3 rounded-full"
1267
+ style={{ background: displayColor }}
1268
+ />
1269
+ {preset.label}
1270
+ </button>
1271
+ );
1272
+ })}
1203
1273
  </div>
1204
1274
  </div>
1205
1275
  </div>
@@ -1248,7 +1318,7 @@ export default function SettingsModal({
1248
1318
  Cancel
1249
1319
  </button>
1250
1320
  <button
1251
- className="rounded-full bg-[var(--accent)] px-4 py-2 text-xs text-white"
1321
+ className="rounded-full px-4 py-2 text-xs settings-btn-accent"
1252
1322
  onClick={handleSave}
1253
1323
  >
1254
1324
  Save
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { useMemo } from "react";
4
4
  import {
5
- Folder,
6
5
  PanelLeftClose,
7
6
  PenSquare,
8
7
  Search,
@@ -83,8 +82,8 @@ export default function Sidebar({
83
82
  </div>
84
83
 
85
84
  <div className="flex-1 overflow-y-auto px-2">
86
- <div className="sidebar-text px-2 py-2 text-[11px] uppercase tracking-[0.18em] text-[var(--text-muted)]">
87
- Your chats
85
+ <div className="sidebar-text px-2 py-2 text-sm text-[var(--text-muted)]">
86
+ Your Chats
88
87
  </div>
89
88
  <div className="space-y-2">
90
89
  {groups.map(({ root }) => (
@@ -106,7 +105,6 @@ export default function Sidebar({
106
105
  }`}
107
106
  >
108
107
  <div className="flex min-w-0 flex-1 items-center gap-2">
109
- <Folder className="h-4 w-4 shrink-0 text-[var(--text-muted)]" />
110
108
  <div className="sidebar-text min-w-0 truncate">
111
109
  {root.title || "Main chat"}
112
110
  </div>
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useRef, useState } from "react";
4
- import { ChevronDown, MessageSquare, Settings, Waypoints } from "lucide-react";
4
+ import { ChevronDown, MessageSquare, Network, Settings } from "lucide-react";
5
5
  import type { ModelConnection } from "../lib/types";
6
6
  import { getModelDisplayLabel } from "../lib/model-presets";
7
7
 
@@ -67,7 +67,7 @@ export default function TopBar({
67
67
  <button
68
68
  type="button"
69
69
  onClick={() => setConnectionMenuOpen((current) => !current)}
70
- className="inline-flex max-w-[42vw] items-center gap-2 rounded-md px-1.5 py-1 text-left sm:max-w-[240px] sm:px-2"
70
+ className="inline-flex max-w-[42vw] items-center gap-2 rounded-md px-1.5 py-1 text-left text-[var(--text-primary)] sm:max-w-[240px] sm:px-2"
71
71
  aria-haspopup="listbox"
72
72
  aria-expanded={connectionMenuOpen}
73
73
  aria-label="Model provider"
@@ -144,15 +144,9 @@ export default function TopBar({
144
144
  onClick={onToggleView}
145
145
  >
146
146
  {viewMode === "chat" ? (
147
- <>
148
- <Waypoints className="h-5 w-5" />
149
- <span className="hidden sm:inline">Map</span>
150
- </>
147
+ <Network className="h-5 w-5" />
151
148
  ) : (
152
- <>
153
- <MessageSquare className="h-5 w-5" />
154
- <span className="hidden sm:inline">Chat</span>
155
- </>
149
+ <MessageSquare className="h-5 w-5" />
156
150
  )}
157
151
  </button>
158
152
  ) : null}
@@ -30,6 +30,11 @@ const DEFAULT_SETTINGS: Settings = {
30
30
  memory: { ...DEFAULT_MEMORY_SETTINGS },
31
31
  };
32
32
 
33
+ /** Default accent gray in dark mode (chat bubble, etc.). */
34
+ export const DEFAULT_ACCENT_DARK = "#66706e";
35
+ /** Default accent gray in light mode (light gray so bubble isn’t too dark on white). */
36
+ export const DEFAULT_ACCENT_LIGHT = "#e4e6e5";
37
+
33
38
  const LEGACY_DEFAULT_LOCAL_TOOLS_SETTINGS: LocalToolsSettings = {
34
39
  ...DEFAULT_LOCAL_TOOLS_SETTINGS,
35
40
  approvalMode: "always_confirm_writes",
@@ -136,6 +136,32 @@ export function filterModelIdsForConnection(params: {
136
136
  return ids.slice(0, Math.min(ids.length, OPENAI_FRONTIER_MODEL_PRESETS.length));
137
137
  }
138
138
 
139
+ const OPENAI_MODEL_LABELS: Record<string, string> = {
140
+ "gpt-5.2": "GPT 5.2",
141
+ "gpt-5.2-pro": "GPT 5.2 Pro",
142
+ "gpt-5.2-chat-latest": "GPT 5.2 Chat (Latest)",
143
+ "gpt-5.2-codex": "GPT 5.2 Codex",
144
+ "gpt-5.1": "GPT 5.1",
145
+ "gpt-5.1-chat-latest": "GPT 5.1 Chat (Latest)",
146
+ "gpt-5.1-codex": "GPT 5.1 Codex",
147
+ "gpt-5.1-codex-max": "GPT 5.1 Codex Max",
148
+ "gpt-5": "GPT 5",
149
+ "gpt-5-chat-latest": "GPT 5 Chat (Latest)",
150
+ "gpt-5-mini": "GPT 5 Mini",
151
+ "gpt-5-nano": "GPT 5 Nano",
152
+ "gpt-5-codex": "GPT 5 Codex",
153
+ "gpt-5-pro": "GPT 5 Pro",
154
+ "gpt-4.1": "GPT 4.1",
155
+ "gpt-4.1-mini": "GPT 4.1 Mini",
156
+ "gpt-4.1-nano": "GPT 4.1 Nano",
157
+ "gpt-4o": "GPT 4o",
158
+ "gpt-4o-mini": "GPT 4o Mini",
159
+ "o3-pro": "O3 Pro",
160
+ "o3": "O3",
161
+ "o4-mini": "O4 Mini",
162
+ "o3-mini": "O3 Mini",
163
+ };
164
+
139
165
  const ANTHROPIC_MODEL_LABELS: Record<string, string> = {
140
166
  "claude-opus-4-61": "Claude Opus 4.6",
141
167
  "claude-sonnet-4-51": "Claude Sonnet 4.5",
@@ -209,6 +235,9 @@ export function getModelDisplayLabel(modelId: string, connection: ModelConnectio
209
235
  if (connection.provider === "google") {
210
236
  return GOOGLE_MODEL_LABELS[modelId] ?? humanizeModelId(modelId);
211
237
  }
238
+ if (connection.provider === "openai") {
239
+ return OPENAI_MODEL_LABELS[modelId] ?? humanizeModelId(modelId);
240
+ }
212
241
  return modelId;
213
242
  }
214
243