feat: integration of models.dev and proper handling of outpout tokens
This commit is contained in:
@@ -242,6 +242,18 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.setting-input-group select {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Model catalog metadata line */
|
||||
.model-catalog-info {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground, #888);
|
||||
padding: 4px 0 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.setting-toggle-visibility {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
@@ -244,6 +244,13 @@ export const SettingsView: React.FC = () => {
|
||||
const [newApiKey, setNewApiKey] = useState('');
|
||||
const [availableModels, setAvailableModels] = useState<{id: string; name: string}[]>([]);
|
||||
const [selectedModel, setSelectedModel] = useState('');
|
||||
const [modelCatalog, setModelCatalog] = useState<Map<string, {
|
||||
maxOutputTokens: number | null;
|
||||
contextWindow: number | null;
|
||||
inputPrice: number | null;
|
||||
outputPrice: number | null;
|
||||
}>>(new Map());
|
||||
const [refreshingCatalog, setRefreshingCatalog] = useState(false);
|
||||
|
||||
// Check if a section has any matching settings
|
||||
const sectionHasMatches = useCallback((sectionKeywords: string[]) => {
|
||||
@@ -395,6 +402,21 @@ export const SettingsView: React.FC = () => {
|
||||
setAvailableModels(modelsResult.models);
|
||||
setSelectedModel(modelsResult.selectedModel || '');
|
||||
}
|
||||
|
||||
// Load model catalog metadata
|
||||
const catalogResult = await window.electronAPI?.chat.getModelCatalog();
|
||||
if (catalogResult?.success && catalogResult.entries) {
|
||||
const map = new Map<string, { maxOutputTokens: number | null; contextWindow: number | null; inputPrice: number | null; outputPrice: number | null }>();
|
||||
for (const entry of catalogResult.entries) {
|
||||
map.set(entry.id, {
|
||||
maxOutputTokens: entry.maxOutputTokens,
|
||||
contextWindow: entry.contextWindow,
|
||||
inputPrice: entry.inputPrice,
|
||||
outputPrice: entry.outputPrice,
|
||||
});
|
||||
}
|
||||
setModelCatalog(map);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load AI settings:', error);
|
||||
}
|
||||
@@ -1080,6 +1102,41 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshModelCatalog = async () => {
|
||||
setRefreshingCatalog(true);
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.refreshModelCatalog();
|
||||
if (result?.success) {
|
||||
if (result.notModified) {
|
||||
showToast.success(t('settings.toast.modelCatalogUpToDate'));
|
||||
} else {
|
||||
showToast.success(t('settings.toast.modelCatalogRefreshed', { count: String(result.modelsUpdated) }));
|
||||
}
|
||||
// Reload catalog data
|
||||
const catalogResult = await window.electronAPI?.chat.getModelCatalog();
|
||||
if (catalogResult?.success && catalogResult.entries) {
|
||||
const map = new Map<string, { maxOutputTokens: number | null; contextWindow: number | null; inputPrice: number | null; outputPrice: number | null }>();
|
||||
for (const entry of catalogResult.entries) {
|
||||
map.set(entry.id, {
|
||||
maxOutputTokens: entry.maxOutputTokens,
|
||||
contextWindow: entry.contextWindow,
|
||||
inputPrice: entry.inputPrice,
|
||||
outputPrice: entry.outputPrice,
|
||||
});
|
||||
}
|
||||
setModelCatalog(map);
|
||||
}
|
||||
} else {
|
||||
showToast.error(t('settings.toast.modelCatalogRefreshFailed'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh model catalog:', error);
|
||||
showToast.error(t('settings.toast.modelCatalogRefreshFailed'));
|
||||
} finally {
|
||||
setRefreshingCatalog(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderAISettings = () => (
|
||||
<SettingSection
|
||||
id="settings-section-ai"
|
||||
@@ -1133,17 +1190,46 @@ export const SettingsView: React.FC = () => {
|
||||
label={t('settings.ai.defaultModelLabel')}
|
||||
description={t('settings.ai.defaultModelDescription')}
|
||||
>
|
||||
<select
|
||||
id="ai-model"
|
||||
value={selectedModel}
|
||||
onChange={(e) => handleModelChange(e.target.value)}
|
||||
disabled={!aiHasApiKey}
|
||||
>
|
||||
{availableModels.length === 0 && <option value="">{t('settings.ai.noModels')}</option>}
|
||||
{availableModels.map(model => (
|
||||
<option key={model.id} value={model.id}>{model.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="setting-input-group">
|
||||
<select
|
||||
id="ai-model"
|
||||
value={selectedModel}
|
||||
onChange={(e) => handleModelChange(e.target.value)}
|
||||
disabled={!aiHasApiKey}
|
||||
>
|
||||
{availableModels.length === 0 && <option value="">{t('settings.ai.noModels')}</option>}
|
||||
{availableModels.map(model => (
|
||||
<option key={model.id} value={model.id}>{model.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="secondary"
|
||||
onClick={handleRefreshModelCatalog}
|
||||
disabled={refreshingCatalog || !aiHasApiKey}
|
||||
title={t('settings.ai.refreshModelCatalog')}
|
||||
>
|
||||
{refreshingCatalog ? t('settings.ai.refreshing') : t('settings.ai.refreshModelCatalog')}
|
||||
</button>
|
||||
</div>
|
||||
{selectedModel && modelCatalog.has(selectedModel) && (() => {
|
||||
const info = modelCatalog.get(selectedModel)!;
|
||||
const parts: string[] = [];
|
||||
if (info.maxOutputTokens != null) {
|
||||
parts.push(`${t('settings.ai.modelInfoMaxOutput')}: ${info.maxOutputTokens.toLocaleString()} ${t('settings.ai.modelInfoTokens')}`);
|
||||
}
|
||||
if (info.contextWindow != null) {
|
||||
parts.push(`${t('settings.ai.modelInfoContext')}: ${info.contextWindow.toLocaleString()} ${t('settings.ai.modelInfoTokens')}`);
|
||||
}
|
||||
if (info.inputPrice != null) {
|
||||
parts.push(`${t('settings.ai.modelInfoInputPrice')}: $${info.inputPrice}${t('settings.ai.modelInfoPerMTok')}`);
|
||||
}
|
||||
if (info.outputPrice != null) {
|
||||
parts.push(`${t('settings.ai.modelInfoOutputPrice')}: $${info.outputPrice}${t('settings.ai.modelInfoPerMTok')}`);
|
||||
}
|
||||
return parts.length > 0 ? (
|
||||
<div className="model-catalog-info">{parts.join(' · ')}</div>
|
||||
) : null;
|
||||
})()}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
|
||||
@@ -725,6 +725,17 @@
|
||||
"settings.ai.systemPromptPlaceholder": "Systemanweisungen für den KI-Assistenten eingeben...",
|
||||
"settings.ai.savePrompt": "Prompt speichern",
|
||||
"settings.ai.resetPrompt": "Auf Standard zurücksetzen",
|
||||
"settings.ai.refreshModelCatalog": "Modellkatalog aktualisieren",
|
||||
"settings.ai.refreshing": "Wird aktualisiert…",
|
||||
"settings.ai.modelInfoMaxOutput": "Max. Ausgabe",
|
||||
"settings.ai.modelInfoContext": "Kontext",
|
||||
"settings.ai.modelInfoInputPrice": "Eingabe",
|
||||
"settings.ai.modelInfoOutputPrice": "Ausgabe",
|
||||
"settings.ai.modelInfoTokens": "Token",
|
||||
"settings.ai.modelInfoPerMTok": "/MTok",
|
||||
"settings.toast.modelCatalogRefreshed": "Modellkatalog aktualisiert ({{count}} Modelle)",
|
||||
"settings.toast.modelCatalogUpToDate": "Modellkatalog ist bereits aktuell",
|
||||
"settings.toast.modelCatalogRefreshFailed": "Modellkatalog konnte nicht aktualisiert werden",
|
||||
"settings.publishing.sshHostDescription": "Hostname oder IP-Adresse des SSH-Servers.",
|
||||
"settings.publishing.sshUsernameDescription": "Benutzername deines SSH-Kontos.",
|
||||
"settings.publishing.sshRemotePathDescription": "Das Zielverzeichnis auf dem Remote-Server, in das dein Blog veröffentlicht wird.",
|
||||
|
||||
@@ -725,6 +725,17 @@
|
||||
"settings.ai.systemPromptPlaceholder": "Enter system instructions for the AI assistant...",
|
||||
"settings.ai.savePrompt": "Save Prompt",
|
||||
"settings.ai.resetPrompt": "Reset to Default",
|
||||
"settings.ai.refreshModelCatalog": "Refresh Model Catalog",
|
||||
"settings.ai.refreshing": "Refreshing…",
|
||||
"settings.ai.modelInfoMaxOutput": "Max output",
|
||||
"settings.ai.modelInfoContext": "Context",
|
||||
"settings.ai.modelInfoInputPrice": "Input",
|
||||
"settings.ai.modelInfoOutputPrice": "Output",
|
||||
"settings.ai.modelInfoTokens": "tokens",
|
||||
"settings.ai.modelInfoPerMTok": "/MTok",
|
||||
"settings.toast.modelCatalogRefreshed": "Model catalog updated ({{count}} models)",
|
||||
"settings.toast.modelCatalogUpToDate": "Model catalog already up to date",
|
||||
"settings.toast.modelCatalogRefreshFailed": "Failed to refresh model catalog",
|
||||
"settings.publishing.sshHostDescription": "The SSH server hostname or IP address.",
|
||||
"settings.publishing.sshUsernameDescription": "Your SSH account username.",
|
||||
"settings.publishing.sshRemotePathDescription": "The destination directory on the remote server where your blog will be published.",
|
||||
|
||||
@@ -725,6 +725,17 @@
|
||||
"settings.ai.systemPromptPlaceholder": "Escribe aquí tu prompt del sistema…",
|
||||
"settings.ai.savePrompt": "Guardar prompt",
|
||||
"settings.ai.resetPrompt": "Restablecer prompt",
|
||||
"settings.ai.refreshModelCatalog": "Actualizar catálogo de modelos",
|
||||
"settings.ai.refreshing": "Actualizando…",
|
||||
"settings.ai.modelInfoMaxOutput": "Salida máx.",
|
||||
"settings.ai.modelInfoContext": "Contexto",
|
||||
"settings.ai.modelInfoInputPrice": "Entrada",
|
||||
"settings.ai.modelInfoOutputPrice": "Salida",
|
||||
"settings.ai.modelInfoTokens": "tokens",
|
||||
"settings.ai.modelInfoPerMTok": "/MTok",
|
||||
"settings.toast.modelCatalogRefreshed": "Catálogo actualizado ({{count}} modelos)",
|
||||
"settings.toast.modelCatalogUpToDate": "El catálogo ya está actualizado",
|
||||
"settings.toast.modelCatalogRefreshFailed": "No se pudo actualizar el catálogo",
|
||||
"settings.publishing.sshHostDescription": "Nombre de host o IP del servidor SSH.",
|
||||
"settings.publishing.sshUsernameDescription": "Nombre de usuario de SSH.",
|
||||
"settings.publishing.sshRemotePathDescription": "El directorio de destino en el servidor remoto donde se publicará tu blog.",
|
||||
|
||||
@@ -723,6 +723,17 @@
|
||||
"settings.ai.systemPromptPlaceholder": "Écrivez votre prompt système ici…",
|
||||
"settings.ai.savePrompt": "Enregistrer le prompt",
|
||||
"settings.ai.resetPrompt": "Réinitialiser le prompt",
|
||||
"settings.ai.refreshModelCatalog": "Actualiser le catalogue",
|
||||
"settings.ai.refreshing": "Actualisation…",
|
||||
"settings.ai.modelInfoMaxOutput": "Sortie max.",
|
||||
"settings.ai.modelInfoContext": "Contexte",
|
||||
"settings.ai.modelInfoInputPrice": "Entrée",
|
||||
"settings.ai.modelInfoOutputPrice": "Sortie",
|
||||
"settings.ai.modelInfoTokens": "tokens",
|
||||
"settings.ai.modelInfoPerMTok": "/MTok",
|
||||
"settings.toast.modelCatalogRefreshed": "Catalogue mis à jour ({{count}} modèles)",
|
||||
"settings.toast.modelCatalogUpToDate": "Le catalogue est déjà à jour",
|
||||
"settings.toast.modelCatalogRefreshFailed": "Échec de l'actualisation du catalogue",
|
||||
"settings.publishing.sshHostDescription": "Nom d'hôte ou IP du serveur SSH.",
|
||||
"settings.publishing.sshUsernameDescription": "Nom d'utilisateur SSH.",
|
||||
"settings.publishing.sshRemotePathDescription": "Le répertoire de destination sur le serveur distant où votre blog sera publié.",
|
||||
|
||||
@@ -723,6 +723,17 @@
|
||||
"settings.ai.systemPromptPlaceholder": "Scrivi qui il tuo prompt di sistema…",
|
||||
"settings.ai.savePrompt": "Salva prompt",
|
||||
"settings.ai.resetPrompt": "Reimposta prompt",
|
||||
"settings.ai.refreshModelCatalog": "Aggiorna catalogo modelli",
|
||||
"settings.ai.refreshing": "Aggiornamento…",
|
||||
"settings.ai.modelInfoMaxOutput": "Output max.",
|
||||
"settings.ai.modelInfoContext": "Contesto",
|
||||
"settings.ai.modelInfoInputPrice": "Input",
|
||||
"settings.ai.modelInfoOutputPrice": "Output",
|
||||
"settings.ai.modelInfoTokens": "token",
|
||||
"settings.ai.modelInfoPerMTok": "/MTok",
|
||||
"settings.toast.modelCatalogRefreshed": "Catalogo aggiornato ({{count}} modelli)",
|
||||
"settings.toast.modelCatalogUpToDate": "Il catalogo è già aggiornato",
|
||||
"settings.toast.modelCatalogRefreshFailed": "Aggiornamento del catalogo non riuscito",
|
||||
"settings.publishing.sshHostDescription": "Hostname o IP del server SSH.",
|
||||
"settings.publishing.sshUsernameDescription": "Nome utente SSH.",
|
||||
"settings.publishing.sshRemotePathDescription": "La directory di destinazione sul server remoto in cui verrà pubblicato il tuo blog.",
|
||||
|
||||
Reference in New Issue
Block a user