- 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`
Showing the changes to apps/web/src/components (3 of 8 files). Show all
Unified · Split
| @@ -194,6 +194,7 @@ import { · 193 unmodified lines | ||
| 194 | 194 | isVersionMismatchDismissed, |
| 195 | 195 | resolveServerConfigVersionMismatch, |
| 196 | 196 | } from "../versionSkew"; |
| 197 | +import { equals } from "effect/Equal"; | |
| 197 | 198 | |
| 198 | 199 | const IMAGE_ONLY_BOOTSTRAP_PROMPT = |
| 199 | 200 | "[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 | ||
| 438 | 439 | if (left.length === 0) { |
| 439 | 440 | return true; |
| 440 | 441 | } |
| 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)); | |
| 443 | 444 | for (let index = 0; index < sortedLeft.length; index += 1) { |
| 444 | 445 | if (sortedLeft[index] !== sortedRight[index]) { |
| 445 | 446 | return false; |
| @@ -870,7 +870,15 @@ export default function ThreadTerminalDrawer({ · 869 unmodified lines | ||
| 870 | 870 | const didResizeDuringDragRef = useRef(false); |
| 871 | 871 | |
| 872 | 872 | 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; | |
| 874 | 882 | }, [terminalIds]); |
| 875 | 883 | |
| 876 | 884 | const resolvedActiveTerminalId = |
| @@ -904,13 +912,17 @@ export default function ThreadTerminalDrawer({ · 27 unmodified lines | ||
| 904 | 912 | }; |
| 905 | 913 | |
| 906 | 914 | 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 | + } | |
| 914 | 926 | if (nextTerminalIds.length === 0) continue; |
| 915 | 927 | |
| 916 | 928 | for (const terminalId of nextTerminalIds) { |
| @@ -14,8 +14,10 @@ import { · 13 unmodified lines | ||
| 14 | 14 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 15 | 15 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; |
| 16 | 16 | import { createModelSelection } from "@t3tools/shared/model"; |
| 17 | +import * as Arr from "effect/Array"; | |
| 17 | 18 | import * as Duration from "effect/Duration"; |
| 18 | 19 | import * as Equal from "effect/Equal"; |
| 20 | +import * as Result from "effect/Result"; | |
| 19 | 21 | import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding"; |
| 20 | 22 | import { |
| 21 | 23 | canCheckForUpdate, |
| @@ -1143,7 +1145,12 @@ export function ProviderSettingsPanel() { · 1121 unmodified lines | ||
| 1143 | 1145 | nextFavoriteModels: ReadonlyArray<string>, |
| 1144 | 1146 | ) => { |
| 1145 | 1147 | 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 | + ), | |
| 1147 | 1154 | ]; |
| 1148 | 1155 | updateSettings({ |
| 1149 | 1156 | favorites: [ |
| @@ -1249,9 +1256,9 @@ export function ProviderSettingsPanel() { · 99 unmodified lines | ||
| 1249 | 1256 | hiddenModels: [], |
| 1250 | 1257 | modelOrder: [], |
| 1251 | 1258 | }; |
| 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 | + ); | |
| 1255 | 1262 | const resetLabel = driverOption?.label ?? String(row.driver); |
| 1256 | 1263 | const headerAction = |
| 1257 | 1264 | row.isDefault && row.isDirty ? ( |