Przeglądaj źródła

feat(desktop): collapse model provider sections (#39283)

usrnk1 2 tygodni temu
rodzic
commit
95636bd3ca

+ 83 - 34
packages/app/src/components/settings-v2/models.tsx

@@ -5,9 +5,12 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
 import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
 import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
 import { type Component, For, Show } from "solid-js"
+import { createStore } from "solid-js/store"
 import { useLanguage } from "@/context/language"
 import { useModels } from "@/context/models"
+import { useServerSDK } from "@/context/server-sdk"
 import { popularProviders } from "@/hooks/use-providers"
+import { Persist, persisted } from "@/utils/persist"
 import { SettingsListV2 } from "./parts/list"
 import { SettingsRowV2 } from "./parts/row"
 import "./settings-v2.css"
@@ -19,6 +22,11 @@ const PROVIDER_ICON_SIZE = 16
 export const SettingsModelsV2: Component = () => {
   const language = useLanguage()
   const models = useModels()
+  const serverSdk = useServerSDK()
+  const [store, setStore] = persisted(
+    Persist.serverGlobal(serverSdk().scope, "settings-v2.models.providers"),
+    createStore({ collapsed: {} as Record<string, boolean> }),
+  )
 
   const list = useFilteredList<ModelItem>({
     items: (_filter) => models.list(),
@@ -94,41 +102,82 @@ export const SettingsModelsV2: Component = () => {
             }
           >
             <For each={list.grouped.latest}>
-              {(group) => (
-                <div class="settings-v2-section" data-component="settings-models-provider">
-                  <div class="settings-v2-models-group-header">
-                    <ProviderIcon
-                      id={group.category}
-                      width={PROVIDER_ICON_SIZE}
-                      height={PROVIDER_ICON_SIZE}
-                      class="settings-v2-models-provider-icon shrink-0"
-                    />
-                    <h3 class="settings-v2-section-title">{group.items[0].provider.name}</h3>
+              {(group) => {
+                const searching = () => list.filter().length > 0
+                const expanded = () => searching() || !store.collapsed[group.category]
+
+                return (
+                  <div
+                    class="settings-v2-section"
+                    data-component="settings-models-provider"
+                    data-expanded={expanded() ? "" : undefined}
+                  >
+                    <h3 class="settings-v2-models-group-header">
+                      <button
+                        type="button"
+                        class="settings-v2-models-group-trigger"
+                        aria-expanded={expanded()}
+                        disabled={searching()}
+                        onClick={() => setStore("collapsed", group.category, expanded())}
+                      >
+                        <span class="settings-v2-models-group-chevron">
+                          <Show
+                            when={expanded()}
+                            fallback={
+                              <svg width="5" height="6" viewBox="0 0 5 6" fill="none" aria-hidden="true">
+                                <path
+                                  d="M0.75194 5.31663C0.41861 5.51103 0 5.27063 0 4.88473V0.500754C0 0.114854 0.41861 -0.125577 0.75194 0.0688635L4.5096 2.26084C4.8404 2.45378 4.8404 2.93168 4.5096 3.12462L0.75194 5.31663Z"
+                                  fill="currentColor"
+                                />
+                              </svg>
+                            }
+                          >
+                            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
+                              <path
+                                d="M5.37624 6.75194C5.18184 6.41861 5.42224 6 5.80814 6H10.1921C10.578 6 10.8184 6.41861 10.624 6.75194L8.43203 10.5096C8.23909 10.8404 7.76119 10.8404 7.56825 10.5096L5.37624 6.75194Z"
+                                fill="currentColor"
+                              />
+                            </svg>
+                          </Show>
+                        </span>
+                        <span class="settings-v2-models-group-label">
+                          <ProviderIcon
+                            id={group.category}
+                            width={PROVIDER_ICON_SIZE}
+                            height={PROVIDER_ICON_SIZE}
+                            class="settings-v2-models-provider-icon shrink-0"
+                          />
+                          <span class="settings-v2-section-title">{group.items[0].provider.name}</span>
+                        </span>
+                      </button>
+                    </h3>
+                    <Show when={expanded()}>
+                      <SettingsListV2>
+                        <For each={group.items}>
+                          {(item) => {
+                            const key = { providerID: item.provider.id, modelID: item.id }
+                            return (
+                              <SettingsRowV2 title={item.name} description="">
+                                <div>
+                                  <Switch
+                                    checked={models.visible(key)}
+                                    onChange={(checked) => {
+                                      models.setVisibility(key, checked)
+                                    }}
+                                    hideLabel
+                                  >
+                                    {item.name}
+                                  </Switch>
+                                </div>
+                              </SettingsRowV2>
+                            )
+                          }}
+                        </For>
+                      </SettingsListV2>
+                    </Show>
                   </div>
-                  <SettingsListV2>
-                    <For each={group.items}>
-                      {(item) => {
-                        const key = { providerID: item.provider.id, modelID: item.id }
-                        return (
-                          <SettingsRowV2 title={item.name} description="">
-                            <div>
-                              <Switch
-                                checked={models.visible(key)}
-                                onChange={(checked) => {
-                                  models.setVisibility(key, checked)
-                                }}
-                                hideLabel
-                              >
-                                {item.name}
-                              </Switch>
-                            </div>
-                          </SettingsRowV2>
-                        )
-                      }}
-                    </For>
-                  </SettingsListV2>
-                </div>
-              )}
+                )
+              }}
             </For>
           </Show>
         </Show>

+ 56 - 2
packages/app/src/components/settings-v2/settings-v2.css

@@ -373,14 +373,64 @@
 }
 
 .settings-v2-models {
-  gap: 24px;
+  gap: 12px;
+}
+
+.settings-v2-models .settings-v2-section {
+  gap: 12px;
+}
+
+.settings-v2-models .settings-v2-section[data-expanded] {
+  padding-bottom: 12px;
 }
 
 .settings-v2-models-group-header {
+  display: flex;
+  align-items: center;
+  height: 28px;
+}
+
+.settings-v2-models-group-trigger {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  height: 28px;
+  padding: 4px 8px 4px 4px;
+  border: 0;
+  border-radius: 4px;
+  background: transparent;
+  color: var(--v2-text-text-base);
+}
+
+.settings-v2-models-group-trigger:focus-visible {
+  outline: 2px solid var(--v2-border-border-focus);
+  outline-offset: 2px;
+}
+
+@media (hover: hover) {
+  .settings-v2-models-group-trigger:not(:disabled):hover {
+    background-color: var(--v2-background-bg-layer-02);
+  }
+}
+
+.settings-v2-models-group-trigger:disabled {
+  cursor: default;
+}
+
+.settings-v2-models-group-chevron {
+  display: flex;
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+  align-items: center;
+  justify-content: center;
+  color: var(--v2-icon-icon-muted);
+}
+
+.settings-v2-models-group-label {
   display: flex;
   align-items: center;
   gap: 8px;
-  padding-bottom: 8px;
 }
 
 .settings-v2-models .settings-v2-section-title {
@@ -394,6 +444,10 @@
   color: var(--v2-icon-icon-base);
 }
 
+.settings-v2-models [data-component="settings-v2-list"] {
+  padding-inline: 16px;
+}
+
 .settings-v2-models .settings-v2-section-title + [data-component="settings-v2-list"] {
   margin-top: 0;
 }