Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,46 @@ func (a *App) UpdatePort(port int) error {
return nil
}

// GetRetryCount returns global retry count
func (a *App) GetRetryCount() int {
return a.config.GetRetryCount()
}

// GetRetryDelaySec returns global retry delay in seconds
func (a *App) GetRetryDelaySec() int {
return a.config.GetRetryDelaySec()
}

// SetRetrySettings updates global retry settings
func (a *App) SetRetrySettings(retryCount, retryDelaySec int) error {
if retryCount < 1 || retryCount > 10 {
return fmt.Errorf("retryCount must be between 1 and 10, got %d", retryCount)
}
if retryDelaySec < 0 || retryDelaySec > 300 {
return fmt.Errorf("retryDelaySec must be between 0 and 300, got %d", retryDelaySec)
}

a.config.UpdateRetrySettings(retryCount, retryDelaySec)

if err := a.config.Validate(); err != nil {
return err
}

if err := a.proxy.UpdateConfig(a.config); err != nil {
return err
}

if a.storage != nil {
configAdapter := storage.NewConfigStorageAdapter(a.storage)
if err := a.config.SaveToStorage(configAdapter); err != nil {
return fmt.Errorf("failed to save retry settings: %w", err)
}
}

logger.Info("Retry settings updated: count=%d, delay=%ds", retryCount, retryDelaySec)
return nil
}

// ToggleEndpoint toggles the enabled state of an endpoint
func (a *App) ToggleEndpoint(index int, enabled bool) error {
endpoints := a.config.GetEndpoints()
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export default {
portUpdateFailed: 'Failed to update port: {error}',
requiredFields: 'Please fill in all required fields',
modelRequired: 'Model field is required for {transformer} transformer',
retryCountInvalid: 'Retry count must be between 1 and 10',
retryDelayInvalid: 'Retry delay must be between 0 and 300 seconds',
saveFailed: 'Failed to save: {error}',
confirmDelete: 'Are you sure you want to delete endpoint "{name}"?',
deleteFailed: 'Failed to delete: {error}'
Expand Down Expand Up @@ -155,6 +157,12 @@ export default {
minimize: 'Minimize to Tray',
ask: 'Ask Every Time'
},
retryCount: 'Retry Count',
retryCountHelp: 'Global retries per endpoint before switching (1-10)',
retryDelaySec: 'Retry Delay (seconds)',
retryDelaySecHelp: 'Global wait time between retries (0-300 seconds)',
retryCountInvalid: 'Retry count must be between 1 and 10',
retryDelayInvalid: 'Retry delay must be between 0 and 300 seconds',
Comment on lines +164 to +165
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate translation keys. The keys retryCountInvalid and retryDelayInvalid are already defined in the endpoints section (lines 69-70). These duplicate entries in the settings section should be removed to avoid confusion and maintain a single source of truth for these error messages.

Suggested change
retryCountInvalid: 'Retry count must be between 1 and 10',
retryDelayInvalid: 'Retry delay must be between 0 and 300 seconds',

Copilot uses AI. Check for mistakes.
languageHelp: 'Select the interface display language',
save: 'Save',
cancel: 'Cancel',
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/i18n/zh-CN.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export default {
portUpdateFailed: '端口修改失败:{error}',
requiredFields: '请填写所有必填项',
modelRequired: '使用 {transformer} 转换器时,模型字段为必填项',
retryCountInvalid: '重试次数需在 1-10 之间',
retryDelayInvalid: '重试间隔需在 0-300 秒之间',
saveFailed: '保存失败:{error}',
confirmDelete: '确认删除端点 "{name}" 吗?',
deleteFailed: '删除失败:{error}'
Expand Down Expand Up @@ -155,6 +157,12 @@ export default {
minimize: '最小化到托盘',
ask: '每次询问'
},
retryCount: '重试次数',
retryCountHelp: '全局每个端点的连续重试次数,超出后切换(1-10)',
retryDelaySec: '重试间隔(秒)',
retryDelaySecHelp: '全局每次重试前等待时间(0-300 秒)',
retryCountInvalid: '重试次数需在 1-10 之间',
retryDelayInvalid: '重试间隔需在 0-300 秒之间',
Comment on lines +164 to +165
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate translation keys. The keys retryCountInvalid and retryDelayInvalid are already defined in the endpoints section (lines 69-70). These duplicate entries in the settings section should be removed to avoid confusion and maintain a single source of truth for these error messages.

Suggested change
retryCountInvalid: '重试次数需在 1-10 之间',
retryDelayInvalid: '重试间隔需在 0-300 秒之间',

Copilot uses AI. Check for mistakes.
languageHelp: '选择界面显示语言',
save: '保存',
cancel: '取消',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { setLanguage } from './i18n/index.js'
import { initUI, changeLanguage } from './modules/ui.js'
import { loadConfig } from './modules/config.js'
import { loadStats, switchStatsPeriod, loadStatsByPeriod, getCurrentPeriod } from './modules/stats.js'
import { renderEndpoints, toggleEndpointPanel } from './modules/endpoints.js'
import { renderEndpoints, toggleEndpointPanel, setTransformerFilter } from './modules/endpoints.js'
import { loadLogs, toggleLogPanel, changeLogLevel, copyLogs, clearLogs } from './modules/logs.js'
import { showDataSyncDialog } from './modules/webdav.js'
import { initTips } from './modules/tips.js'
Expand Down Expand Up @@ -154,6 +154,7 @@ window.minimizeToTray = minimizeToTray;
window.showDataSyncDialog = showDataSyncDialog;
window.switchStatsPeriod = switchStatsPeriod;
window.toggleEndpointPanel = toggleEndpointPanel;
window.setTransformerFilter = setTransformerFilter;
window.showSettingsModal = showSettingsModal;
window.closeSettingsModal = closeSettingsModal;
window.saveSettings = saveSettings;
Expand Down
30 changes: 25 additions & 5 deletions frontend/src/modules/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ let currentTestButton = null;
let currentTestButtonOriginalText = '';
let currentTestIndex = -1;
let endpointPanelExpanded = true;
let currentTransformerFilter = 'claude';

function copyToClipboard(text, button) {
navigator.clipboard.writeText(text).then(() => {
Expand Down Expand Up @@ -38,6 +39,15 @@ export function setTestState(button, index) {

export async function renderEndpoints(endpoints) {
const container = document.getElementById('endpointList');
const filterTabs = document.getElementById('endpointFilterTabs');
if (filterTabs) {
filterTabs.querySelectorAll('.endpoint-filter-btn').forEach((btn) => {
const isActive = btn.dataset.transformer === currentTransformerFilter;
btn.classList.toggle('active', isActive);
btn.classList.toggle('btn-primary', isActive);
btn.classList.toggle('btn-secondary', !isActive);
});
}

// Get current endpoint
let currentEndpointName = '';
Expand All @@ -60,11 +70,16 @@ export async function renderEndpoints(endpoints) {

const endpointStats = getEndpointStats();
// Display endpoints in config file order (no sorting by enabled status)
const sortedEndpoints = endpoints.map((ep, index) => {
const stats = endpointStats[ep.name] || { requests: 0, errors: 0, inputTokens: 0, outputTokens: 0 };
const enabled = ep.enabled !== undefined ? ep.enabled : true;
return { endpoint: ep, originalIndex: index, stats, enabled };
});
const sortedEndpoints = endpoints
.map((ep, index) => {
const stats = endpointStats[ep.name] || { requests: 0, errors: 0, inputTokens: 0, outputTokens: 0 };
const enabled = ep.enabled !== undefined ? ep.enabled : true;
return { endpoint: ep, originalIndex: index, stats, enabled };
})
.filter(({ endpoint: ep }) => {
const transformer = ep.transformer || 'claude';
return transformer === currentTransformerFilter;
});

sortedEndpoints.forEach(({ endpoint: ep, originalIndex: index, stats }) => {
const totalTokens = stats.inputTokens + stats.outputTokens;
Expand Down Expand Up @@ -193,6 +208,11 @@ export function toggleEndpointPanel() {
}
}

export function setTransformerFilter(transformer) {
currentTransformerFilter = transformer || 'claude';
window.loadConfig(); // re-render with new filter
}

// Drag and drop state
let draggedElement = null;
let draggedOverElement = null;
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/modules/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ async function loadCurrentSettings() {
behaviorSelect.value = closeWindowBehavior;
}

const retryCount = config.retryCount ?? 2;
const retryDelaySec = config.retryDelaySec ?? 0;
const retryCountInput = document.getElementById('settingsRetryCount');
const retryDelayInput = document.getElementById('settingsRetryDelaySec');
if (retryCountInput) retryCountInput.value = retryCount;
if (retryDelayInput) retryDelayInput.value = retryDelaySec;

// Set language
const language = config.language || 'zh-CN';
const languageSelect = document.getElementById('settingsLanguage');
Expand Down Expand Up @@ -275,6 +282,19 @@ export async function saveSettings() {
const language = document.getElementById('settingsLanguage').value;
const theme = document.getElementById('settingsTheme').value;
const themeAuto = document.getElementById('settingsThemeAuto').checked;
const retryCountInput = document.getElementById('settingsRetryCount');
const retryDelayInput = document.getElementById('settingsRetryDelaySec');
const retryCount = parseInt(retryCountInput?.value ?? '2', 10);
const retryDelaySec = parseInt(retryDelayInput?.value ?? '0', 10);

if (Number.isNaN(retryCount) || retryCount < 1 || retryCount > 10) {
showNotification(t('settings.retryCountInvalid'), 'error');
return;
}
if (Number.isNaN(retryDelaySec) || retryDelaySec < 0 || retryDelaySec > 300) {
showNotification(t('settings.retryDelayInvalid'), 'error');
return;
}

// Save close window behavior
await window.go.main.App.SetCloseWindowBehavior(closeWindowBehavior);
Expand All @@ -293,6 +313,11 @@ export async function saveSettings() {
await window.go.main.App.SetThemeAuto(themeAuto);
}

// Save retry settings if changed
if (config.retryCount !== retryCount || config.retryDelaySec !== retryDelaySec) {
await window.go.main.App.SetRetrySettings(retryCount, retryDelaySec);
}

// Apply theme based on final settings
stopAutoThemeCheck();
if (themeAuto) {
Expand Down
31 changes: 26 additions & 5 deletions frontend/src/modules/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,18 @@ export function initUI() {
<!-- Endpoints -->
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 style="margin: 0;">🔗 ${t('endpoints.title')}</h2>
<button class="endpoint-toggle-btn" onclick="window.toggleEndpointPanel()">
<span id="endpointToggleIcon">🔼</span> <span id="endpointToggleText">${t('endpoints.collapse')}</span>
</button>
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
<div style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0;">🔗 ${t('endpoints.title')}</h2>
<button class="endpoint-toggle-btn" onclick="window.toggleEndpointPanel()">
<span id="endpointToggleIcon">🔼</span> <span id="endpointToggleText">${t('endpoints.collapse')}</span>
</button>
</div>
<div id="endpointFilterTabs" style="display: flex; gap: 8px; flex-wrap: wrap;">
<button class="btn btn-secondary endpoint-filter-btn active" data-transformer="claude" onclick="window.setTransformerFilter('claude')">Claude</button>
<button class="btn btn-secondary endpoint-filter-btn" data-transformer="openai" onclick="window.setTransformerFilter('openai')">OpenAI</button>
<button class="btn btn-secondary endpoint-filter-btn" data-transformer="gemini" onclick="window.setTransformerFilter('gemini')">Gemini</button>
</div>
</div>
<div style="display: flex; gap: 10px;">
<button class="btn btn-secondary" onclick="window.showDataSyncDialog()">
Expand Down Expand Up @@ -486,6 +493,20 @@ export function initUI() {
${t('settings.languageHelp')}
</p>
</div>
<div class="form-group">
<label>🔁 ${t('settings.retryCount')} (1-10)</label>
<input type="number" id="settingsRetryCount" min="1" max="10" placeholder="2" value="2">
<p style="color: #666; font-size: 12px; margin-top: 5px;">
${t('settings.retryCountHelp')}
</p>
</div>
<div class="form-group">
<label>⏳ ${t('settings.retryDelaySec')} (0-300s)</label>
<input type="number" id="settingsRetryDelaySec" min="0" max="300" placeholder="0" value="0">
<p style="color: #666; font-size: 12px; margin-top: 5px;">
${t('settings.retryDelaySecHelp')}
</p>
</div>
<div class="form-group">
<label>🌓 ${t('settings.theme')}</label>
<div style="display: flex; align-items: center; gap: 12px;">
Expand Down
6 changes: 6 additions & 0 deletions frontend/wailsjs/go/main/App.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export function GetLogs():Promise<string>;

export function GetLogsByLevel(arg1:number):Promise<string>;

export function GetRetryCount():Promise<number>;

export function GetRetryDelaySec():Promise<number>;

export function GetStats():Promise<string>;

export function GetStatsDaily():Promise<string>;
Expand Down Expand Up @@ -83,6 +87,8 @@ export function SetLanguage(arg1:string):Promise<void>;

export function SetLogLevel(arg1:number):Promise<void>;

export function SetRetrySettings(arg1:number,arg2:number):Promise<void>;

export function SetTheme(arg1:string):Promise<void>;

export function SetThemeAuto(arg1:boolean):Promise<void>;
Expand Down
12 changes: 12 additions & 0 deletions frontend/wailsjs/go/main/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export function GetLogsByLevel(arg1) {
return window['go']['main']['App']['GetLogsByLevel'](arg1);
}

export function GetRetryCount() {
return window['go']['main']['App']['GetRetryCount']();
}

export function GetRetryDelaySec() {
return window['go']['main']['App']['GetRetryDelaySec']();
}

export function GetStats() {
return window['go']['main']['App']['GetStats']();
}
Expand Down Expand Up @@ -166,6 +174,10 @@ export function SetLogLevel(arg1) {
return window['go']['main']['App']['SetLogLevel'](arg1);
}

export function SetRetrySettings(arg1, arg2) {
return window['go']['main']['App']['SetRetrySettings'](arg1, arg2);
}

export function SetTheme(arg1) {
return window['go']['main']['App']['SetTheme'](arg1);
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/wailsjs/runtime/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}

export function EventsOffAll() {
return window.runtime.EventsOffAll();
}

export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
Expand Down
Loading
Loading