Skip to content

Commit a6181c6

Browse files
committed
feat(config): add recommendation settings and fix shortcut button layout
- Add smart recommendation settings section in Config page - Auto-switch toggle with description - Latency/stability/success rate weight sliders - Min test interval configuration - Excluded nodes management - Clear history button - Add latency trend mini chart in node tooltip (sparkline) - Fix ShortcutsSettings reset button icon (use inline SVG) - Archive smart-node-recommendation change - Sync spec to main specs directory
1 parent 14df403 commit a6181c6

File tree

9 files changed

+361
-10
lines changed

9 files changed

+361
-10
lines changed

components/ProxyNodeCard.vue

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,45 @@ const lastTestTimeFormatted = computed(() => {
7171
return formatTimeSince(perf.lastTestTime)
7272
})
7373
74+
// Latency trend data for mini chart (from nodeRecommendationStore)
75+
const latencyTrendData = computed(() => {
76+
const perf = nodePerformance.value
77+
if (!perf || perf.history.length < 2) return null
78+
79+
// Get successful latency values (most recent first, so reverse for chronological order)
80+
const latencies = perf.history
81+
.filter((h) => h.success && h.latency !== null)
82+
.map((h) => h.latency as number)
83+
.slice(0, 10)
84+
.reverse()
85+
86+
if (latencies.length < 2) return null
87+
88+
const min = Math.min(...latencies)
89+
const max = Math.max(...latencies)
90+
const range = max - min || 1
91+
92+
// Normalize to 0-100 for SVG viewBox
93+
const points = latencies.map((lat, i) => ({
94+
x: (i / (latencies.length - 1)) * 100,
95+
y: 100 - ((lat - min) / range) * 80 - 10, // 10-90 range to leave padding
96+
}))
97+
98+
return {
99+
points,
100+
min,
101+
max,
102+
avg: Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length),
103+
}
104+
})
105+
106+
// SVG path for sparkline
107+
const sparklinePath = computed(() => {
108+
if (!latencyTrendData.value) return ''
109+
const { points } = latencyTrendData.value
110+
return points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ')
111+
})
112+
74113
// Score color class
75114
const scoreColorClass = computed(() => {
76115
if (nodeScore.value === null) return ''
@@ -308,6 +347,56 @@ function handleLatencyTest() {
308347
{{ specialTypes }}
309348
</div>
310349

350+
<!-- Latency Trend Mini Chart -->
351+
<div
352+
v-if="latencyTrendData"
353+
class="w-full rounded-lg bg-[color-mix(in_oklch,var(--color-primary-content)_10%,transparent)] p-2"
354+
>
355+
<div
356+
class="mb-1 flex items-center justify-between text-[0.625rem] opacity-70"
357+
>
358+
<span>{{ latencyTrendData.min }}ms</span>
359+
<span>avg: {{ latencyTrendData.avg }}ms</span>
360+
<span>{{ latencyTrendData.max }}ms</span>
361+
</div>
362+
<svg
363+
viewBox="0 0 100 50"
364+
class="h-8 w-full"
365+
preserveAspectRatio="none"
366+
>
367+
<!-- Grid lines -->
368+
<line
369+
x1="0"
370+
y1="25"
371+
x2="100"
372+
y2="25"
373+
stroke="currentColor"
374+
stroke-opacity="0.1"
375+
stroke-dasharray="2,2"
376+
/>
377+
<!-- Sparkline -->
378+
<path
379+
:d="sparklinePath"
380+
fill="none"
381+
stroke="currentColor"
382+
stroke-width="2"
383+
stroke-linecap="round"
384+
stroke-linejoin="round"
385+
class="opacity-80"
386+
/>
387+
<!-- Data points -->
388+
<circle
389+
v-for="(point, idx) in latencyTrendData.points"
390+
:key="idx"
391+
:cx="point.x"
392+
:cy="point.y"
393+
r="2"
394+
fill="currentColor"
395+
class="opacity-60"
396+
/>
397+
</svg>
398+
</div>
399+
311400
<template v-if="latencyTestHistory.length > 0">
312401
<ul class="m-0 max-h-60 w-full list-none overflow-y-auto p-0">
313402
<li

components/ShortcutsSettings.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
<script setup lang="ts">
2-
import type {ShortcutAction} from '~/constants/shortcuts';
2+
import type { ShortcutAction } from '~/constants/shortcuts'
33
import {
44
formatShortcutKey,
55
SHORTCUT_CATEGORIES,
6-
SHORTCUT_LABELS
7-
6+
SHORTCUT_LABELS,
87
} from '~/constants/shortcuts'
98
109
const shortcutsStore = useShortcutsStore()
@@ -168,7 +167,17 @@ const isCustomized = (action: ShortcutAction) => {
168167
<!-- Reset button -->
169168
<div class="mt-2 flex justify-end">
170169
<button class="btn btn-outline btn-sm btn-error" @click="resetAll">
171-
<span class="i-tabler-refresh size-4" />
170+
<svg
171+
xmlns="http://www.w3.org/2000/svg"
172+
class="size-4"
173+
viewBox="0 0 24 24"
174+
fill="none"
175+
stroke="currentColor"
176+
stroke-width="2"
177+
>
178+
<path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8" />
179+
<path d="M3 3v5h5" />
180+
</svg>
172181
{{ t('shortcuts.resetToDefaults', 'Reset to Defaults') }}
173182
</button>
174183
</div>

openspec/changes/smart-node-recommendation/.openspec.yaml renamed to openspec/changes/archive/2026-02-03-smart-node-recommendation/.openspec.yaml

File renamed without changes.

openspec/changes/smart-node-recommendation/design.md renamed to openspec/changes/archive/2026-02-03-smart-node-recommendation/design.md

File renamed without changes.

openspec/changes/smart-node-recommendation/proposal.md renamed to openspec/changes/archive/2026-02-03-smart-node-recommendation/proposal.md

File renamed without changes.

openspec/changes/smart-node-recommendation/specs/smart-node-recommendation/spec.md renamed to openspec/changes/archive/2026-02-03-smart-node-recommendation/specs/smart-node-recommendation/spec.md

File renamed without changes.

openspec/changes/smart-node-recommendation/tasks.md renamed to openspec/changes/archive/2026-02-03-smart-node-recommendation/tasks.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
- [x] 4.2 实现评分颜色编码 (绿/黄/红)
2626
- [x] 4.3 添加推荐节点标记 badge
2727
- [x] 4.4 添加最后测试时间显示
28-
- [ ] 4.5 实现悬停显示延迟趋势迷你图
28+
- [x] 4.5 实现悬停显示延迟趋势迷你图
2929

3030
## 5. 代理组增强
3131

@@ -36,9 +36,9 @@
3636

3737
## 6. 设置与配置
3838

39-
- [ ] 6.1 在 Config 页面添加推荐设置区域
40-
- [ ] 6.2 实现权重参数配置 (延迟/稳定性/成功率)
41-
- [ ] 6.3 实现测试间隔配置
42-
- [ ] 6.4 实现节点排除功能
43-
- [ ] 6.5 实现自动切换开关
39+
- [x] 6.1 在 Config 页面添加推荐设置区域
40+
- [x] 6.2 实现权重参数配置 (延迟/稳定性/成功率)
41+
- [x] 6.3 实现测试间隔配置
42+
- [x] 6.4 实现节点排除功能
43+
- [x] 6.5 实现自动切换开关
4444
- [x] 6.6 添加国际化文本
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Batch latency testing
4+
5+
The system SHALL provide batch latency testing for all nodes in a proxy group.
6+
7+
#### Scenario: Test all nodes in group
8+
9+
- **WHEN** user clicks "Test All" button on a proxy group
10+
- **THEN** system tests latency for all nodes in that group concurrently (max 10 parallel)
11+
12+
#### Scenario: Show testing progress
13+
14+
- **WHEN** batch testing is in progress
15+
- **THEN** system displays a progress indicator showing completed/total nodes
16+
17+
#### Scenario: Test all groups
18+
19+
- **WHEN** user clicks "Test All Groups" button
20+
- **THEN** system tests all nodes across all proxy groups
21+
22+
### Requirement: Node scoring
23+
24+
The system SHALL calculate a performance score for each node based on multiple metrics.
25+
26+
#### Scenario: Calculate node score
27+
28+
- **WHEN** latency test completes for a node
29+
- **THEN** system calculates score: 50% latency + 30% stability + 20% success rate
30+
31+
#### Scenario: Display node score
32+
33+
- **WHEN** a node has been tested
34+
- **THEN** system displays the score (0-100) on the node card
35+
36+
#### Scenario: Score color coding
37+
38+
- **WHEN** displaying node score
39+
- **THEN** system uses green (80-100), yellow (50-79), red (0-49) color coding
40+
41+
### Requirement: Smart node recommendation
42+
43+
The system SHALL recommend the best node in each proxy group.
44+
45+
#### Scenario: Show recommended badge
46+
47+
- **WHEN** viewing a proxy group with tested nodes
48+
- **THEN** system displays a "Recommended" badge on the highest-scoring available node
49+
50+
#### Scenario: Quick switch to recommended
51+
52+
- **WHEN** user clicks "Switch to Recommended" button on a proxy group
53+
- **THEN** system switches the active node to the recommended node
54+
55+
#### Scenario: Auto-switch option
56+
57+
- **WHEN** user enables "Auto-switch to recommended" setting
58+
- **THEN** system automatically switches to recommended node after batch testing
59+
60+
### Requirement: Node performance history
61+
62+
The system SHALL track historical performance data for nodes.
63+
64+
#### Scenario: Store test results
65+
66+
- **WHEN** a latency test completes
67+
- **THEN** system stores the result (timestamp, latency, success) keeping last 20 records per node
68+
69+
#### Scenario: Show performance trend
70+
71+
- **WHEN** user hovers over a node
72+
- **THEN** system displays a mini chart showing recent latency trend
73+
74+
#### Scenario: Show last test time
75+
76+
- **WHEN** viewing a node
77+
- **THEN** system displays time since last test (e.g., "5 min ago")
78+
79+
### Requirement: Recommendation settings
80+
81+
The system SHALL allow users to configure recommendation parameters.
82+
83+
#### Scenario: Configure score weights
84+
85+
- **WHEN** user opens recommendation settings
86+
- **THEN** user can adjust latency/stability/success rate weight percentages
87+
88+
#### Scenario: Configure test interval
89+
90+
- **WHEN** user opens recommendation settings
91+
- **THEN** user can set minimum interval between batch tests
92+
93+
#### Scenario: Exclude nodes from recommendation
94+
95+
- **WHEN** user marks a node as "excluded"
96+
- **THEN** system never recommends that node regardless of score

0 commit comments

Comments
 (0)