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

feat(app): reintroduce model context tooltip in model selector (#35087)

Aarav Sareen 2 месяцев назад
Родитель
Сommit
2a08f8f5f4

+ 36 - 19
packages/app/src/components/dialog-select-model.tsx

@@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { Icon } from "@opencode-ai/ui/v2/icon"
 import { Icon } from "@opencode-ai/ui/v2/icon"
 import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
 import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
 import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
 import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
+import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-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"
@@ -92,6 +93,7 @@ const ModelList: Component<{
           class="w-full"
           class="w-full"
           placement="right-start"
           placement="right-start"
           gutter={12}
           gutter={12}
+          openDelay={0}
           value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
           value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
         >
         >
           {node}
           {node}
@@ -446,26 +448,41 @@ export function ModelSelectorPopoverV2(props: {
                       <MenuV2.RadioGroup value={current()}>
                       <MenuV2.RadioGroup value={current()}>
                         <For each={group.items}>
                         <For each={group.items}>
                           {(item) => (
                           {(item) => (
-                            <MenuV2.RadioItem
-                              value={modelKey(item)}
-                              data-option-key={modelKey(item)}
-                              data-selected-model={current() === modelKey(item) ? true : undefined}
-                              class="scroll-my-6"
-                              classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
-                              onMouseEnter={() => {
-                                setStore("active", modelKey(item))
-                                setTimeout(() => searchRef?.focus())
-                              }}
-                              onSelect={() => selectModel(item)}
+                            <TooltipV2
+                              class="w-full"
+                              placement="right-start"
+                              gutter={6}
+                              openDelay={0}
+                              value={
+                                <ModelTooltip
+                                  model={item}
+                                  latest={item.latest}
+                                  free={isFree(item.provider.id, item.cost)}
+                                  v2
+                                />
+                              }
                             >
                             >
-                              <span class="min-w-0 truncate">{item.name}</span>
-                              <Show when={isFree(item.provider.id, item.cost)}>
-                                <TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
-                              </Show>
-                              <Show when={item.latest}>
-                                <TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
-                              </Show>
-                            </MenuV2.RadioItem>
+                              <MenuV2.RadioItem
+                                value={modelKey(item)}
+                                data-option-key={modelKey(item)}
+                                data-selected-model={current() === modelKey(item) ? true : undefined}
+                                class="scroll-my-6 w-full"
+                                classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
+                                onMouseEnter={() => {
+                                  setStore("active", modelKey(item))
+                                  setTimeout(() => searchRef?.focus())
+                                }}
+                                onSelect={() => selectModel(item)}
+                              >
+                                <span class="min-w-0 truncate">{item.name}</span>
+                                <Show when={isFree(item.provider.id, item.cost)}>
+                                  <TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
+                                </Show>
+                                <Show when={item.latest}>
+                                  <TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
+                                </Show>
+                              </MenuV2.RadioItem>
+                            </TooltipV2>
                           )}
                           )}
                         </For>
                         </For>
                       </MenuV2.RadioGroup>
                       </MenuV2.RadioGroup>

+ 33 - 2
packages/app/src/components/model-tooltip.tsx

@@ -1,4 +1,4 @@
-import { Show, type Component } from "solid-js"
+import { Show, type Component, type JSX } from "solid-js"
 import { useLanguage } from "@/context/language"
 import { useLanguage } from "@/context/language"
 
 
 type InputKey = "text" | "image" | "audio" | "video" | "pdf"
 type InputKey = "text" | "image" | "audio" | "video" | "pdf"
@@ -23,7 +23,16 @@ type ModelInfo = {
   }
   }
 }
 }
 
 
-export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => {
+function ModelTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
+  return (
+    <div class="flex min-w-0 items-center gap-4">
+      <span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
+      <span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
+    </div>
+  )
+}
+
+export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean; v2?: boolean }> = (props) => {
   const language = useLanguage()
   const language = useLanguage()
   const sourceName = (model: ModelInfo) => {
   const sourceName = (model: ModelInfo) => {
     const value = `${model.id} ${model.name}`.toLowerCase()
     const value = `${model.id} ${model.name}`.toLowerCase()
@@ -51,6 +60,13 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
     const suffix = tags.length ? ` (${tags.join(", ")})` : ""
     const suffix = tags.length ? ` (${tags.join(", ")})` : ""
     return `${sourceName(props.model)} ${props.model.name}${suffix}`
     return `${sourceName(props.model)} ${props.model.name}${suffix}`
   }
   }
+  const name = () => {
+    const tags: Array<string> = []
+    if (props.latest) tags.push(language.t("model.tag.latest"))
+    if (props.free) tags.push(language.t("model.tag.free"))
+    const suffix = tags.length ? ` (${tags.join(", ")})` : ""
+    return `${props.model.name}${suffix}`
+  }
   const inputs = () => {
   const inputs = () => {
     if (props.model.capabilities) {
     if (props.model.capabilities) {
       const input = props.model.capabilities.input
       const input = props.model.capabilities.input
@@ -73,6 +89,21 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
       : language.t("model.tooltip.reasoning.none")
       : language.t("model.tooltip.reasoning.none")
   }
   }
   const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
   const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
+  const contextLimit = () => props.model.limit.context.toLocaleString(language.intl())
+
+  if (props.v2) {
+    return (
+      <div class="flex w-[180px] flex-col gap-2">
+        <ModelTooltipRow name={language.t("model.tooltip.model")} value={name()} />
+        <ModelTooltipRow name={language.t("model.tooltip.provider")} value={props.model.provider.name} />
+        <Show when={inputs()}>
+          {(value) => <ModelTooltipRow name={language.t("model.tooltip.inputs")} value={value()} />}
+        </Show>
+        <ModelTooltipRow name={language.t("model.tooltip.reasoning")} value={reasoning()} />
+        <ModelTooltipRow name={language.t("model.tooltip.context.label")} value={contextLimit()} />
+      </div>
+    )
+  }
 
 
   return (
   return (
     <div class="flex flex-col gap-1 py-1">
     <div class="flex flex-col gap-1 py-1">

+ 5 - 0
packages/app/src/i18n/en.ts

@@ -212,6 +212,11 @@ export const dict = {
   "model.input.video": "video",
   "model.input.video": "video",
   "model.input.pdf": "pdf",
   "model.input.pdf": "pdf",
   "model.tooltip.allows": "Allows: {{inputs}}",
   "model.tooltip.allows": "Allows: {{inputs}}",
+  "model.tooltip.context.label": "Context",
+  "model.tooltip.inputs": "Inputs",
+  "model.tooltip.model": "Model",
+  "model.tooltip.provider": "Provider",
+  "model.tooltip.reasoning": "Reasoning",
   "model.tooltip.reasoning.allowed": "Allows reasoning",
   "model.tooltip.reasoning.allowed": "Allows reasoning",
   "model.tooltip.reasoning.none": "No reasoning",
   "model.tooltip.reasoning.none": "No reasoning",
   "model.tooltip.context": "Context limit {{limit}}",
   "model.tooltip.context": "Context limit {{limit}}",