Compare commits

..

2 Commits

Author SHA1 Message Date
xunc lee
31644dbcd8 feat: add toggle to show unvalidated models in model selector (#413)
* feat: add toggle to show unvalidated models in model selector

Add a toggle switch in the model configuration dialog to allow users to
display models that haven't been validated. This helps users who work with
model providers that have disabled their verification endpoints.

Changes:
- Add showUnvalidatedModels field to MultiModelConfig type
- Add setShowUnvalidatedModels method to useModelConfig hook
- Add Switch toggle in model-config-dialog footer
- Update model-selector to filter based on showUnvalidatedModels setting
- Add warning icon for unvalidated models in the selector
- Add i18n translations for en/zh/ja

Closes #410

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: wrap AlertTriangle in span for title attribute

The AlertTriangle icon from lucide-react doesn't support the title prop directly.
Wrapped it in a span element to properly display the tooltip.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 12:19:59 +09:00
Dayuan Jiang
067d309927 fix: handle fork PRs in auto-format workflow (#419)
- Use head.sha instead of head_ref for checkout (works for forks)
- For fork PRs: fail with helpful message if formatting needed
- For same-repo PRs: auto-commit and push as before
2025-12-26 12:15:31 +09:00
10 changed files with 92 additions and 20 deletions

View File

@@ -14,7 +14,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: ${{ github.head_ref }} ref: ${{ github.event.pull_request.head.sha }}
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js - name: Setup Node.js
@@ -37,11 +37,21 @@ jobs:
echo "has_changes=true" >> $GITHUB_OUTPUT echo "has_changes=true" >> $GITHUB_OUTPUT
fi fi
# For fork PRs, just fail if formatting is needed (can't push to forks)
- name: Fail if fork PR needs formatting
if: steps.changes.outputs.has_changes == 'true' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::This PR has formatting issues. Please run 'npx @biomejs/biome check --write .' locally and push the changes."
git diff --stat
exit 1
# For same-repo PRs, commit and push the changes
- name: Commit changes - name: Commit changes
if: steps.changes.outputs.has_changes == 'true' if: steps.changes.outputs.has_changes == 'true' && github.event.pull_request.head.repo.full_name == github.repository
run: | run: |
git config --global user.name "github-actions[bot]" git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add . git add .
git commit -m "style: auto-format with Biome" git commit -m "style: auto-format with Biome"
git push git push origin HEAD:${{ github.head_ref }}

View File

@@ -162,6 +162,7 @@ interface ChatInputProps {
models?: FlattenedModel[] models?: FlattenedModel[]
selectedModelId?: string selectedModelId?: string
onModelSelect?: (modelId: string | undefined) => void onModelSelect?: (modelId: string | undefined) => void
showUnvalidatedModels?: boolean
onConfigureModels?: () => void onConfigureModels?: () => void
} }
@@ -183,6 +184,7 @@ export function ChatInput({
models = [], models = [],
selectedModelId, selectedModelId,
onModelSelect = () => {}, onModelSelect = () => {},
showUnvalidatedModels = false,
onConfigureModels = () => {}, onConfigureModels = () => {},
}: ChatInputProps) { }: ChatInputProps) {
const dict = useDictionary() const dict = useDictionary()
@@ -482,6 +484,7 @@ export function ChatInput({
onSelect={onModelSelect} onSelect={onModelSelect}
onConfigure={onConfigureModels} onConfigure={onConfigureModels}
disabled={isDisabled} disabled={isDisabled}
showUnvalidatedModels={showUnvalidatedModels}
/> />
<div className="w-px h-5 bg-border mx-1" /> <div className="w-px h-5 bg-border mx-1" />

View File

@@ -1071,6 +1071,7 @@ export default function ChatPanel({
models={modelConfig.models} models={modelConfig.models}
selectedModelId={modelConfig.selectedModelId} selectedModelId={modelConfig.selectedModelId}
onModelSelect={modelConfig.setSelectedModelId} onModelSelect={modelConfig.setSelectedModelId}
showUnvalidatedModels={modelConfig.showUnvalidatedModels}
onConfigureModels={() => setShowModelConfigDialog(true)} onConfigureModels={() => setShowModelConfigDialog(true)}
/> />
</footer> </footer>

View File

@@ -50,6 +50,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select" } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { useDictionary } from "@/hooks/use-dictionary" import { useDictionary } from "@/hooks/use-dictionary"
import type { UseModelConfigReturn } from "@/hooks/use-model-config" import type { UseModelConfigReturn } from "@/hooks/use-model-config"
import { formatMessage } from "@/lib/i18n/utils" import { formatMessage } from "@/lib/i18n/utils"
@@ -1447,10 +1448,23 @@ export function ModelConfigDialog({
{/* Footer */} {/* Footer */}
<div className="px-6 py-3 border-t border-border-subtle bg-surface-1/30 shrink-0"> <div className="px-6 py-3 border-t border-border-subtle bg-surface-1/30 shrink-0">
<p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5"> <div className="flex items-center justify-between">
<Key className="h-3 w-3" /> <div className="flex items-center gap-2">
{dict.modelConfig.apiKeyStored} <Switch
</p> checked={modelConfig.showUnvalidatedModels}
onCheckedChange={
modelConfig.setShowUnvalidatedModels
}
/>
<Label className="text-xs text-muted-foreground cursor-pointer">
{dict.modelConfig.showUnvalidatedModels}
</Label>
</div>
<p className="text-xs text-muted-foreground flex items-center gap-1.5">
<Key className="h-3 w-3" />
{dict.modelConfig.apiKeyStored}
</p>
</div>
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -1,6 +1,13 @@
"use client" "use client"
import { Bot, Check, ChevronDown, Server, Settings2 } from "lucide-react" import {
AlertTriangle,
Bot,
Check,
ChevronDown,
Server,
Settings2,
} from "lucide-react"
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import { import {
ModelSelectorContent, ModelSelectorContent,
@@ -26,6 +33,7 @@ interface ModelSelectorProps {
onSelect: (modelId: string | undefined) => void onSelect: (modelId: string | undefined) => void
onConfigure: () => void onConfigure: () => void
disabled?: boolean disabled?: boolean
showUnvalidatedModels?: boolean
} }
// Map our provider names to models.dev logo names // Map our provider names to models.dev logo names
@@ -67,17 +75,20 @@ export function ModelSelector({
onSelect, onSelect,
onConfigure, onConfigure,
disabled = false, disabled = false,
showUnvalidatedModels = false,
}: ModelSelectorProps) { }: ModelSelectorProps) {
const dict = useDictionary() const dict = useDictionary()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
// Only show validated models in the selector // Filter models based on showUnvalidatedModels setting
const validatedModels = useMemo( const displayModels = useMemo(() => {
() => models.filter((m) => m.validated === true), if (showUnvalidatedModels) {
[models], return models
) }
return models.filter((m) => m.validated === true)
}, [models, showUnvalidatedModels])
const groupedModels = useMemo( const groupedModels = useMemo(
() => groupModelsByProvider(validatedModels), () => groupModelsByProvider(displayModels),
[validatedModels], [displayModels],
) )
// Find selected model for display // Find selected model for display
@@ -126,7 +137,7 @@ export function ModelSelector({
/> />
<ModelSelectorList className="[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]"> <ModelSelectorList className="[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
<ModelSelectorEmpty> <ModelSelectorEmpty>
{validatedModels.length === 0 && models.length > 0 {displayModels.length === 0 && models.length > 0
? dict.modelConfig.noVerifiedModels ? dict.modelConfig.noVerifiedModels
: dict.modelConfig.noModelsFound} : dict.modelConfig.noModelsFound}
</ModelSelectorEmpty> </ModelSelectorEmpty>
@@ -191,6 +202,16 @@ export function ModelSelector({
<ModelSelectorName> <ModelSelectorName>
{model.modelId} {model.modelId}
</ModelSelectorName> </ModelSelectorName>
{model.validated !== true && (
<span
title={
dict.modelConfig
.unvalidatedModelWarning
}
>
<AlertTriangle className="ml-auto h-3 w-3 text-warning" />
</span>
)}
</ModelSelectorItem> </ModelSelectorItem>
))} ))}
</ModelSelectorGroup> </ModelSelectorGroup>
@@ -213,7 +234,9 @@ export function ModelSelector({
</ModelSelectorGroup> </ModelSelectorGroup>
{/* Info text */} {/* Info text */}
<div className="px-3 py-2 text-xs text-muted-foreground border-t"> <div className="px-3 py-2 text-xs text-muted-foreground border-t">
{dict.modelConfig.onlyVerifiedShown} {showUnvalidatedModels
? dict.modelConfig.allModelsShown
: dict.modelConfig.onlyVerifiedShown}
</div> </div>
</ModelSelectorList> </ModelSelectorList>
</ModelSelectorContent> </ModelSelectorContent>

View File

@@ -109,9 +109,11 @@ export interface UseModelConfigReturn {
models: FlattenedModel[] models: FlattenedModel[]
selectedModel: FlattenedModel | undefined selectedModel: FlattenedModel | undefined
selectedModelId: string | undefined selectedModelId: string | undefined
showUnvalidatedModels: boolean
// Actions // Actions
setSelectedModelId: (modelId: string | undefined) => void setSelectedModelId: (modelId: string | undefined) => void
setShowUnvalidatedModels: (show: boolean) => void
addProvider: (provider: ProviderName) => ProviderConfig addProvider: (provider: ProviderName) => ProviderConfig
updateProvider: ( updateProvider: (
providerId: string, providerId: string,
@@ -160,6 +162,13 @@ export function useModelConfig(): UseModelConfigReturn {
})) }))
}, []) }, [])
const setShowUnvalidatedModels = useCallback((show: boolean) => {
setConfig((prev) => ({
...prev,
showUnvalidatedModels: show,
}))
}, [])
const addProvider = useCallback( const addProvider = useCallback(
(provider: ProviderName): ProviderConfig => { (provider: ProviderName): ProviderConfig => {
const newProvider = createProviderConfig(provider) const newProvider = createProviderConfig(provider)
@@ -278,7 +287,9 @@ export function useModelConfig(): UseModelConfigReturn {
models, models,
selectedModel, selectedModel,
selectedModelId: config.selectedModelId, selectedModelId: config.selectedModelId,
showUnvalidatedModels: config.showUnvalidatedModels ?? false,
setSelectedModelId, setSelectedModelId,
setShowUnvalidatedModels,
addProvider, addProvider,
updateProvider, updateProvider,
deleteProvider, deleteProvider,

View File

@@ -243,6 +243,9 @@
"default": "Default", "default": "Default",
"serverDefault": "Server Default", "serverDefault": "Server Default",
"configureModels": "Configure Models...", "configureModels": "Configure Models...",
"onlyVerifiedShown": "Only verified models are shown" "onlyVerifiedShown": "Only verified models are shown",
"showUnvalidatedModels": "Show unvalidated models",
"allModelsShown": "All models are shown (including unvalidated)",
"unvalidatedModelWarning": "This model has not been validated"
} }
} }

View File

@@ -243,6 +243,9 @@
"default": "デフォルト", "default": "デフォルト",
"serverDefault": "サーバーデフォルト", "serverDefault": "サーバーデフォルト",
"configureModels": "モデルを設定...", "configureModels": "モデルを設定...",
"onlyVerifiedShown": "検証済みのモデルのみ表示" "onlyVerifiedShown": "検証済みのモデルのみ表示",
"showUnvalidatedModels": "未検証のモデルを表示",
"allModelsShown": "すべてのモデルを表示(未検証を含む)",
"unvalidatedModelWarning": "このモデルは検証されていません"
} }
} }

View File

@@ -243,6 +243,9 @@
"default": "默认", "default": "默认",
"serverDefault": "服务器默认", "serverDefault": "服务器默认",
"configureModels": "配置模型...", "configureModels": "配置模型...",
"onlyVerifiedShown": "仅显示已验证的模型" "onlyVerifiedShown": "仅显示已验证的模型",
"showUnvalidatedModels": "显示未验证的模型",
"allModelsShown": "显示所有模型(包括未验证的)",
"unvalidatedModelWarning": "此模型尚未验证"
} }
} }

View File

@@ -40,6 +40,7 @@ export interface MultiModelConfig {
version: 1 version: 1
providers: ProviderConfig[] providers: ProviderConfig[]
selectedModelId?: string // Currently selected model's UUID selectedModelId?: string // Currently selected model's UUID
showUnvalidatedModels?: boolean // Show models that haven't been validated
} }
// Flattened model for dropdown display // Flattened model for dropdown display