History

Normalize branch and model selection handling

- Deduplicate terminal IDs and favorite model slugs with trim-aware iteration - Use effect-based filtering for local branch and built-in model lists - Switch stable array handling to non-mutating `toSorted`

Julius Marminge committed 6 months ago

Commit 7793bd61 parent 7f03f4d8 files · +63 −23Browse files

Showing the changes to apps/web/src (6 of 8 files). Show all

Unified · Split

6 files
  1. apps/web/src/components/ChatView.tsx +3 −2
  2. apps/web/src/components/ThreadTerminalDrawer.tsx +20 −8
  3. apps/web/src/components/settings/SettingsPanels.tsx +11 −4
  4. apps/web/src/lib/sourceControlActions.ts +1 −1
  5. apps/web/src/modelSelection.ts +8 −4
  6. apps/web/src/terminalUiStateStore.ts +10 −2
apps/web/src/components/ChatView.tsx+3−2
@@ -194,6 +194,7 @@ import { · 193 unmodified lines
194194 isVersionMismatchDismissed,
195195 resolveServerConfigVersionMismatch,
196196 } from "../versionSkew";
197+import { equals } from "effect/Equal";
197198
198199 const IMAGE_ONLY_BOOTSTRAP_PROMPT =
199200 "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]";
@@ -438,8 +439,8 @@ function terminalIdListsEqual(left: readonly string[], right: readonly string[]) · 238 unmodified lines
438439 if (left.length === 0) {
439440 return true;
440441 }
441− const sortedLeft = [...left].sort((a, b) => a.localeCompare(b));
442− const sortedRight = [...right].sort((a, b) => a.localeCompare(b));
442+ const sortedLeft = left.toSorted((a, b) => a.localeCompare(b));
443+ const sortedRight = right.toSorted((a, b) => a.localeCompare(b));
443444 for (let index = 0; index < sortedLeft.length; index += 1) {
444445 if (sortedLeft[index] !== sortedRight[index]) {
445446 return false;
apps/web/src/components/ThreadTerminalDrawer.tsx+20−8
@@ -870,7 +870,15 @@ export default function ThreadTerminalDrawer({ · 869 unmodified lines
870870 const didResizeDuringDragRef = useRef(false);
871871
872872 const normalizedTerminalIds = useMemo(() => {
873− return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))];
873+ const normalizedIds: string[] = [];
874+ const seen = new Set<string>();
875+ for (const id of terminalIds) {
876+ const trimmedId = id.trim();
877+ if (trimmedId.length === 0 || seen.has(trimmedId)) continue;
878+ seen.add(trimmedId);
879+ normalizedIds.push(trimmedId);
880+ }
881+ return normalizedIds;
874882 }, [terminalIds]);
875883
876884 const resolvedActiveTerminalId =
@@ -904,13 +912,17 @@ export default function ThreadTerminalDrawer({ · 27 unmodified lines
904912 };
905913
906914 for (const terminalGroup of terminalGroups) {
907− const nextTerminalIds = [
908− ...new Set(terminalGroup.terminalIds.map((id) => id.trim()).filter((id) => id.length > 0)),
909− ].filter((terminalId) => {
910− if (!validTerminalIdSet.has(terminalId)) return false;
911− if (assignedTerminalIds.has(terminalId)) return false;
912− return true;
913− });
915+ const nextTerminalIds: string[] = [];
916+ const seenGroupTerminalIds = new Set<string>();
917+ for (const id of terminalGroup.terminalIds) {
918+ const terminalId = id.trim();
919+ if (terminalId.length === 0) continue;
920+ if (seenGroupTerminalIds.has(terminalId)) continue;
921+ seenGroupTerminalIds.add(terminalId);
922+ if (!validTerminalIdSet.has(terminalId)) continue;
923+ if (assignedTerminalIds.has(terminalId)) continue;
924+ nextTerminalIds.push(terminalId);
925+ }
914926 if (nextTerminalIds.length === 0) continue;
915927
916928 for (const terminalId of nextTerminalIds) {
apps/web/src/components/settings/SettingsPanels.tsx+11−4
@@ -14,8 +14,10 @@ import { · 13 unmodified lines
1414 import { scopeThreadRef } from "@t3tools/client-runtime";
1515 import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings";
1616 import { createModelSelection } from "@t3tools/shared/model";
17+import * as Arr from "effect/Array";
1718 import * as Duration from "effect/Duration";
1819 import * as Equal from "effect/Equal";
20+import * as Result from "effect/Result";
1921 import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding";
2022 import {
2123 canCheckForUpdate,
@@ -1143,7 +1145,12 @@ export function ProviderSettingsPanel() { · 1121 unmodified lines
11431145 nextFavoriteModels: ReadonlyArray<string>,
11441146 ) => {
11451147 const favoriteModels = [
1146− ...new Set(nextFavoriteModels.map((slug) => slug.trim()).filter((slug) => slug.length > 0)),
1148+ ...new Set(
1149+ Arr.filterMap(nextFavoriteModels, (slug) => {
1150+ const trimmedSlug = slug.trim();
1151+ return trimmedSlug.length > 0 ? Result.succeed(trimmedSlug) : Result.failVoid;
1152+ }),
1153+ ),
11471154 ];
11481155 updateSettings({
11491156 favorites: [
@@ -1249,9 +1256,9 @@ export function ProviderSettingsPanel() { · 99 unmodified lines
12491256 hiddenModels: [],
12501257 modelOrder: [],
12511258 };
1252− const favoriteModels = (settings.favorites ?? [])
1253− .filter((favorite) => favorite.provider === row.instanceId)
1254− .map((favorite) => favorite.model);
1259+ const favoriteModels = Arr.filterMap(settings.favorites ?? [], (favorite) =>
1260+ favorite.provider === row.instanceId ? Result.succeed(favorite.model) : Result.failVoid,
1261+ );
12551262 const resetLabel = driverOption?.label ?? String(row.driver);
12561263 const headerAction =
12571264 row.isDefault && row.isDirty ? (
apps/web/src/lib/sourceControlActions.ts+1−1
@@ -204,7 +204,7 @@ export function useSourceControlActionRunning( · 203 unmodified lines
204204 scope: SourceControlActionScope,
205205 kinds: ReadonlyArray<SourceControlActionKind>,
206206 ): boolean {
207− const stableKinds = useMemo(() => [...kinds].sort(), [kinds]);
207+ const stableKinds = useMemo(() => kinds.toSorted(), [kinds]);
208208 const appActionRunning = useSyncExternalStore(
209209 subscribeActionState,
210210 () => isAnyActionRunning(stableKinds, scope),
apps/web/src/modelSelection.ts+8−4
@@ -14,6 +14,8 @@ import { · 13 unmodified lines
1414 } from "@t3tools/shared/model";
1515 import { getComposerProviderState } from "./components/chat/composerProviderState";
1616 import { UnifiedSettings } from "@t3tools/contracts/settings";
17+import * as Arr from "effect/Array";
18+import * as Result from "effect/Result";
1719 import {
1820 getDefaultServerModel,
1921 getProviderModels,
@@ -150,9 +152,9 @@ export function getAppModelOptions( · 130 unmodified lines
150152 const options: AppModelOption[] = getProviderModels(providers, provider).map(toAppModelOption);
151153 const seen = new Set(options.map((option) => option.slug));
152154 const builtInModelSlugs = new Set(
153− getProviderModels(providers, provider)
154− .filter((model) => !model.isCustom)
155− .map((model) => model.slug),
155+ Arr.filterMap(getProviderModels(providers, provider), (model) =>
156+ model.isCustom ? Result.failVoid : Result.succeed(model.slug),
157+ ),
156158 );
157159
158160 // Read from the default instance's config first (that's where edits
@@ -198,7 +200,9 @@ export function getAppModelOptionsForInstance( · 39 unmodified lines
198200 const options: AppModelOption[] = entry.models.map(toAppModelOption);
199201 const seen = new Set(options.map((option) => option.slug));
200202 const builtInModelSlugs = new Set(
201− entry.models.filter((model) => !model.isCustom).map((model) => model.slug),
203+ Arr.filterMap(entry.models, (model) =>
204+ model.isCustom ? Result.failVoid : Result.succeed(model.slug),
205+ ),
202206 );
203207
204208 const customModels = readInstanceCustomModels(settings, entry.instanceId, entry.driverKind);
apps/web/src/terminalUiStateStore.ts+10−2
@@ -58,7 +58,15 @@ function createTerminalUiStateStorage() { · 57 unmodified lines
5858 }
5959
6060 function normalizeTerminalIds(terminalIds: string[]): string[] {
61− return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))];
61+ const normalizedIds: string[] = [];
62+ const seen = new Set<string>();
63+ for (const id of terminalIds) {
64+ const trimmedId = id.trim();
65+ if (trimmedId.length === 0 || seen.has(trimmedId)) continue;
66+ seen.add(trimmedId);
67+ normalizedIds.push(trimmedId);
68+ }
69+ return normalizedIds;
6270 }
6371
6472 function fallbackGroupId(terminalId: string): string {
@@ -84,7 +92,7 @@ function findGroupIndexByTerminalId( · 19 unmodified lines
8492 }
8593
8694 function normalizeTerminalGroupIds(terminalIds: string[]): string[] {
87− return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))];
95+ return normalizeTerminalIds(terminalIds);
8896 }
8997
9098 function normalizeTerminalGroups(