Feature/lmstudio provider (#30)
* chore: just a plan update * Add LM Studio as local AI provider (OpenAI-compatible, like Ollama) * Convert WebP thumbnails to JPEG before image analysis for LM Studio compatibility * Strengthen language enforcement in image analysis prompt for local models * Use i18n localized prompts for image analysis instead of English instructions * Add airplane mode (Flugmodus) with status bar toggle and offline model preferences * Fix flightmode: persist model IDs, skip network when offline, airplane icon * Auto-fallback to offline models in airplane mode for chat, title, and image analysis * Auto-select first local model as offline fallback when no explicit offline model configured * Block git fetch/pull/push and site upload in airplane mode * fix: thumbnails optimized for AI * fix: error handling in airplane mode --------- Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
@@ -520,9 +520,13 @@ const App: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
await window.electronAPI?.publish.uploadSite(prefs);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Site upload failed:', error);
|
||||
showToast.error(tr('app.uploadSiteFailed'));
|
||||
if (error?.message?.includes('Airplane mode')) {
|
||||
useAppStore.getState().showErrorModal({ message: tr('app.uploadSiteOfflineMode') });
|
||||
} else {
|
||||
showToast.error(tr('app.uploadSiteFailed'));
|
||||
}
|
||||
}
|
||||
}) || (() => {})
|
||||
);
|
||||
|
||||
@@ -15,9 +15,9 @@ interface ErrorModalProps {
|
||||
|
||||
export const ErrorModal: React.FC<ErrorModalProps> = ({ error, onClose }) => {
|
||||
const { t: tr } = useI18n();
|
||||
if (!error) return null;
|
||||
|
||||
const handleCopyStack = useCallback(async () => {
|
||||
if (!error) return;
|
||||
const textToCopy = `${error.title || tr('errorModal.error')}\n${error.message}\n\n${tr('errorModal.stackTrace')}:\n${error.stack || tr('errorModal.noStack')}`;
|
||||
try {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
@@ -32,6 +32,8 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({ error, onClose }) => {
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<div className="error-modal-backdrop" onClick={handleBackdropClick}>
|
||||
<div className="error-modal">
|
||||
|
||||
@@ -32,7 +32,7 @@ const mergeStatusFilesIncremental = (
|
||||
|
||||
export const GitSidebar: React.FC = () => {
|
||||
const { t: tr } = useI18n();
|
||||
const { activeProject, openTab, tabs, closeTab } = useAppStore();
|
||||
const { activeProject, openTab, tabs, closeTab, showErrorModal } = useAppStore();
|
||||
const [projectPath, setProjectPath] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [initializing, setInitializing] = useState(false);
|
||||
@@ -390,6 +390,10 @@ export const GitSidebar: React.FC = () => {
|
||||
recentCommitsToKeep: 2,
|
||||
});
|
||||
if (!result.success) {
|
||||
if (result.code === 'offline') {
|
||||
showErrorModal({ message: tr('gitSidebar.error.offlineMode') });
|
||||
return;
|
||||
}
|
||||
setError(result.error || tr('gitSidebar.error.actionFailed', { action }));
|
||||
setErrorGuidance('guidance' in result ? result.guidance || [] : []);
|
||||
return;
|
||||
|
||||
@@ -565,3 +565,36 @@
|
||||
.ollama-caps-table input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* LM Studio model capabilities table */
|
||||
.lmstudio-model-capabilities {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.lmstudio-model-capabilities .setting-description {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.lmstudio-caps-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.lmstudio-caps-table th,
|
||||
.lmstudio-caps-table td {
|
||||
padding: 4px 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--pico-muted-border-color, #ccc);
|
||||
}
|
||||
|
||||
.lmstudio-caps-table th:not(:first-child),
|
||||
.lmstudio-caps-table td:not(:first-child) {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.lmstudio-caps-table input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -248,6 +248,14 @@ export const SettingsView: React.FC = () => {
|
||||
const [ollamaEnabled, setOllamaEnabled] = useState(false);
|
||||
const [ollamaCapabilities, setOllamaCapabilities] = useState<Record<string, { tools: boolean; vision: boolean }>>({});
|
||||
const [ollamaModels, setOllamaModels] = useState<{id: string; name: string}[]>([]);
|
||||
const [lmstudioEnabled, setLmstudioEnabled] = useState(false);
|
||||
const [lmstudioCapabilities, setLmstudioCapabilities] = useState<Record<string, { tools: boolean; vision: boolean }>>({});
|
||||
const [lmstudioModels, setLmstudioModels] = useState<{id: string; name: string}[]>([]);
|
||||
const [offlineModeEnabled, setOfflineModeEnabled] = useState(false);
|
||||
const [offlineChatModel, setOfflineChatModel] = useState('');
|
||||
const [offlineTitleModel, setOfflineTitleModel] = useState('');
|
||||
const [offlineImageAnalysisModel, setOfflineImageAnalysisModel] = useState('');
|
||||
const [knownLocalModels, setKnownLocalModels] = useState<{id: string; name: string; provider?: string; vision?: boolean}[]>([]);
|
||||
const [titleModel, setTitleModel] = useState('claude-haiku-4-5');
|
||||
const [imageAnalysisModel, setImageAnalysisModel] = useState('claude-sonnet-4-5');
|
||||
const [availableModels, setAvailableModels] = useState<{id: string; name: string; provider?: string; vision?: boolean}[]>([]);
|
||||
@@ -432,6 +440,20 @@ export const SettingsView: React.FC = () => {
|
||||
if (models) setOllamaModels(models.map(m => ({ id: m.id, name: m.name })));
|
||||
}
|
||||
|
||||
// Load LM Studio enabled state
|
||||
const lmstudioState = await window.electronAPI?.chat.getLmstudioEnabled();
|
||||
setLmstudioEnabled(!!lmstudioState);
|
||||
|
||||
// Load LM Studio model capabilities and models list
|
||||
if (lmstudioState) {
|
||||
const [lmCaps, lmModels] = await Promise.all([
|
||||
window.electronAPI?.chat.getLmstudioModelCapabilities(),
|
||||
window.electronAPI?.chat.getLmstudioModels(),
|
||||
]);
|
||||
if (lmCaps) setLmstudioCapabilities(lmCaps);
|
||||
if (lmModels) setLmstudioModels(lmModels.map(m => ({ id: m.id, name: m.name })));
|
||||
}
|
||||
|
||||
// Load per-purpose model preferences
|
||||
const titleModelResult = await window.electronAPI?.chat.getTitleModel();
|
||||
if (titleModelResult?.success && titleModelResult.modelId) {
|
||||
@@ -442,6 +464,22 @@ export const SettingsView: React.FC = () => {
|
||||
setImageAnalysisModel(imageModelResult.modelId);
|
||||
}
|
||||
|
||||
// Load offline mode preferences
|
||||
const offlineState = await window.electronAPI?.chat.getOfflineMode();
|
||||
setOfflineModeEnabled(!!offlineState);
|
||||
const offlineChat = await window.electronAPI?.chat.getOfflineChatModel();
|
||||
if (offlineChat?.success && offlineChat.modelId) setOfflineChatModel(offlineChat.modelId);
|
||||
const offlineTitle = await window.electronAPI?.chat.getOfflineTitleModel();
|
||||
if (offlineTitle?.success && offlineTitle.modelId) setOfflineTitleModel(offlineTitle.modelId);
|
||||
const offlineImage = await window.electronAPI?.chat.getOfflineImageAnalysisModel();
|
||||
if (offlineImage?.success && offlineImage.modelId) setOfflineImageAnalysisModel(offlineImage.modelId);
|
||||
|
||||
// Load known local models (persisted, no network needed)
|
||||
try {
|
||||
const locals = await window.electronAPI?.chat.getKnownLocalModels();
|
||||
if (locals && locals.length > 0) setKnownLocalModels(locals);
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Load model catalog metadata
|
||||
const catalogResult = await window.electronAPI?.chat.getModelCatalog();
|
||||
if (catalogResult?.success && catalogResult.entries) {
|
||||
@@ -553,7 +591,7 @@ export const SettingsView: React.FC = () => {
|
||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'url', 'public', 'path', 'folder', 'location', 'data', 'language', 'author', 'default', 'preview', 'max', 'posts', 'page', 'bookmarklet', 'blogmark'];
|
||||
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
|
||||
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
|
||||
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode', 'ollama', 'local'];
|
||||
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode', 'ollama', 'lmstudio', 'lm studio', 'local'];
|
||||
const technologyKeywords = ['technology', 'python', 'runtime', 'worker', 'webworker', 'main thread', 'execution'];
|
||||
const publishingKeywords = ['publishing', 'ssh', 'deploy', 'server', 'host', 'upload', 'scp', 'rsync'];
|
||||
const dataKeywords = ['data', 'database', 'rebuild', 'maintenance', 'posts', 'media', 'scripts', 'links', 'folder', 'filesystem'];
|
||||
@@ -1210,6 +1248,55 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLmstudioToggle = async (enabled: boolean) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setLmstudioEnabled(enabled);
|
||||
if (result?.success) {
|
||||
setLmstudioEnabled(enabled);
|
||||
showToast.success(t(enabled ? 'settings.toast.lmstudioEnabled' : 'settings.toast.lmstudioDisabled'));
|
||||
|
||||
// Refresh models after toggle
|
||||
const modelsResult = await window.electronAPI?.chat.getAvailableModels();
|
||||
if (modelsResult?.success && modelsResult.models) {
|
||||
setAvailableModels(modelsResult.models);
|
||||
setSelectedModel(modelsResult.selectedModel || '');
|
||||
}
|
||||
|
||||
// Load LM Studio models and capabilities when enabling
|
||||
if (enabled) {
|
||||
const [caps, lmstudioModelsList] = await Promise.all([
|
||||
window.electronAPI?.chat.getLmstudioModelCapabilities(),
|
||||
window.electronAPI?.chat.getLmstudioModels(),
|
||||
]);
|
||||
if (caps) setLmstudioCapabilities(caps);
|
||||
if (lmstudioModelsList) setLmstudioModels(lmstudioModelsList.map(m => ({ id: m.id, name: m.name })));
|
||||
} else {
|
||||
setLmstudioModels([]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle LM Studio:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLmstudioCapabilityToggle = async (modelId: string, field: 'tools' | 'vision', value: boolean) => {
|
||||
const current = lmstudioCapabilities[modelId] ?? { tools: false, vision: false };
|
||||
const updated = { ...current, [field]: value };
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setLmstudioModelCapabilities(modelId, updated);
|
||||
if (result?.success) {
|
||||
setLmstudioCapabilities(prev => ({ ...prev, [modelId]: updated }));
|
||||
// Refresh available models to reflect vision change
|
||||
const modelsResult = await window.electronAPI?.chat.getAvailableModels();
|
||||
if (modelsResult?.success && modelsResult.models) {
|
||||
setAvailableModels(modelsResult.models);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update LM Studio model capabilities:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTitleModelChange = async (modelId: string) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setTitleModel(modelId);
|
||||
@@ -1232,6 +1319,45 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleOfflineToggle = async (enabled: boolean) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setOfflineMode(enabled);
|
||||
if (result?.success) {
|
||||
setOfflineModeEnabled(enabled);
|
||||
showToast.success(t(enabled ? 'settings.toast.offlineEnabled' : 'settings.toast.offlineDisabled'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle offline mode:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOfflineChatModelChange = async (modelId: string) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setOfflineChatModel(modelId);
|
||||
if (result?.success) setOfflineChatModel(modelId);
|
||||
} catch (error) {
|
||||
console.error('Failed to set offline chat model:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOfflineTitleModelChange = async (modelId: string) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setOfflineTitleModel(modelId);
|
||||
if (result?.success) setOfflineTitleModel(modelId);
|
||||
} catch (error) {
|
||||
console.error('Failed to set offline title model:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOfflineImageAnalysisModelChange = async (modelId: string) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setOfflineImageAnalysisModel(modelId);
|
||||
if (result?.success) setOfflineImageAnalysisModel(modelId);
|
||||
} catch (error) {
|
||||
console.error('Failed to set offline image analysis model:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelChange = async (modelId: string) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.chat.setDefaultModel(modelId);
|
||||
@@ -1299,10 +1425,26 @@ export const SettingsView: React.FC = () => {
|
||||
[availableModels, groupModelsByProvider]
|
||||
);
|
||||
|
||||
// Local-only models (for offline / airplane mode selectors)
|
||||
// Prefer knownLocalModels (persisted, always available) over filtering availableModels (needs network)
|
||||
const localModelSource = useMemo(() => {
|
||||
const fromAvailable = availableModels.filter(m => m.provider === 'ollama' || m.provider === 'lmstudio');
|
||||
return fromAvailable.length > 0 ? fromAvailable : knownLocalModels;
|
||||
}, [availableModels, knownLocalModels]);
|
||||
const groupedLocalModels = useMemo(
|
||||
() => groupModelsByProvider(localModelSource),
|
||||
[localModelSource, groupModelsByProvider]
|
||||
);
|
||||
const groupedLocalVisionModels = useMemo(
|
||||
() => groupModelsByProvider(localModelSource.filter(m => m.vision)),
|
||||
[localModelSource, groupModelsByProvider]
|
||||
);
|
||||
|
||||
const providerLabel = (provider: string) => {
|
||||
if (provider === 'anthropic' || provider === 'openai' || provider === 'google' || provider === 'other') return t('settings.ai.providerOpenCode');
|
||||
if (provider === 'mistral') return t('settings.ai.providerMistral');
|
||||
if (provider === 'ollama') return t('settings.ai.providerOllama');
|
||||
if (provider === 'lmstudio') return t('settings.ai.providerLmstudio');
|
||||
return provider;
|
||||
};
|
||||
|
||||
@@ -1472,17 +1614,120 @@ export const SettingsView: React.FC = () => {
|
||||
)}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="ai-lmstudio"
|
||||
label={t('settings.ai.lmstudioLabel')}
|
||||
description={t('settings.ai.lmstudioDescription')}
|
||||
>
|
||||
<div className="setting-input-group">
|
||||
<label className="toggle-label">
|
||||
<input
|
||||
id="ai-lmstudio"
|
||||
type="checkbox"
|
||||
checked={lmstudioEnabled}
|
||||
onChange={(e) => handleLmstudioToggle(e.target.checked)}
|
||||
/>
|
||||
{t('settings.ai.lmstudioEnable')}
|
||||
</label>
|
||||
{lmstudioEnabled && (
|
||||
<span className="setting-status-badge success">{t('settings.ai.configured')}</span>
|
||||
)}
|
||||
</div>
|
||||
{lmstudioEnabled && lmstudioModels.length > 0 && (
|
||||
<div className="lmstudio-model-capabilities">
|
||||
<small className="setting-description">{t('settings.ai.lmstudioCapabilitiesDescription')}</small>
|
||||
<table className="lmstudio-caps-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('settings.ai.lmstudioCapModel')}</th>
|
||||
<th>{t('settings.ai.lmstudioCapTools')}</th>
|
||||
<th>{t('settings.ai.lmstudioCapVision')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{lmstudioModels.map(m => {
|
||||
const caps = lmstudioCapabilities[m.id] ?? { tools: false, vision: false };
|
||||
return (
|
||||
<tr key={m.id}>
|
||||
<td>{m.name}</td>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={caps.tools}
|
||||
onChange={(e) => handleLmstudioCapabilityToggle(m.id, 'tools', e.target.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={caps.vision}
|
||||
onChange={(e) => handleLmstudioCapabilityToggle(m.id, 'vision', e.target.checked)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="ai-offline"
|
||||
label={t('settings.ai.offlineLabel')}
|
||||
description={t('settings.ai.offlineDescription')}
|
||||
>
|
||||
<div className="setting-input-group">
|
||||
<label className="toggle-label">
|
||||
<input
|
||||
id="ai-offline"
|
||||
type="checkbox"
|
||||
checked={offlineModeEnabled}
|
||||
onChange={(e) => handleOfflineToggle(e.target.checked)}
|
||||
disabled={!ollamaEnabled && !lmstudioEnabled}
|
||||
/>
|
||||
{t('settings.ai.offlineEnable')}
|
||||
</label>
|
||||
{offlineModeEnabled && (
|
||||
<span className="setting-status-badge success">{t('settings.ai.configured')}</span>
|
||||
)}
|
||||
</div>
|
||||
{!ollamaEnabled && !lmstudioEnabled && (
|
||||
<small className="setting-description">{t('settings.ai.offlineNoLocalProviders')}</small>
|
||||
)}
|
||||
{offlineModeEnabled && (ollamaEnabled || lmstudioEnabled) && (
|
||||
<div className="offline-model-preferences">
|
||||
<div className="setting-field">
|
||||
<label htmlFor="ai-offline-chat-model">{t('settings.ai.offlineChatModel')}</label>
|
||||
<small className="setting-description">{t('settings.ai.offlineChatModelDescription')}</small>
|
||||
{renderModelSelect('ai-offline-chat-model', offlineChatModel, handleOfflineChatModelChange, false, groupedLocalModels)}
|
||||
</div>
|
||||
<div className="setting-field">
|
||||
<label htmlFor="ai-offline-title-model">{t('settings.ai.offlineTitleModel')}</label>
|
||||
<small className="setting-description">{t('settings.ai.offlineTitleModelDescription')}</small>
|
||||
{renderModelSelect('ai-offline-title-model', offlineTitleModel, handleOfflineTitleModelChange, false, groupedLocalModels)}
|
||||
</div>
|
||||
<div className="setting-field">
|
||||
<label htmlFor="ai-offline-image-model">{t('settings.ai.offlineImageAnalysisModel')}</label>
|
||||
<small className="setting-description">{t('settings.ai.offlineImageAnalysisModelDescription')}</small>
|
||||
{renderModelSelect('ai-offline-image-model', offlineImageAnalysisModel, handleOfflineImageAnalysisModelChange, false, groupedLocalVisionModels)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="ai-model"
|
||||
label={t('settings.ai.defaultModelLabel')}
|
||||
description={t('settings.ai.defaultModelDescription')}
|
||||
>
|
||||
<div className="setting-input-group">
|
||||
{renderModelSelect('ai-model', selectedModel, handleModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled)}
|
||||
{renderModelSelect('ai-model', selectedModel, handleModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled && !lmstudioEnabled)}
|
||||
<button
|
||||
className="secondary"
|
||||
onClick={handleRefreshModelCatalog}
|
||||
disabled={refreshingCatalog || (!aiHasApiKey && !aiHasMistralKey && !ollamaEnabled)}
|
||||
disabled={refreshingCatalog || (!aiHasApiKey && !aiHasMistralKey && !ollamaEnabled && !lmstudioEnabled)}
|
||||
title={t('settings.ai.refreshModelCatalog')}
|
||||
>
|
||||
{refreshingCatalog ? t('settings.ai.refreshing') : t('settings.ai.refreshModelCatalog')}
|
||||
@@ -1514,7 +1759,7 @@ export const SettingsView: React.FC = () => {
|
||||
label={t('settings.ai.titleModelLabel')}
|
||||
description={t('settings.ai.titleModelDescription')}
|
||||
>
|
||||
{renderModelSelect('ai-title-model', titleModel, handleTitleModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled)}
|
||||
{renderModelSelect('ai-title-model', titleModel, handleTitleModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled && !lmstudioEnabled)}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
@@ -1522,7 +1767,7 @@ export const SettingsView: React.FC = () => {
|
||||
label={t('settings.ai.imageAnalysisModelLabel')}
|
||||
description={t('settings.ai.imageAnalysisModelDescription')}
|
||||
>
|
||||
{renderModelSelect('ai-image-analysis-model', imageAnalysisModel, handleImageAnalysisModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled, groupedVisionModels)}
|
||||
{renderModelSelect('ai-image-analysis-model', imageAnalysisModel, handleImageAnalysisModelChange, !aiHasApiKey && !aiHasMistralKey && !ollamaEnabled && !lmstudioEnabled, groupedVisionModels)}
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
|
||||
@@ -123,6 +123,19 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.status-bar-item.offline-badge {
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
font-size: 13px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.status-bar-item.offline-badge.active {
|
||||
background-color: var(--vscode-notificationsWarningIcon-foreground);
|
||||
border-radius: 3px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.status-bar-language-select {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useAppStore } from '../../store';
|
||||
import { ProjectSelector } from '../ProjectSelector';
|
||||
import { getRendererPicoTheme } from '../../utils/picoTheme';
|
||||
@@ -27,6 +27,22 @@ export const StatusBar: React.FC = () => {
|
||||
} = useAppStore();
|
||||
|
||||
const [selectedPostStatus, setSelectedPostStatus] = useState<string | null>(null);
|
||||
const [offlineMode, setOfflineMode] = useState(false);
|
||||
|
||||
// Fetch offline mode state on mount
|
||||
useEffect(() => {
|
||||
window.electronAPI?.chat?.getOfflineMode().then(setOfflineMode).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const toggleOfflineMode = useCallback(async () => {
|
||||
const newValue = !offlineMode;
|
||||
try {
|
||||
await window.electronAPI?.chat?.setOfflineMode(newValue);
|
||||
setOfflineMode(newValue);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [offlineMode]);
|
||||
|
||||
// Fetch selected post status from database
|
||||
useEffect(() => {
|
||||
@@ -96,6 +112,18 @@ export const StatusBar: React.FC = () => {
|
||||
<span>{t('statusBar.theme', { theme: activeTheme })}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`status-bar-item offline-badge${offlineMode ? ' active' : ''}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
data-testid="statusbar-offline-toggle"
|
||||
title={t('statusBar.offlineModeTooltip')}
|
||||
onClick={toggleOfflineMode}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleOfflineMode(); } }}
|
||||
>
|
||||
<span>✈</span>
|
||||
</div>
|
||||
|
||||
<div className="status-bar-item language-badge">
|
||||
<span>{t('statusBar.ui')}</span>
|
||||
<select
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"app.sitemapGenerationFailed": "Sitemap-Erstellung fehlgeschlagen",
|
||||
"app.calendarRegenerationFailed": "Kalender-Neuerstellung fehlgeschlagen",
|
||||
"app.uploadSiteFailed": "Website-Upload fehlgeschlagen",
|
||||
"app.uploadSiteOfflineMode": "Website-Upload ist im Flugmodus nicht verfügbar.",
|
||||
"app.uploadSiteNoCredentials": "Bitte konfigurieren Sie zuerst die SSH-Zugangsdaten in den Einstellungen.",
|
||||
"app.previewOpenFailed": "Ausgewählte Beitragsvorschau konnte nicht geöffnet werden",
|
||||
"app.metadataDiff": "Metadaten-Diff",
|
||||
@@ -314,6 +315,7 @@
|
||||
"gitSidebar.error.loadRepoStatus": "Repository-Status konnte nicht geladen werden.",
|
||||
"gitSidebar.error.initFailed": "Git-Repository konnte nicht initialisiert werden.",
|
||||
"gitSidebar.error.actionFailed": "Fehler beim {action}.",
|
||||
"gitSidebar.error.offlineMode": "Diese Aktion ist im Flugmodus nicht verfügbar.",
|
||||
"gitSidebar.error.commitFailed": "Änderungen konnten nicht committet werden.",
|
||||
"gitSidebar.progress.preparingInit": "Repository-Initialisierung wird vorbereitet...",
|
||||
"gitSidebar.progress.pushingRemote": "Commits werden zum Remote übertragen... das kann bei großen Uploads eine Weile dauern.",
|
||||
@@ -742,6 +744,7 @@
|
||||
"settings.ai.providerOpenCode": "OpenCode",
|
||||
"settings.ai.providerMistral": "Mistral",
|
||||
"settings.ai.providerOllama": "Ollama (Lokal)",
|
||||
"settings.ai.providerLmstudio": "LM Studio (Lokal)",
|
||||
"settings.ai.providerOther": "Andere",
|
||||
"settings.ai.ollamaLabel": "Ollama (Lokale Modelle)",
|
||||
"settings.ai.ollamaDescription": "Verbinde dich mit einer lokal laufenden Ollama-Instanz, um lokale KI-Modelle zu verwenden.",
|
||||
@@ -756,6 +759,28 @@
|
||||
"settings.toast.modelCatalogRefreshFailed": "Modellkatalog konnte nicht aktualisiert werden",
|
||||
"settings.toast.ollamaEnabled": "Ollama aktiviert",
|
||||
"settings.toast.ollamaDisabled": "Ollama deaktiviert",
|
||||
"settings.ai.lmstudioLabel": "LM Studio (Lokale Modelle)",
|
||||
"settings.ai.lmstudioDescription": "Verbinde dich mit einer lokal laufenden LM Studio-Instanz, um lokale KI-Modelle zu verwenden.",
|
||||
"settings.ai.lmstudioEnable": "LM Studio aktivieren",
|
||||
"settings.ai.lmstudioCapabilitiesDescription": "Fähigkeiten für jedes LM Studio-Modell konfigurieren. Tools für Funktionsaufrufe oder Vision für Bildanalyse aktivieren.",
|
||||
"settings.ai.lmstudioCapModel": "Modell",
|
||||
"settings.ai.lmstudioCapTools": "Tools",
|
||||
"settings.ai.lmstudioCapVision": "Vision",
|
||||
"settings.toast.lmstudioEnabled": "LM Studio aktiviert",
|
||||
"settings.toast.lmstudioDisabled": "LM Studio deaktiviert",
|
||||
"settings.ai.offlineLabel": "Flugmodus",
|
||||
"settings.ai.offlineDescription": "Wenn aktiviert, werden nur lokal gehostete Modelle (Ollama, LM Studio) verwendet. Cloud-Anbieter werden deaktiviert.",
|
||||
"settings.ai.offlineEnable": "Flugmodus aktivieren",
|
||||
"settings.ai.offlineChatModel": "Offline-Chat-Modell",
|
||||
"settings.ai.offlineChatModelDescription": "Modell für Chat-Gespräche im Flugmodus.",
|
||||
"settings.ai.offlineTitleModel": "Offline-Titelmodell",
|
||||
"settings.ai.offlineTitleModelDescription": "Modell für die Titelgenerierung im Flugmodus.",
|
||||
"settings.ai.offlineImageAnalysisModel": "Offline-Bildanalysemodell",
|
||||
"settings.ai.offlineImageAnalysisModelDescription": "Modell für die Bildanalyse im Flugmodus.",
|
||||
"settings.ai.offlineNoLocalProviders": "Keine lokalen Anbieter aktiviert. Aktiviere zuerst Ollama oder LM Studio.",
|
||||
"settings.ai.offlineNoLocalModels": "Keine lokalen Modelle verfügbar",
|
||||
"settings.toast.offlineEnabled": "Flugmodus aktiviert",
|
||||
"settings.toast.offlineDisabled": "Flugmodus deaktiviert",
|
||||
"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.",
|
||||
@@ -891,6 +916,9 @@
|
||||
"statusBar.theme": "Theme: {theme}",
|
||||
"statusBar.ui": "UI",
|
||||
"statusBar.uiLanguage": "UI-Sprache",
|
||||
"statusBar.offlineMode": "Flugmodus",
|
||||
"statusBar.offlineModeActive": "Flugmodus (aktiv)",
|
||||
"statusBar.offlineModeTooltip": "Klicken zum Umschalten des Flugmodus",
|
||||
"windowTitleBar.toggleSidebar": "Seitenleiste umschalten",
|
||||
"windowTitleBar.hideSidebar": "Seitenleiste ausblenden (Ctrl+B)",
|
||||
"windowTitleBar.showSidebar": "Seitenleiste anzeigen (Ctrl+B)",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"app.sitemapGenerationFailed": "Sitemap generation failed",
|
||||
"app.calendarRegenerationFailed": "Calendar regeneration failed",
|
||||
"app.uploadSiteFailed": "Site upload failed",
|
||||
"app.uploadSiteOfflineMode": "Site upload is blocked while airplane mode is active.",
|
||||
"app.uploadSiteNoCredentials": "Please configure SSH publishing credentials in Settings first.",
|
||||
"app.previewOpenFailed": "Failed to open selected post preview",
|
||||
"app.metadataDiff": "Metadata Diff",
|
||||
@@ -314,6 +315,7 @@
|
||||
"gitSidebar.error.loadRepoStatus": "Unable to load repository status.",
|
||||
"gitSidebar.error.initFailed": "Failed to initialize git repository.",
|
||||
"gitSidebar.error.actionFailed": "Failed to {action}.",
|
||||
"gitSidebar.error.offlineMode": "This action is blocked while airplane mode is active.",
|
||||
"gitSidebar.error.commitFailed": "Failed to commit changes.",
|
||||
"gitSidebar.progress.preparingInit": "Preparing repository initialization...",
|
||||
"gitSidebar.progress.pushingRemote": "Pushing commits to remote... this can take a while for large uploads.",
|
||||
@@ -742,6 +744,7 @@
|
||||
"settings.ai.providerOpenCode": "OpenCode",
|
||||
"settings.ai.providerMistral": "Mistral",
|
||||
"settings.ai.providerOllama": "Ollama (Local)",
|
||||
"settings.ai.providerLmstudio": "LM Studio (Local)",
|
||||
"settings.ai.providerOther": "Other",
|
||||
"settings.ai.ollamaLabel": "Ollama (Local Models)",
|
||||
"settings.ai.ollamaDescription": "Connect to a locally running Ollama instance to use local AI models.",
|
||||
@@ -756,6 +759,28 @@
|
||||
"settings.toast.modelCatalogRefreshFailed": "Failed to refresh model catalog",
|
||||
"settings.toast.ollamaEnabled": "Ollama enabled",
|
||||
"settings.toast.ollamaDisabled": "Ollama disabled",
|
||||
"settings.ai.lmstudioLabel": "LM Studio (Local Models)",
|
||||
"settings.ai.lmstudioDescription": "Connect to a locally running LM Studio instance to use local AI models.",
|
||||
"settings.ai.lmstudioEnable": "Enable LM Studio",
|
||||
"settings.ai.lmstudioCapabilitiesDescription": "Configure capabilities for each LM Studio model. Enable tools for function calling or vision for image analysis.",
|
||||
"settings.ai.lmstudioCapModel": "Model",
|
||||
"settings.ai.lmstudioCapTools": "Tools",
|
||||
"settings.ai.lmstudioCapVision": "Vision",
|
||||
"settings.toast.lmstudioEnabled": "LM Studio enabled",
|
||||
"settings.toast.lmstudioDisabled": "LM Studio disabled",
|
||||
"settings.ai.offlineLabel": "Airplane Mode",
|
||||
"settings.ai.offlineDescription": "When enabled, only locally hosted models (Ollama, LM Studio) are used. Cloud providers are disabled.",
|
||||
"settings.ai.offlineEnable": "Enable Airplane Mode",
|
||||
"settings.ai.offlineChatModel": "Offline Chat Model",
|
||||
"settings.ai.offlineChatModelDescription": "Model used for chat conversations when in airplane mode.",
|
||||
"settings.ai.offlineTitleModel": "Offline Title Model",
|
||||
"settings.ai.offlineTitleModelDescription": "Model used for title generation when in airplane mode.",
|
||||
"settings.ai.offlineImageAnalysisModel": "Offline Image Analysis Model",
|
||||
"settings.ai.offlineImageAnalysisModelDescription": "Model used for image analysis when in airplane mode.",
|
||||
"settings.ai.offlineNoLocalProviders": "No local providers enabled. Enable Ollama or LM Studio first.",
|
||||
"settings.ai.offlineNoLocalModels": "No local models available",
|
||||
"settings.toast.offlineEnabled": "Airplane mode enabled",
|
||||
"settings.toast.offlineDisabled": "Airplane mode disabled",
|
||||
"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.",
|
||||
@@ -891,6 +916,9 @@
|
||||
"statusBar.theme": "Theme: {theme}",
|
||||
"statusBar.ui": "UI",
|
||||
"statusBar.uiLanguage": "UI language",
|
||||
"statusBar.offlineMode": "Airplane Mode",
|
||||
"statusBar.offlineModeActive": "Airplane Mode (active)",
|
||||
"statusBar.offlineModeTooltip": "Click to toggle airplane mode",
|
||||
"windowTitleBar.toggleSidebar": "Toggle Sidebar",
|
||||
"windowTitleBar.hideSidebar": "Hide Sidebar (Ctrl+B)",
|
||||
"windowTitleBar.showSidebar": "Show Sidebar (Ctrl+B)",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"app.sitemapGenerationFailed": "La generación del sitemap falló",
|
||||
"app.calendarRegenerationFailed": "La regeneración del calendario falló",
|
||||
"app.uploadSiteFailed": "Error al subir el sitio",
|
||||
"app.uploadSiteOfflineMode": "La subida del sitio no está disponible en modo avión.",
|
||||
"app.uploadSiteNoCredentials": "Configure primero las credenciales SSH en Configuración.",
|
||||
"app.previewOpenFailed": "No se pudo abrir la vista previa de la entrada seleccionada",
|
||||
"app.metadataDiff": "Diferencia de Metadatos",
|
||||
@@ -314,6 +315,7 @@
|
||||
"gitSidebar.error.loadRepoStatus": "No se pudo cargar el estado del repositorio.",
|
||||
"gitSidebar.error.initFailed": "No se pudo inicializar el repositorio Git.",
|
||||
"gitSidebar.error.actionFailed": "No se pudo {action}.",
|
||||
"gitSidebar.error.offlineMode": "Esta acción no está disponible en modo avión.",
|
||||
"gitSidebar.error.commitFailed": "No se pudieron confirmar los cambios.",
|
||||
"gitSidebar.progress.preparingInit": "Preparando inicialización del repositorio...",
|
||||
"gitSidebar.progress.pushingRemote": "Enviando commits al remoto... esto puede tardar con cargas grandes.",
|
||||
@@ -742,6 +744,7 @@
|
||||
"settings.ai.providerOpenCode": "OpenCode",
|
||||
"settings.ai.providerMistral": "Mistral",
|
||||
"settings.ai.providerOllama": "Ollama (Local)",
|
||||
"settings.ai.providerLmstudio": "LM Studio (Local)",
|
||||
"settings.ai.providerOther": "Otro",
|
||||
"settings.ai.ollamaLabel": "Ollama (Modelos locales)",
|
||||
"settings.ai.ollamaDescription": "Conéctate a una instancia local de Ollama para usar modelos de IA locales.",
|
||||
@@ -756,6 +759,28 @@
|
||||
"settings.toast.modelCatalogRefreshFailed": "No se pudo actualizar el catálogo",
|
||||
"settings.toast.ollamaEnabled": "Ollama activado",
|
||||
"settings.toast.ollamaDisabled": "Ollama desactivado",
|
||||
"settings.ai.lmstudioLabel": "LM Studio (Modelos locales)",
|
||||
"settings.ai.lmstudioDescription": "Conéctate a una instancia local de LM Studio para usar modelos de IA locales.",
|
||||
"settings.ai.lmstudioEnable": "Activar LM Studio",
|
||||
"settings.ai.lmstudioCapabilitiesDescription": "Configurar las capacidades de cada modelo LM Studio. Activar herramientas para llamadas a funciones o visión para análisis de imágenes.",
|
||||
"settings.ai.lmstudioCapModel": "Modelo",
|
||||
"settings.ai.lmstudioCapTools": "Herramientas",
|
||||
"settings.ai.lmstudioCapVision": "Visión",
|
||||
"settings.toast.lmstudioEnabled": "LM Studio activado",
|
||||
"settings.toast.lmstudioDisabled": "LM Studio desactivado",
|
||||
"settings.ai.offlineLabel": "Modo avión",
|
||||
"settings.ai.offlineDescription": "Cuando está activado, solo se usan modelos alojados localmente (Ollama, LM Studio). Los proveedores en la nube se desactivan.",
|
||||
"settings.ai.offlineEnable": "Activar modo avión",
|
||||
"settings.ai.offlineChatModel": "Modelo de chat sin conexión",
|
||||
"settings.ai.offlineChatModelDescription": "Modelo usado para conversaciones en modo avión.",
|
||||
"settings.ai.offlineTitleModel": "Modelo de título sin conexión",
|
||||
"settings.ai.offlineTitleModelDescription": "Modelo usado para generar títulos en modo avión.",
|
||||
"settings.ai.offlineImageAnalysisModel": "Modelo de análisis de imagen sin conexión",
|
||||
"settings.ai.offlineImageAnalysisModelDescription": "Modelo usado para el análisis de imágenes en modo avión.",
|
||||
"settings.ai.offlineNoLocalProviders": "No hay proveedores locales activados. Activa primero Ollama o LM Studio.",
|
||||
"settings.ai.offlineNoLocalModels": "No hay modelos locales disponibles",
|
||||
"settings.toast.offlineEnabled": "Modo avión activado",
|
||||
"settings.toast.offlineDisabled": "Modo avión desactivado",
|
||||
"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.",
|
||||
@@ -891,6 +916,9 @@
|
||||
"statusBar.theme": "Tema: {theme}",
|
||||
"statusBar.ui": "UI",
|
||||
"statusBar.uiLanguage": "Idioma de la interfaz",
|
||||
"statusBar.offlineMode": "Modo avión",
|
||||
"statusBar.offlineModeActive": "Modo avión (activo)",
|
||||
"statusBar.offlineModeTooltip": "Haz clic para activar/desactivar el modo avión",
|
||||
"windowTitleBar.toggleSidebar": "Alternar barra lateral",
|
||||
"windowTitleBar.hideSidebar": "Ocultar barra lateral",
|
||||
"windowTitleBar.showSidebar": "Mostrar barra lateral",
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
"app.databaseRebuildFailed": "Échec de la reconstruction de la base de données",
|
||||
"app.textReindexFailed": "Échec de la réindexation du texte",
|
||||
"app.sitemapGenerationFailed": "Échec de la génération du sitemap",
|
||||
"app.calendarRegenerationFailed": "Échec de la régénération du calendrier", "app.uploadSiteFailed": "Échec de la publication du site",
|
||||
"app.uploadSiteNoCredentials": "Veuillez d'abord configurer les identifiants SSH dans les paramètres.", "app.previewOpenFailed": "Impossible d’ouvrir l’aperçu de l’article sélectionné",
|
||||
"app.calendarRegenerationFailed": "Échec de la régénération du calendrier",
|
||||
"app.uploadSiteFailed": "Échec de la publication du site",
|
||||
"app.uploadSiteOfflineMode": "La publication du site est bloquée en mode avion.",
|
||||
"app.uploadSiteNoCredentials": "Veuillez d'abord configurer les identifiants SSH dans les paramètres.",
|
||||
"app.previewOpenFailed": "Impossible d’ouvrir l’aperçu de l’article sélectionné",
|
||||
"app.metadataDiff": "Diff Métadonnées",
|
||||
"app.importComplete": "Import terminé : {posts} articles, {media} fichiers média",
|
||||
"siteValidation.tabTitle": "Validation du site",
|
||||
@@ -312,6 +315,7 @@
|
||||
"gitSidebar.error.loadRepoStatus": "Impossible de charger l’état du dépôt.",
|
||||
"gitSidebar.error.initFailed": "Impossible d’initialiser le dépôt Git.",
|
||||
"gitSidebar.error.actionFailed": "Échec de {action}.",
|
||||
"gitSidebar.error.offlineMode": "Cette action est bloquée en mode avion.",
|
||||
"gitSidebar.error.commitFailed": "Impossible de valider les modifications.",
|
||||
"gitSidebar.progress.preparingInit": "Préparation de l’initialisation du dépôt...",
|
||||
"gitSidebar.progress.pushingRemote": "Envoi des commits vers le distant... cela peut prendre un moment pour les gros envois.",
|
||||
@@ -740,6 +744,7 @@
|
||||
"settings.ai.providerOpenCode": "OpenCode",
|
||||
"settings.ai.providerMistral": "Mistral",
|
||||
"settings.ai.providerOllama": "Ollama (Local)",
|
||||
"settings.ai.providerLmstudio": "LM Studio (Local)",
|
||||
"settings.ai.providerOther": "Autre",
|
||||
"settings.ai.ollamaLabel": "Ollama (Modèles locaux)",
|
||||
"settings.ai.ollamaDescription": "Connectez-vous à une instance Ollama locale pour utiliser des modèles d'IA locaux.",
|
||||
@@ -754,6 +759,28 @@
|
||||
"settings.toast.modelCatalogRefreshFailed": "Échec de l'actualisation du catalogue",
|
||||
"settings.toast.ollamaEnabled": "Ollama activé",
|
||||
"settings.toast.ollamaDisabled": "Ollama désactivé",
|
||||
"settings.ai.lmstudioLabel": "LM Studio (Modèles locaux)",
|
||||
"settings.ai.lmstudioDescription": "Connectez-vous à une instance LM Studio locale pour utiliser des modèles d'IA locaux.",
|
||||
"settings.ai.lmstudioEnable": "Activer LM Studio",
|
||||
"settings.ai.lmstudioCapabilitiesDescription": "Configurer les capacités de chaque modèle LM Studio. Activer les outils pour les appels de fonctions ou la vision pour l'analyse d'images.",
|
||||
"settings.ai.lmstudioCapModel": "Modèle",
|
||||
"settings.ai.lmstudioCapTools": "Outils",
|
||||
"settings.ai.lmstudioCapVision": "Vision",
|
||||
"settings.toast.lmstudioEnabled": "LM Studio activé",
|
||||
"settings.toast.lmstudioDisabled": "LM Studio désactivé",
|
||||
"settings.ai.offlineLabel": "Mode avion",
|
||||
"settings.ai.offlineDescription": "Lorsqu'il est activé, seuls les modèles hébergés localement (Ollama, LM Studio) sont utilisés. Les fournisseurs cloud sont désactivés.",
|
||||
"settings.ai.offlineEnable": "Activer le mode avion",
|
||||
"settings.ai.offlineChatModel": "Modèle de chat hors ligne",
|
||||
"settings.ai.offlineChatModelDescription": "Modèle utilisé pour les conversations en mode avion.",
|
||||
"settings.ai.offlineTitleModel": "Modèle de titre hors ligne",
|
||||
"settings.ai.offlineTitleModelDescription": "Modèle utilisé pour la génération de titres en mode avion.",
|
||||
"settings.ai.offlineImageAnalysisModel": "Modèle d'analyse d'image hors ligne",
|
||||
"settings.ai.offlineImageAnalysisModelDescription": "Modèle utilisé pour l'analyse d'images en mode avion.",
|
||||
"settings.ai.offlineNoLocalProviders": "Aucun fournisseur local activé. Activez d'abord Ollama ou LM Studio.",
|
||||
"settings.ai.offlineNoLocalModels": "Aucun modèle local disponible",
|
||||
"settings.toast.offlineEnabled": "Mode avion activé",
|
||||
"settings.toast.offlineDisabled": "Mode avion désactivé",
|
||||
"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é.",
|
||||
@@ -889,6 +916,9 @@
|
||||
"statusBar.theme": "Thème : {theme}",
|
||||
"statusBar.ui": "UI",
|
||||
"statusBar.uiLanguage": "Langue de l’interface",
|
||||
"statusBar.offlineMode": "Mode avion",
|
||||
"statusBar.offlineModeActive": "Mode avion (actif)",
|
||||
"statusBar.offlineModeTooltip": "Cliquer pour basculer le mode avion",
|
||||
"windowTitleBar.toggleSidebar": "Basculer la barre latérale",
|
||||
"windowTitleBar.hideSidebar": "Masquer la barre latérale",
|
||||
"windowTitleBar.showSidebar": "Afficher la barre latérale",
|
||||
@@ -1005,9 +1035,7 @@
|
||||
"importAnalysis.usedIn": "Utilisé dans : {items}{more}",
|
||||
"importAnalysis.moreSuffix": ", +{count} de plus",
|
||||
"importAnalysis.noParameters": "(aucun paramètre)",
|
||||
|
||||
"sidebar.nav.mcp": "Serveur MCP",
|
||||
|
||||
"settings.mcp.title": "Serveur MCP",
|
||||
"settings.mcp.description": "Configurez le serveur Model Context Protocol qui permet aux agents de programmation IA d'interagir avec votre blog.",
|
||||
"settings.mcp.statusLabel": "État du serveur",
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
"app.databaseRebuildFailed": "Ricostruzione database non riuscita",
|
||||
"app.textReindexFailed": "Reindicizzazione testo non riuscita",
|
||||
"app.sitemapGenerationFailed": "Generazione sitemap non riuscita",
|
||||
"app.calendarRegenerationFailed": "Rigenerazione del calendario non riuscita", "app.uploadSiteFailed": "Caricamento del sito non riuscito",
|
||||
"app.uploadSiteNoCredentials": "Configurare prima le credenziali SSH nelle impostazioni.", "app.previewOpenFailed": "Impossibile aprire l’anteprima del post selezionato",
|
||||
"app.calendarRegenerationFailed": "Rigenerazione del calendario non riuscita",
|
||||
"app.uploadSiteFailed": "Caricamento del sito non riuscito",
|
||||
"app.uploadSiteOfflineMode": "Il caricamento del sito non è disponibile in modalità aereo.",
|
||||
"app.uploadSiteNoCredentials": "Configurare prima le credenziali SSH nelle impostazioni.",
|
||||
"app.previewOpenFailed": "Impossibile aprire l’anteprima del post selezionato",
|
||||
"app.metadataDiff": "Diff Metadati",
|
||||
"app.importComplete": "Import completato: {posts} post, {media} file multimediali",
|
||||
"siteValidation.tabTitle": "Validazione sito",
|
||||
@@ -312,6 +315,7 @@
|
||||
"gitSidebar.error.loadRepoStatus": "Impossibile caricare lo stato del repository.",
|
||||
"gitSidebar.error.initFailed": "Impossibile inizializzare il repository Git.",
|
||||
"gitSidebar.error.actionFailed": "Impossibile {action}.",
|
||||
"gitSidebar.error.offlineMode": "Questa azione non è disponibile in modalità aereo.",
|
||||
"gitSidebar.error.commitFailed": "Impossibile eseguire il commit delle modifiche.",
|
||||
"gitSidebar.progress.preparingInit": "Preparazione inizializzazione repository...",
|
||||
"gitSidebar.progress.pushingRemote": "Invio dei commit al remoto... può richiedere tempo per upload grandi.",
|
||||
@@ -740,6 +744,7 @@
|
||||
"settings.ai.providerOpenCode": "OpenCode",
|
||||
"settings.ai.providerMistral": "Mistral",
|
||||
"settings.ai.providerOllama": "Ollama (Locale)",
|
||||
"settings.ai.providerLmstudio": "LM Studio (Locale)",
|
||||
"settings.ai.providerOther": "Altro",
|
||||
"settings.ai.ollamaLabel": "Ollama (Modelli locali)",
|
||||
"settings.ai.ollamaDescription": "Connettiti a un'istanza Ollama locale per utilizzare modelli IA locali.",
|
||||
@@ -754,6 +759,28 @@
|
||||
"settings.toast.modelCatalogRefreshFailed": "Aggiornamento del catalogo non riuscito",
|
||||
"settings.toast.ollamaEnabled": "Ollama attivato",
|
||||
"settings.toast.ollamaDisabled": "Ollama disattivato",
|
||||
"settings.ai.lmstudioLabel": "LM Studio (Modelli locali)",
|
||||
"settings.ai.lmstudioDescription": "Connettiti a un'istanza LM Studio locale per utilizzare modelli IA locali.",
|
||||
"settings.ai.lmstudioEnable": "Attiva LM Studio",
|
||||
"settings.ai.lmstudioCapabilitiesDescription": "Configura le capacità per ogni modello LM Studio. Attiva gli strumenti per le chiamate a funzioni o la visione per l'analisi delle immagini.",
|
||||
"settings.ai.lmstudioCapModel": "Modello",
|
||||
"settings.ai.lmstudioCapTools": "Strumenti",
|
||||
"settings.ai.lmstudioCapVision": "Visione",
|
||||
"settings.toast.lmstudioEnabled": "LM Studio attivato",
|
||||
"settings.toast.lmstudioDisabled": "LM Studio disattivato",
|
||||
"settings.ai.offlineLabel": "Modalità aereo",
|
||||
"settings.ai.offlineDescription": "Quando attivato, vengono utilizzati solo i modelli ospitati localmente (Ollama, LM Studio). I provider cloud sono disabilitati.",
|
||||
"settings.ai.offlineEnable": "Attiva modalità aereo",
|
||||
"settings.ai.offlineChatModel": "Modello chat offline",
|
||||
"settings.ai.offlineChatModelDescription": "Modello utilizzato per le conversazioni in modalità aereo.",
|
||||
"settings.ai.offlineTitleModel": "Modello titolo offline",
|
||||
"settings.ai.offlineTitleModelDescription": "Modello utilizzato per la generazione dei titoli in modalità aereo.",
|
||||
"settings.ai.offlineImageAnalysisModel": "Modello analisi immagini offline",
|
||||
"settings.ai.offlineImageAnalysisModelDescription": "Modello utilizzato per l'analisi delle immagini in modalità aereo.",
|
||||
"settings.ai.offlineNoLocalProviders": "Nessun provider locale attivato. Attiva prima Ollama o LM Studio.",
|
||||
"settings.ai.offlineNoLocalModels": "Nessun modello locale disponibile",
|
||||
"settings.toast.offlineEnabled": "Modalità aereo attivata",
|
||||
"settings.toast.offlineDisabled": "Modalità aereo disattivata",
|
||||
"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.",
|
||||
@@ -889,6 +916,9 @@
|
||||
"statusBar.theme": "Tema: {theme}",
|
||||
"statusBar.ui": "UI",
|
||||
"statusBar.uiLanguage": "Lingua interfaccia",
|
||||
"statusBar.offlineMode": "Modalità aereo",
|
||||
"statusBar.offlineModeActive": "Modalità aereo (attiva)",
|
||||
"statusBar.offlineModeTooltip": "Clicca per attivare/disattivare la modalità aereo",
|
||||
"windowTitleBar.toggleSidebar": "Mostra/Nascondi barra laterale",
|
||||
"windowTitleBar.hideSidebar": "Nascondi barra laterale",
|
||||
"windowTitleBar.showSidebar": "Mostra barra laterale",
|
||||
@@ -1005,9 +1035,7 @@
|
||||
"importAnalysis.usedIn": "Usato in: {items}{more}",
|
||||
"importAnalysis.moreSuffix": ", +{count} altri",
|
||||
"importAnalysis.noParameters": "(nessun parametro)",
|
||||
|
||||
"sidebar.nav.mcp": "Server MCP",
|
||||
|
||||
"settings.mcp.title": "Server MCP",
|
||||
"settings.mcp.description": "Configura il server Model Context Protocol che permette agli agenti di programmazione IA di interagire con il tuo blog.",
|
||||
"settings.mcp.statusLabel": "Stato del server",
|
||||
|
||||
Reference in New Issue
Block a user