Просмотр исходного кода

feat(app): improvements to model search (#34954)

Aarav Sareen 1 месяц назад
Родитель
Сommit
a9144eccf8

+ 19 - 0
packages/app/src/components/dialog-select-model-search.test.ts

@@ -0,0 +1,19 @@
+import { describe, expect, test } from "bun:test"
+import { matchesModelSearch } from "./dialog-select-model-search"
+
+describe("matchesModelSearch", () => {
+  test("matches model names across separators", () => {
+    expect(matchesModelSearch("gpt 5", ["GPT-5.5"])).toBe(true)
+    expect(matchesModelSearch("gpt-5", ["GPT-5.5"])).toBe(true)
+    expect(matchesModelSearch("gpt5", ["GPT-5.5"])).toBe(true)
+  })
+
+  test("matches any searchable model field", () => {
+    expect(matchesModelSearch("open ai", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(true)
+    expect(matchesModelSearch("gpt 5", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(true)
+  })
+
+  test("does not match unrelated searches", () => {
+    expect(matchesModelSearch("claude", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(false)
+  })
+})

+ 18 - 0
packages/app/src/components/dialog-select-model-search.ts

@@ -0,0 +1,18 @@
+export const normalizeModelSearch = (value: string) =>
+  value
+    .toLowerCase()
+    .replace(/[^\p{Letter}\p{Number}]+/gu, " ")
+    .trim()
+    .replace(/\s+/g, " ")
+
+export const compactModelSearch = (value: string) => normalizeModelSearch(value).replaceAll(" ", "")
+
+export const matchesModelSearch = (query: string, values: string[]) => {
+  const search = normalizeModelSearch(query)
+  if (!search) return true
+
+  const compactSearch = compactModelSearch(query)
+  return values.some(
+    (value) => normalizeModelSearch(value).includes(search) || compactModelSearch(value).includes(compactSearch),
+  )
+}

+ 5 - 13
packages/app/src/components/dialog-select-model.tsx

@@ -17,6 +17,7 @@ import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
 import { ModelTooltip } from "./model-tooltip"
 import { ModelTooltip } from "./model-tooltip"
 import { useLanguage } from "@/context/language"
 import { useLanguage } from "@/context/language"
 import { decode64 } from "@/utils/base64"
 import { decode64 } from "@/utils/base64"
+import { matchesModelSearch } from "./dialog-select-model-search"
 
 
 const isFree = (provider: string, cost: { input: number } | undefined) =>
 const isFree = (provider: string, cost: { input: number } | undefined) =>
   provider === "opencode" && (!cost || cost.input === 0)
   provider === "opencode" && (!cost || cost.input === 0)
@@ -242,13 +243,10 @@ export function ModelSelectorPopoverV2(props: {
       .filter((item) => (props.provider ? item.provider.id === props.provider : true)),
       .filter((item) => (props.provider ? item.provider.id === props.provider : true)),
   )
   )
   const models = createMemo(() => {
   const models = createMemo(() => {
-    const search = store.search.trim().toLowerCase()
+    const search = store.search.trim()
     const filtered = search
     const filtered = search
       ? allModels().filter(
       ? allModels().filter(
-          (item) =>
-            item.name.toLowerCase().includes(search) ||
-            item.id.toLowerCase().includes(search) ||
-            item.provider.name.toLowerCase().includes(search),
+          (item) => matchesModelSearch(search, [item.name, item.id, item.provider.name]),
         )
         )
       : allModels()
       : allModels()
 
 
@@ -333,16 +331,10 @@ export function ModelSelectorPopoverV2(props: {
     queueMicrotask(() => activeItem()?.scrollIntoView({ block: "nearest" }))
     queueMicrotask(() => activeItem()?.scrollIntoView({ block: "nearest" }))
   }
   }
   const setSearch = (value: string) => {
   const setSearch = (value: string) => {
-    const search = value.trim().toLowerCase()
+    const search = value.trim()
     const first = [...allModels()]
     const first = [...allModels()]
       .sort((a, b) => a.name.localeCompare(b.name))
       .sort((a, b) => a.name.localeCompare(b.name))
-      .find(
-        (item) =>
-          !search ||
-          item.name.toLowerCase().includes(search) ||
-          item.id.toLowerCase().includes(search) ||
-          item.provider.name.toLowerCase().includes(search),
-      )
+      .find((item) => matchesModelSearch(search, [item.name, item.id, item.provider.name]))
     setStore({ search: value, active: first ? modelKey(first) : manageKey })
     setStore({ search: value, active: first ? modelKey(first) : manageKey })
   }
   }