super-dev 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- super_dev/__init__.py +11 -0
- super_dev/analyzer/__init__.py +34 -0
- super_dev/analyzer/analyzer.py +440 -0
- super_dev/analyzer/detectors.py +511 -0
- super_dev/analyzer/models.py +285 -0
- super_dev/cli.py +3257 -0
- super_dev/config/__init__.py +11 -0
- super_dev/config/frontend.py +557 -0
- super_dev/config/manager.py +281 -0
- super_dev/creators/__init__.py +26 -0
- super_dev/creators/creator.py +134 -0
- super_dev/creators/document_generator.py +2473 -0
- super_dev/creators/frontend_builder.py +371 -0
- super_dev/creators/implementation_builder.py +789 -0
- super_dev/creators/prompt_generator.py +289 -0
- super_dev/creators/requirement_parser.py +354 -0
- super_dev/creators/spec_builder.py +195 -0
- super_dev/deployers/__init__.py +20 -0
- super_dev/deployers/cicd.py +1269 -0
- super_dev/deployers/delivery.py +229 -0
- super_dev/deployers/migration.py +1032 -0
- super_dev/design/__init__.py +74 -0
- super_dev/design/aesthetics.py +530 -0
- super_dev/design/charts.py +396 -0
- super_dev/design/codegen.py +379 -0
- super_dev/design/engine.py +528 -0
- super_dev/design/generator.py +395 -0
- super_dev/design/landing.py +422 -0
- super_dev/design/tech_stack.py +524 -0
- super_dev/design/tokens.py +269 -0
- super_dev/design/ux_guide.py +391 -0
- super_dev/exceptions.py +119 -0
- super_dev/experts/__init__.py +19 -0
- super_dev/experts/service.py +161 -0
- super_dev/integrations/__init__.py +7 -0
- super_dev/integrations/manager.py +264 -0
- super_dev/orchestrator/__init__.py +12 -0
- super_dev/orchestrator/engine.py +958 -0
- super_dev/orchestrator/experts.py +423 -0
- super_dev/orchestrator/knowledge.py +352 -0
- super_dev/orchestrator/quality.py +356 -0
- super_dev/reviewers/__init__.py +17 -0
- super_dev/reviewers/code_review.py +471 -0
- super_dev/reviewers/quality_gate.py +964 -0
- super_dev/reviewers/redteam.py +881 -0
- super_dev/skills/__init__.py +7 -0
- super_dev/skills/manager.py +307 -0
- super_dev/specs/__init__.py +44 -0
- super_dev/specs/generator.py +264 -0
- super_dev/specs/manager.py +428 -0
- super_dev/specs/models.py +348 -0
- super_dev/specs/validator.py +415 -0
- super_dev/utils/__init__.py +11 -0
- super_dev/utils/logger.py +133 -0
- super_dev/web/api.py +1402 -0
- super_dev-2.0.0.dist-info/METADATA +252 -0
- super_dev-2.0.0.dist-info/RECORD +61 -0
- super_dev-2.0.0.dist-info/WHEEL +5 -0
- super_dev-2.0.0.dist-info/entry_points.txt +2 -0
- super_dev-2.0.0.dist-info/licenses/LICENSE +21 -0
- super_dev-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
开发:Excellent(11964948@qq.com)
|
|
3
|
+
功能:Design Token 生成器
|
|
4
|
+
作用:生成可复用的设计 tokens
|
|
5
|
+
创建时间:2025-12-30
|
|
6
|
+
最后修改:2025-12-30
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TokenGenerator:
|
|
12
|
+
"""
|
|
13
|
+
Design Token 生成器
|
|
14
|
+
|
|
15
|
+
支持生成:
|
|
16
|
+
1. Color Tokens
|
|
17
|
+
2. Typography Tokens
|
|
18
|
+
3. Spacing Tokens
|
|
19
|
+
4. Shadow Tokens
|
|
20
|
+
5. Animation Tokens
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def generate_color_tokens(
|
|
24
|
+
self,
|
|
25
|
+
primary: str,
|
|
26
|
+
palette_type: str = "monochromatic",
|
|
27
|
+
) -> dict[str, str]:
|
|
28
|
+
"""
|
|
29
|
+
生成色彩 tokens
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
primary: 主色(hex)
|
|
33
|
+
palette_type: 调色板类型(monochromatic, analogous, complementary, triadic)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
色彩 token 字典
|
|
37
|
+
"""
|
|
38
|
+
base_color = self._hex_to_hsl(primary)
|
|
39
|
+
|
|
40
|
+
if palette_type == "monochromatic":
|
|
41
|
+
return self._generate_monochromatic_palette(base_color)
|
|
42
|
+
elif palette_type == "analogous":
|
|
43
|
+
return self._generate_analogous_palette(base_color)
|
|
44
|
+
elif palette_type == "complementary":
|
|
45
|
+
return self._generate_complementary_palette(base_color)
|
|
46
|
+
elif palette_type == "triadic":
|
|
47
|
+
return self._generate_triadic_palette(base_color)
|
|
48
|
+
else:
|
|
49
|
+
return self._generate_monochromatic_palette(base_color)
|
|
50
|
+
|
|
51
|
+
def _generate_monochromatic_palette(self, base_hsl: tuple) -> dict[str, str]:
|
|
52
|
+
"""生成单色调色板"""
|
|
53
|
+
h, s, lightness = base_hsl
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
"50": self._hsl_to_hex(h, s, min(lightness + 45, 100)),
|
|
57
|
+
"100": self._hsl_to_hex(h, s, min(lightness + 40, 100)),
|
|
58
|
+
"200": self._hsl_to_hex(h, s, min(lightness + 30, 100)),
|
|
59
|
+
"300": self._hsl_to_hex(h, s, min(lightness + 20, 100)),
|
|
60
|
+
"400": self._hsl_to_hex(h, s, min(lightness + 10, 100)),
|
|
61
|
+
"500": self._hsl_to_hex(h, s, lightness),
|
|
62
|
+
"600": self._hsl_to_hex(h, s, max(lightness - 10, 0)),
|
|
63
|
+
"700": self._hsl_to_hex(h, s, max(lightness - 20, 0)),
|
|
64
|
+
"800": self._hsl_to_hex(h, s, max(lightness - 30, 0)),
|
|
65
|
+
"900": self._hsl_to_hex(h, s, max(lightness - 40, 0)),
|
|
66
|
+
"950": self._hsl_to_hex(h, s, max(lightness - 45, 0)),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def _generate_analogous_palette(self, base_hsl: tuple) -> dict[str, str]:
|
|
70
|
+
"""生成类比调色板"""
|
|
71
|
+
h, s, lightness = base_hsl
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"primary": self._hsl_to_hex(h, s, lightness),
|
|
75
|
+
"secondary": self._hsl_to_hex((h + 30) % 360, s, lightness),
|
|
76
|
+
"accent": self._hsl_to_hex((h - 30) % 360, s, lightness),
|
|
77
|
+
"muted": self._hsl_to_hex(h, s * 0.5, lightness * 1.1),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def _generate_complementary_palette(self, base_hsl: tuple) -> dict[str, str]:
|
|
81
|
+
"""生成互补调色板"""
|
|
82
|
+
h, s, lightness = base_hsl
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"primary": self._hsl_to_hex(h, s, lightness),
|
|
86
|
+
"secondary": self._hsl_to_hex((h + 180) % 360, s, lightness),
|
|
87
|
+
"accent": self._hsl_to_hex((h + 90) % 360, s, lightness),
|
|
88
|
+
"muted": self._hsl_to_hex(h, s * 0.6, lightness * 1.1),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def _generate_triadic_palette(self, base_hsl: tuple) -> dict[str, str]:
|
|
92
|
+
"""生成三元调色板"""
|
|
93
|
+
h, s, lightness = base_hsl
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"primary": self._hsl_to_hex(h, s, lightness),
|
|
97
|
+
"secondary": self._hsl_to_hex((h + 120) % 360, s, lightness),
|
|
98
|
+
"tertiary": self._hsl_to_hex((h + 240) % 360, s, lightness),
|
|
99
|
+
"accent": self._hsl_to_hex((h + 60) % 360, s, lightness),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def generate_spacing_tokens(self, base_unit: int = 8) -> dict[str, str]:
|
|
103
|
+
"""
|
|
104
|
+
生成间距 tokens(8pt 栅格)
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
base_unit: 基础单位(像素)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
间距 token 字典
|
|
111
|
+
"""
|
|
112
|
+
return {
|
|
113
|
+
"0": "0",
|
|
114
|
+
"px": f"{base_unit * 0.5}px",
|
|
115
|
+
"0.5": f"{base_unit * 0.5}px",
|
|
116
|
+
"1": f"{base_unit}px",
|
|
117
|
+
"1.5": f"{base_unit * 1.5}px",
|
|
118
|
+
"2": f"{base_unit * 2}px",
|
|
119
|
+
"2.5": f"{base_unit * 2.5}px",
|
|
120
|
+
"3": f"{base_unit * 3}px",
|
|
121
|
+
"3.5": f"{base_unit * 3.5}px",
|
|
122
|
+
"4": f"{base_unit * 4}px",
|
|
123
|
+
"5": f"{base_unit * 5}px",
|
|
124
|
+
"6": f"{base_unit * 6}px",
|
|
125
|
+
"7": f"{base_unit * 7}px",
|
|
126
|
+
"8": f"{base_unit * 8}px",
|
|
127
|
+
"9": f"{base_unit * 9}px",
|
|
128
|
+
"10": f"{base_unit * 10}px",
|
|
129
|
+
"12": f"{base_unit * 12}px",
|
|
130
|
+
"16": f"{base_unit * 16}px",
|
|
131
|
+
"20": f"{base_unit * 20}px",
|
|
132
|
+
"24": f"{base_unit * 24}px",
|
|
133
|
+
"32": f"{base_unit * 32}px",
|
|
134
|
+
"40": f"{base_unit * 40}px",
|
|
135
|
+
"48": f"{base_unit * 48}px",
|
|
136
|
+
"56": f"{base_unit * 56}px",
|
|
137
|
+
"64": f"{base_unit * 64}px",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
def generate_shadow_tokens(self, elevation: int = 3) -> dict[str, str]:
|
|
141
|
+
"""
|
|
142
|
+
生成阴影 tokens(Material Design 风格)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
elevation: 最大级别
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
阴影 token 字典
|
|
149
|
+
"""
|
|
150
|
+
shadows = {}
|
|
151
|
+
|
|
152
|
+
for i in range(elevation + 1):
|
|
153
|
+
opacity = 0.1 + (i * 0.05)
|
|
154
|
+
blur = 2 + (i * 4)
|
|
155
|
+
y_offset = 1 + (i * 2)
|
|
156
|
+
|
|
157
|
+
shadows[str(i)] = f"0 {y_offset}px {blur}px 0 rgb(0 0 0 / {opacity:.2f})"
|
|
158
|
+
|
|
159
|
+
return shadows
|
|
160
|
+
|
|
161
|
+
def generate_animation_tokens(self) -> dict[str, str]:
|
|
162
|
+
"""
|
|
163
|
+
生成动画 tokens
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
动画 token 字典
|
|
167
|
+
"""
|
|
168
|
+
return {
|
|
169
|
+
"duration-fast": "150ms",
|
|
170
|
+
"duration-base": "200ms",
|
|
171
|
+
"duration-slow": "300ms",
|
|
172
|
+
"duration-slower": "500ms",
|
|
173
|
+
"easing-default": "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
174
|
+
"easing-in": "cubic-bezier(0.4, 0, 1, 1)",
|
|
175
|
+
"easing-out": "cubic-bezier(0, 0, 0.2, 1)",
|
|
176
|
+
"easing-in-out": "cubic-bezier(0.4, 0, 0.6, 1)",
|
|
177
|
+
"easing-bounce": "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def _hex_to_hsl(self, hex_color: str) -> tuple[int, int, int]:
|
|
181
|
+
"""Hex 转 HSL"""
|
|
182
|
+
hex_color = hex_color.lstrip("#")
|
|
183
|
+
|
|
184
|
+
if len(hex_color) == 3:
|
|
185
|
+
hex_color = "".join([c * 2 for c in hex_color])
|
|
186
|
+
|
|
187
|
+
r = int(hex_color[0:2], 16) / 255
|
|
188
|
+
g = int(hex_color[2:4], 16) / 255
|
|
189
|
+
b = int(hex_color[4:6], 16) / 255
|
|
190
|
+
|
|
191
|
+
max_val = max(r, g, b)
|
|
192
|
+
min_val = min(r, g, b)
|
|
193
|
+
delta = max_val - min_val
|
|
194
|
+
|
|
195
|
+
# Hue
|
|
196
|
+
h: float = 0.0
|
|
197
|
+
if delta == 0:
|
|
198
|
+
h = 0
|
|
199
|
+
elif max_val == r:
|
|
200
|
+
h = 60 * (((g - b) / delta) % 6)
|
|
201
|
+
elif max_val == g:
|
|
202
|
+
h = 60 * (((b - r) / delta) + 2)
|
|
203
|
+
elif max_val == b:
|
|
204
|
+
h = 60 * (((r - g) / delta) + 4)
|
|
205
|
+
|
|
206
|
+
# Saturation
|
|
207
|
+
s: float = 0.0
|
|
208
|
+
if max_val == 0:
|
|
209
|
+
s = 0
|
|
210
|
+
else:
|
|
211
|
+
s = (delta / max_val) * 100
|
|
212
|
+
|
|
213
|
+
# Lightness
|
|
214
|
+
lightness: float = ((max_val + min_val) / 2) * 100
|
|
215
|
+
|
|
216
|
+
return (round(h), round(s), round(lightness))
|
|
217
|
+
|
|
218
|
+
def _hsl_to_hex(self, h: float, s: float, lightness: float) -> str:
|
|
219
|
+
"""HSL 转 Hex"""
|
|
220
|
+
s /= 100
|
|
221
|
+
lightness /= 100
|
|
222
|
+
|
|
223
|
+
c = (1 - abs(2 * lightness - 1)) * s
|
|
224
|
+
x = c * (1 - abs((h / 60) % 2 - 1))
|
|
225
|
+
m = lightness - c / 2
|
|
226
|
+
|
|
227
|
+
r: float
|
|
228
|
+
g: float
|
|
229
|
+
b: float
|
|
230
|
+
if 0 <= h < 60:
|
|
231
|
+
r, g, b = c, x, 0.0
|
|
232
|
+
elif 60 <= h < 120:
|
|
233
|
+
r, g, b = x, c, 0.0
|
|
234
|
+
elif 120 <= h < 180:
|
|
235
|
+
r, g, b = 0.0, c, x
|
|
236
|
+
elif 180 <= h < 240:
|
|
237
|
+
r, g, b = 0.0, x, c
|
|
238
|
+
elif 240 <= h < 300:
|
|
239
|
+
r, g, b = x, 0.0, c
|
|
240
|
+
else:
|
|
241
|
+
r, g, b = c, 0.0, x
|
|
242
|
+
|
|
243
|
+
r = int((r + m) * 255)
|
|
244
|
+
g = int((g + m) * 255)
|
|
245
|
+
b = int((b + m) * 255)
|
|
246
|
+
|
|
247
|
+
return f"#{r:02x}{g:02x}{b:02x}"
|
|
248
|
+
|
|
249
|
+
def generate_all_tokens(
|
|
250
|
+
self,
|
|
251
|
+
primary_color: str,
|
|
252
|
+
palette_type: str = "monochromatic",
|
|
253
|
+
) -> dict[str, dict[str, str]]:
|
|
254
|
+
"""
|
|
255
|
+
生成所有 tokens
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
primary_color: 主色
|
|
259
|
+
palette_type: 调色板类型
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
所有 tokens 字典
|
|
263
|
+
"""
|
|
264
|
+
return {
|
|
265
|
+
"colors": self.generate_color_tokens(primary_color, palette_type),
|
|
266
|
+
"spacing": self.generate_spacing_tokens(),
|
|
267
|
+
"shadows": self.generate_shadow_tokens(),
|
|
268
|
+
"animations": self.generate_animation_tokens(),
|
|
269
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
开发:Excellent(11964948@qq.com)
|
|
3
|
+
功能:UX 指南数据库引擎
|
|
4
|
+
作用:提供 UX 最佳实践和反模式查询
|
|
5
|
+
创建时间:2025-01-04
|
|
6
|
+
最后修改:2025-01-04
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import csv
|
|
10
|
+
import random
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UXDomain(str, Enum):
|
|
17
|
+
"""UX 领域"""
|
|
18
|
+
ANIMATION = "Animation"
|
|
19
|
+
A11Y = "A11y" # Accessibility
|
|
20
|
+
PERFORMANCE = "Performance"
|
|
21
|
+
RESPONSIVE = "Responsive"
|
|
22
|
+
FORMS = "Forms"
|
|
23
|
+
NAVIGATION = "Navigation"
|
|
24
|
+
LOADING = "Loading"
|
|
25
|
+
ERROR = "Error"
|
|
26
|
+
DARK_MODE = "Dark Mode"
|
|
27
|
+
I18N = "I18n" # Internationalization
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class UXGuideline:
|
|
32
|
+
"""UX 指南"""
|
|
33
|
+
domain: UXDomain
|
|
34
|
+
topic: str
|
|
35
|
+
best_practice: str
|
|
36
|
+
anti_pattern: str
|
|
37
|
+
example: str
|
|
38
|
+
impact: str
|
|
39
|
+
complexity: str # low, medium, high
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class UXRecommendation:
|
|
44
|
+
"""UX 推荐建议"""
|
|
45
|
+
guideline: UXGuideline
|
|
46
|
+
priority: str # critical, high, medium, low
|
|
47
|
+
implementation_effort: str # low, medium, high
|
|
48
|
+
user_impact: str # high, medium, low
|
|
49
|
+
resources: list[str] # 相关资源链接
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class UXGuideEngine:
|
|
53
|
+
"""UX 指南引擎"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, data_dir: Path | None = None):
|
|
56
|
+
"""
|
|
57
|
+
初始化引擎
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
data_dir: 数据目录路径
|
|
61
|
+
"""
|
|
62
|
+
if data_dir is None:
|
|
63
|
+
data_dir = Path(__file__).parent.parent / "data" / "design"
|
|
64
|
+
|
|
65
|
+
self.data_dir = Path(data_dir)
|
|
66
|
+
self.guidelines: list[UXGuideline] = []
|
|
67
|
+
self._load_guidelines()
|
|
68
|
+
|
|
69
|
+
def _load_guidelines(self):
|
|
70
|
+
"""从 CSV 加载指南数据"""
|
|
71
|
+
csv_path = self.data_dir / "ux_guidelines.csv"
|
|
72
|
+
|
|
73
|
+
if not csv_path.exists():
|
|
74
|
+
self.guidelines = self._get_default_guidelines()
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
with open(csv_path, encoding='utf-8') as f:
|
|
78
|
+
reader = csv.DictReader(f)
|
|
79
|
+
for row in reader:
|
|
80
|
+
try:
|
|
81
|
+
guideline = UXGuideline(
|
|
82
|
+
domain=UXDomain(row["domain"]),
|
|
83
|
+
topic=row["topic"],
|
|
84
|
+
best_practice=row["best_practice"],
|
|
85
|
+
anti_pattern=row["anti_pattern"],
|
|
86
|
+
example=row["example"],
|
|
87
|
+
impact=row["impact"],
|
|
88
|
+
complexity=row["complexity"]
|
|
89
|
+
)
|
|
90
|
+
self.guidelines.append(guideline)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"Warning: Failed to parse UX guideline: {e}")
|
|
93
|
+
|
|
94
|
+
def _get_default_guidelines(self) -> list[UXGuideline]:
|
|
95
|
+
"""获取默认指南(当 CSV 不存在时)"""
|
|
96
|
+
return [
|
|
97
|
+
UXGuideline(
|
|
98
|
+
domain=UXDomain.ANIMATION,
|
|
99
|
+
topic="Loading",
|
|
100
|
+
best_practice="Use skeleton screens for content loading",
|
|
101
|
+
anti_pattern="Use spinners for all loading states",
|
|
102
|
+
example="Skeleton while profile data loads",
|
|
103
|
+
impact="Reduced perceived wait time",
|
|
104
|
+
complexity="medium"
|
|
105
|
+
),
|
|
106
|
+
UXGuideline(
|
|
107
|
+
domain=UXDomain.A11Y,
|
|
108
|
+
topic="Color",
|
|
109
|
+
best_practice="Use 4.5:1 contrast ratio for text",
|
|
110
|
+
anti_pattern="Light gray text on white background",
|
|
111
|
+
example="Dark text on light background",
|
|
112
|
+
impact="Readable by all users",
|
|
113
|
+
complexity="low"
|
|
114
|
+
),
|
|
115
|
+
UXGuideline(
|
|
116
|
+
domain=UXDomain.PERFORMANCE,
|
|
117
|
+
topic="Images",
|
|
118
|
+
best_practice="Use WebP format with fallbacks",
|
|
119
|
+
anti_pattern="Unoptimized 5MB PNGs",
|
|
120
|
+
example="WebP with JPEG fallback",
|
|
121
|
+
impact="Faster page load",
|
|
122
|
+
complexity="low"
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def search(
|
|
127
|
+
self,
|
|
128
|
+
query: str,
|
|
129
|
+
domain: str | None = None,
|
|
130
|
+
max_results: int = 5
|
|
131
|
+
) -> list[UXRecommendation]:
|
|
132
|
+
"""
|
|
133
|
+
搜索 UX 指南
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
query: 搜索查询
|
|
137
|
+
domain: 领域过滤
|
|
138
|
+
max_results: 最大结果数
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
推荐建议列表
|
|
142
|
+
"""
|
|
143
|
+
query_lower = query.lower()
|
|
144
|
+
|
|
145
|
+
# 筛选和评分
|
|
146
|
+
scored_guidelines = []
|
|
147
|
+
for guideline in self.guidelines:
|
|
148
|
+
# 领域过滤
|
|
149
|
+
if domain and guideline.domain.value.lower() != domain.lower():
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
score = 0
|
|
153
|
+
|
|
154
|
+
# 主题匹配
|
|
155
|
+
if query_lower in guideline.topic.lower():
|
|
156
|
+
score += 10
|
|
157
|
+
|
|
158
|
+
# 最佳实践匹配
|
|
159
|
+
if query_lower in guideline.best_practice.lower():
|
|
160
|
+
score += 8
|
|
161
|
+
|
|
162
|
+
# 反模式匹配
|
|
163
|
+
if query_lower in guideline.anti_pattern.lower():
|
|
164
|
+
score += 8
|
|
165
|
+
|
|
166
|
+
# 关键词匹配(从影响描述中提取)
|
|
167
|
+
words = query_lower.split()
|
|
168
|
+
for word in words:
|
|
169
|
+
if word in guideline.impact.lower():
|
|
170
|
+
score += 3
|
|
171
|
+
|
|
172
|
+
if score > 0:
|
|
173
|
+
# 确定优先级
|
|
174
|
+
priority = self._determine_priority(guideline)
|
|
175
|
+
|
|
176
|
+
# 确定实现难度
|
|
177
|
+
effort = guideline.complexity
|
|
178
|
+
|
|
179
|
+
# 确定用户影响
|
|
180
|
+
user_impact = self._determine_user_impact(guideline)
|
|
181
|
+
|
|
182
|
+
scored_guidelines.append((guideline, score, priority, effort, user_impact))
|
|
183
|
+
|
|
184
|
+
# 按分数排序
|
|
185
|
+
scored_guidelines.sort(key=lambda x: x[1], reverse=True)
|
|
186
|
+
|
|
187
|
+
# 构建推荐列表
|
|
188
|
+
recommendations = []
|
|
189
|
+
for guideline, score, priority, effort, user_impact in scored_guidelines[:max_results]:
|
|
190
|
+
recommendations.append(UXRecommendation(
|
|
191
|
+
guideline=guideline,
|
|
192
|
+
priority=priority,
|
|
193
|
+
implementation_effort=effort,
|
|
194
|
+
user_impact=user_impact,
|
|
195
|
+
resources=self._get_resources(guideline)
|
|
196
|
+
))
|
|
197
|
+
|
|
198
|
+
return recommendations
|
|
199
|
+
|
|
200
|
+
def _determine_priority(self, guideline: UXGuideline) -> str:
|
|
201
|
+
"""确定优先级"""
|
|
202
|
+
# 无障碍性通常是关键优先级
|
|
203
|
+
if guideline.domain == UXDomain.A11Y:
|
|
204
|
+
return "critical"
|
|
205
|
+
|
|
206
|
+
# 性能影响通常是高优先级
|
|
207
|
+
if guideline.domain == UXDomain.PERFORMANCE:
|
|
208
|
+
return "high"
|
|
209
|
+
|
|
210
|
+
# 基于复杂度确定优先级
|
|
211
|
+
if guideline.complexity == "low":
|
|
212
|
+
return "high" # 容易实现的优先级高
|
|
213
|
+
|
|
214
|
+
return "medium"
|
|
215
|
+
|
|
216
|
+
def _determine_user_impact(self, guideline: UXGuideline) -> str:
|
|
217
|
+
"""确定用户影响"""
|
|
218
|
+
# 从影响描述中推断
|
|
219
|
+
impact_lower = guideline.impact.lower()
|
|
220
|
+
|
|
221
|
+
if any(word in impact_lower for word in ["all users", "everyone", "critical", "essential"]):
|
|
222
|
+
return "high"
|
|
223
|
+
|
|
224
|
+
if any(word in impact_lower for word in ["some users", "improved", "better"]):
|
|
225
|
+
return "medium"
|
|
226
|
+
|
|
227
|
+
return "low"
|
|
228
|
+
|
|
229
|
+
def _get_resources(self, guideline: UXGuideline) -> list[str]:
|
|
230
|
+
"""获取相关资源"""
|
|
231
|
+
resources = []
|
|
232
|
+
|
|
233
|
+
# 基于领域添加资源
|
|
234
|
+
domain_resources = {
|
|
235
|
+
UXDomain.A11Y: [
|
|
236
|
+
"WCAG 2.1 Guidelines: https://www.w3.org/WAI/WCAG21/quickref/",
|
|
237
|
+
"WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/"
|
|
238
|
+
],
|
|
239
|
+
UXDomain.PERFORMANCE: [
|
|
240
|
+
"Web.dev Performance: https://web.dev/performance/",
|
|
241
|
+
"Google Lighthouse: https://developers.google.com/web/tools/lighthouse"
|
|
242
|
+
],
|
|
243
|
+
UXDomain.RESPONSIVE: [
|
|
244
|
+
"MDN Responsive Design: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design"
|
|
245
|
+
],
|
|
246
|
+
UXDomain.ANIMATION: [
|
|
247
|
+
"Motion Design Guidelines: https://material.io/design/motion"
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
resources.extend(domain_resources.get(guideline.domain, []))
|
|
252
|
+
|
|
253
|
+
return resources
|
|
254
|
+
|
|
255
|
+
def get_guidelines_by_domain(self, domain: str) -> list[UXGuideline]:
|
|
256
|
+
"""
|
|
257
|
+
按领域获取指南
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
domain: 领域名称
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
该领域的所有指南
|
|
264
|
+
"""
|
|
265
|
+
domain_lower = domain.lower()
|
|
266
|
+
return [
|
|
267
|
+
g for g in self.guidelines
|
|
268
|
+
if g.domain.value.lower() == domain_lower
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
def get_quick_wins(self, max_results: int = 5) -> list[UXRecommendation]:
|
|
272
|
+
"""
|
|
273
|
+
获取快速见效的改进(低复杂度、高影响)
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
max_results: 最大结果数
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
快速见效的建议列表
|
|
280
|
+
"""
|
|
281
|
+
quick_wins = []
|
|
282
|
+
|
|
283
|
+
for guideline in self.guidelines:
|
|
284
|
+
# 低复杂度
|
|
285
|
+
if guideline.complexity == "low":
|
|
286
|
+
user_impact = self._determine_user_impact(guideline)
|
|
287
|
+
|
|
288
|
+
# 高或中等影响
|
|
289
|
+
if user_impact in ["high", "medium"]:
|
|
290
|
+
quick_wins.append(UXRecommendation(
|
|
291
|
+
guideline=guideline,
|
|
292
|
+
priority="high",
|
|
293
|
+
implementation_effort="low",
|
|
294
|
+
user_impact=user_impact,
|
|
295
|
+
resources=self._get_resources(guideline)
|
|
296
|
+
))
|
|
297
|
+
|
|
298
|
+
# 按领域分组,避免同一领域的建议过多
|
|
299
|
+
from collections import defaultdict
|
|
300
|
+
domain_groups = defaultdict(list)
|
|
301
|
+
for rec in quick_wins:
|
|
302
|
+
domain_groups[rec.guideline.domain].append(rec)
|
|
303
|
+
|
|
304
|
+
# 从每个领域选择最好的
|
|
305
|
+
result: list[UXRecommendation] = []
|
|
306
|
+
domains = list(domain_groups.keys())
|
|
307
|
+
random.shuffle(domains)
|
|
308
|
+
|
|
309
|
+
while len(result) < max_results and domains:
|
|
310
|
+
domain = domains.pop(0)
|
|
311
|
+
if domain_groups[domain]:
|
|
312
|
+
result.append(domain_groups[domain].pop(0))
|
|
313
|
+
|
|
314
|
+
return result
|
|
315
|
+
|
|
316
|
+
def get_checklist(self, domains: list[str] | None = None) -> dict[str, list[str]]:
|
|
317
|
+
"""
|
|
318
|
+
获取 UX 检查清单
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
domains: 要包含的领域列表,None 表示全部
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
按领域分组的检查清单
|
|
325
|
+
"""
|
|
326
|
+
checklist: dict[str, list[str]] = {}
|
|
327
|
+
|
|
328
|
+
for guideline in self.guidelines:
|
|
329
|
+
domain = guideline.domain.value
|
|
330
|
+
|
|
331
|
+
# 领域过滤
|
|
332
|
+
if domains and domain not in domains:
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
if domain not in checklist:
|
|
336
|
+
checklist[domain] = []
|
|
337
|
+
|
|
338
|
+
checklist[domain].append(
|
|
339
|
+
f"[ ] {guideline.best_practice}"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return checklist
|
|
343
|
+
|
|
344
|
+
def get_anti_patterns(self) -> dict[str, list[dict[str, str]]]:
|
|
345
|
+
"""
|
|
346
|
+
获取所有反模式
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
按领域分组的反模式
|
|
350
|
+
"""
|
|
351
|
+
anti_patterns: dict[str, list[dict[str, str]]] = {}
|
|
352
|
+
|
|
353
|
+
for guideline in self.guidelines:
|
|
354
|
+
domain = guideline.domain.value
|
|
355
|
+
|
|
356
|
+
if domain not in anti_patterns:
|
|
357
|
+
anti_patterns[domain] = []
|
|
358
|
+
|
|
359
|
+
anti_patterns[domain].append({
|
|
360
|
+
"anti_pattern": guideline.anti_pattern,
|
|
361
|
+
"best_practice": guideline.best_practice,
|
|
362
|
+
"impact": guideline.impact
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
return anti_patterns
|
|
366
|
+
|
|
367
|
+
def list_domains(self) -> list[str]:
|
|
368
|
+
"""列出所有领域"""
|
|
369
|
+
return list(set(g.domain.value for g in self.guidelines))
|
|
370
|
+
|
|
371
|
+
def list_topics(self, domain: str | None = None) -> list[str]:
|
|
372
|
+
"""
|
|
373
|
+
列出所有主题
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
domain: 可选的领域过滤
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
主题列表
|
|
380
|
+
"""
|
|
381
|
+
if domain:
|
|
382
|
+
guidelines = self.get_guidelines_by_domain(domain)
|
|
383
|
+
return list(set(g.topic for g in guidelines))
|
|
384
|
+
|
|
385
|
+
return list(set(g.topic for g in self.guidelines))
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# 便捷函数
|
|
389
|
+
def get_ux_guide(data_dir: Path | None = None) -> UXGuideEngine:
|
|
390
|
+
"""获取 UX 指南引擎实例"""
|
|
391
|
+
return UXGuideEngine(data_dir)
|