dash-devtools 1.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.
- dash_devtools/__init__.py +8 -0
- dash_devtools/__main__.py +11 -0
- dash_devtools/ai_engine.py +441 -0
- dash_devtools/browser.py +541 -0
- dash_devtools/cli.py +1452 -0
- dash_devtools/database.py +338 -0
- dash_devtools/dbdiagram.py +183 -0
- dash_devtools/e2e.py +329 -0
- dash_devtools/fixers/__init__.py +57 -0
- dash_devtools/fixers/migration_fixer.py +115 -0
- dash_devtools/fixers/ux_fixer.py +106 -0
- dash_devtools/fixers/version_bumper.py +115 -0
- dash_devtools/gas_mes_test.py +1241 -0
- dash_devtools/generators/__init__.py +84 -0
- dash_devtools/health.py +476 -0
- dash_devtools/hooks/__init__.py +250 -0
- dash_devtools/hooks/pre_commit.py +161 -0
- dash_devtools/hooks/pre_push.py +275 -0
- dash_devtools/init_test.py +352 -0
- dash_devtools/markdown_report.py +309 -0
- dash_devtools/migrators/__init__.py +21 -0
- dash_devtools/perf.py +321 -0
- dash_devtools/report.py +667 -0
- dash_devtools/reporters/__init__.py +11 -0
- dash_devtools/spec.py +230 -0
- dash_devtools/stats.py +355 -0
- dash_devtools/test_suite.py +690 -0
- dash_devtools/testing.py +416 -0
- dash_devtools/validators/__init__.py +157 -0
- dash_devtools/validators/backend/__init__.py +12 -0
- dash_devtools/validators/backend/nodejs.py +245 -0
- dash_devtools/validators/backend/python.py +439 -0
- dash_devtools/validators/code_quality.py +243 -0
- dash_devtools/validators/common/__init__.py +11 -0
- dash_devtools/validators/common/quality.py +319 -0
- dash_devtools/validators/common/security.py +270 -0
- dash_devtools/validators/common/spec.py +273 -0
- dash_devtools/validators/detector.py +394 -0
- dash_devtools/validators/frontend/__init__.py +14 -0
- dash_devtools/validators/frontend/angular.py +245 -0
- dash_devtools/validators/frontend/gas.py +310 -0
- dash_devtools/validators/frontend/vite.py +539 -0
- dash_devtools/validators/migration.py +292 -0
- dash_devtools/validators/performance.py +167 -0
- dash_devtools/validators/security.py +205 -0
- dash_devtools/vision/__init__.py +368 -0
- dash_devtools/watch.py +266 -0
- dash_devtools/word_report.py +690 -0
- dash_devtools-1.0.0.dist-info/METADATA +834 -0
- dash_devtools-1.0.0.dist-info/RECORD +53 -0
- dash_devtools-1.0.0.dist-info/WHEEL +5 -0
- dash_devtools-1.0.0.dist-info/entry_points.txt +2 -0
- dash_devtools-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DashAI DevTools - AI Engine
|
|
3
|
+
使用 Google GenAI SDK (Gemini) - 新版 API
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
# 檢查 Google GenAI SDK (新版)
|
|
13
|
+
GENAI_AVAILABLE = False
|
|
14
|
+
_GENAI_IMPORT_ERROR = ""
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from google import genai
|
|
18
|
+
from google.genai import types
|
|
19
|
+
GENAI_AVAILABLE = True
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
_GENAI_IMPORT_ERROR = str(e)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_dotenv_multi_path() -> list[str]:
|
|
25
|
+
"""
|
|
26
|
+
多路徑載入 .env 檔案
|
|
27
|
+
|
|
28
|
+
搜尋順序:
|
|
29
|
+
1. 當前工作目錄 .env
|
|
30
|
+
2. 使用者家目錄 ~/.env
|
|
31
|
+
3. dash-devtools 專案目錄 .env
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
成功載入的路徑列表
|
|
35
|
+
"""
|
|
36
|
+
loaded_paths = []
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from dotenv import load_dotenv
|
|
40
|
+
except ImportError:
|
|
41
|
+
return loaded_paths # dotenv 未安裝
|
|
42
|
+
|
|
43
|
+
# 搜尋路徑列表
|
|
44
|
+
search_paths = [
|
|
45
|
+
Path.cwd() / '.env', # 當前目錄
|
|
46
|
+
Path.home() / '.env', # 家目錄
|
|
47
|
+
Path(__file__).parent.parent / '.env', # dash-devtools 根目錄
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for env_path in search_paths:
|
|
51
|
+
if env_path.exists():
|
|
52
|
+
load_dotenv(env_path, override=False) # 不覆蓋已存在的值
|
|
53
|
+
loaded_paths.append(str(env_path))
|
|
54
|
+
|
|
55
|
+
return loaded_paths
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _mask_api_key(key: str) -> str:
|
|
59
|
+
"""隱藏 API Key 中間字元"""
|
|
60
|
+
if len(key) <= 8:
|
|
61
|
+
return key[:2] + '*' * (len(key) - 2)
|
|
62
|
+
return key[:4] + '*' * (len(key) - 8) + key[-4:]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _debug_env_info() -> str:
|
|
66
|
+
"""產生除錯資訊"""
|
|
67
|
+
lines = []
|
|
68
|
+
|
|
69
|
+
# 檢查 GEMINI_API_KEY 是否存在
|
|
70
|
+
api_key = os.environ.get("GEMINI_API_KEY")
|
|
71
|
+
if api_key:
|
|
72
|
+
lines.append(f" GEMINI_API_KEY: {_mask_api_key(api_key)} (已設定)")
|
|
73
|
+
else:
|
|
74
|
+
lines.append(" GEMINI_API_KEY: (未設定)")
|
|
75
|
+
|
|
76
|
+
# 列出所有 GEMINI 或 GOOGLE 相關的環境變數
|
|
77
|
+
related_keys = [k for k in os.environ.keys()
|
|
78
|
+
if 'GEMINI' in k.upper() or 'GOOGLE' in k.upper()]
|
|
79
|
+
if related_keys:
|
|
80
|
+
lines.append(" 相關環境變數:")
|
|
81
|
+
for k in sorted(related_keys):
|
|
82
|
+
val = os.environ.get(k, "")
|
|
83
|
+
if 'KEY' in k.upper() or 'SECRET' in k.upper() or 'TOKEN' in k.upper():
|
|
84
|
+
val = _mask_api_key(val) if val else "(空)"
|
|
85
|
+
else:
|
|
86
|
+
val = val[:50] + "..." if len(val) > 50 else val
|
|
87
|
+
lines.append(f" {k}: {val}")
|
|
88
|
+
|
|
89
|
+
return "\n".join(lines)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AIModel(Enum):
|
|
93
|
+
"""可用的 AI 模型"""
|
|
94
|
+
GEMINI_FLASH = "gemini-2.5-flash" # 最新快速版
|
|
95
|
+
GEMINI_PRO = "gemini-2.0-flash" # 穩定版
|
|
96
|
+
GEMINI_FLASH_LITE = "gemini-2.0-flash-lite" # 輕量版
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class AIResponse:
|
|
101
|
+
"""AI 回應結構"""
|
|
102
|
+
success: bool
|
|
103
|
+
content: str
|
|
104
|
+
model: str
|
|
105
|
+
prompt_tokens: int = 0
|
|
106
|
+
completion_tokens: int = 0
|
|
107
|
+
error: Optional[str] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AIEngine:
|
|
111
|
+
"""
|
|
112
|
+
DashAI DevTools AI 引擎
|
|
113
|
+
|
|
114
|
+
使用方式:
|
|
115
|
+
from dash_devtools.ai_engine import AIEngine
|
|
116
|
+
|
|
117
|
+
ai = AIEngine()
|
|
118
|
+
response = ai.analyze_code("def foo(): pass")
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
model: AIModel = AIModel.GEMINI_FLASH,
|
|
124
|
+
api_key: Optional[str] = None
|
|
125
|
+
):
|
|
126
|
+
"""
|
|
127
|
+
初始化 AI 引擎
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
model: 使用的模型 (預設 gemini-2.5-flash)
|
|
131
|
+
api_key: API Key (預設從環境變數 GEMINI_API_KEY 讀取)
|
|
132
|
+
"""
|
|
133
|
+
# 在 __init__ 最前面載入 .env (多路徑搜尋)
|
|
134
|
+
loaded_paths = _load_dotenv_multi_path()
|
|
135
|
+
|
|
136
|
+
if not GENAI_AVAILABLE:
|
|
137
|
+
raise ImportError(
|
|
138
|
+
f"Google GenAI SDK 未安裝或載入失敗。\n"
|
|
139
|
+
f"錯誤: {_GENAI_IMPORT_ERROR}\n"
|
|
140
|
+
f"請執行: pip install google-genai"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self.api_key = api_key or os.environ.get("GEMINI_API_KEY")
|
|
144
|
+
if not self.api_key:
|
|
145
|
+
# 除錯資訊
|
|
146
|
+
debug_info = _debug_env_info()
|
|
147
|
+
loaded_info = ""
|
|
148
|
+
if loaded_paths:
|
|
149
|
+
loaded_info = f"\n已搜尋的 .env 檔案:\n " + "\n ".join(loaded_paths)
|
|
150
|
+
else:
|
|
151
|
+
loaded_info = "\n未找到任何 .env 檔案"
|
|
152
|
+
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"未設定 GEMINI_API_KEY。\n"
|
|
155
|
+
f"請設定環境變數: export GEMINI_API_KEY='your-api-key'\n"
|
|
156
|
+
f"或在 .env 檔案中設定: GEMINI_API_KEY=your-api-key\n"
|
|
157
|
+
f"\n診斷資訊:\n{debug_info}"
|
|
158
|
+
f"{loaded_info}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.model_name = model.value
|
|
162
|
+
self.client = genai.Client(api_key=self.api_key)
|
|
163
|
+
|
|
164
|
+
def generate(
|
|
165
|
+
self,
|
|
166
|
+
prompt: str,
|
|
167
|
+
system_prompt: Optional[str] = None,
|
|
168
|
+
temperature: float = 0.7,
|
|
169
|
+
max_tokens: int = 2048
|
|
170
|
+
) -> AIResponse:
|
|
171
|
+
"""
|
|
172
|
+
生成文字回應
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
prompt: 使用者提示
|
|
176
|
+
system_prompt: 系統提示 (可選)
|
|
177
|
+
temperature: 創意度 (0.0-1.0)
|
|
178
|
+
max_tokens: 最大輸出 token 數
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
AIResponse 物件
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
full_prompt = prompt
|
|
185
|
+
if system_prompt:
|
|
186
|
+
full_prompt = f"{system_prompt}\n\n{prompt}"
|
|
187
|
+
|
|
188
|
+
config = types.GenerateContentConfig(
|
|
189
|
+
temperature=temperature,
|
|
190
|
+
max_output_tokens=max_tokens
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
response = self.client.models.generate_content(
|
|
194
|
+
model=self.model_name,
|
|
195
|
+
contents=full_prompt,
|
|
196
|
+
config=config
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# 取得 token 使用量
|
|
200
|
+
prompt_tokens = 0
|
|
201
|
+
completion_tokens = 0
|
|
202
|
+
if hasattr(response, 'usage_metadata') and response.usage_metadata:
|
|
203
|
+
prompt_tokens = getattr(response.usage_metadata, 'prompt_token_count', 0) or 0
|
|
204
|
+
completion_tokens = getattr(response.usage_metadata, 'candidates_token_count', 0) or 0
|
|
205
|
+
|
|
206
|
+
return AIResponse(
|
|
207
|
+
success=True,
|
|
208
|
+
content=response.text,
|
|
209
|
+
model=self.model_name,
|
|
210
|
+
prompt_tokens=prompt_tokens,
|
|
211
|
+
completion_tokens=completion_tokens
|
|
212
|
+
)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return AIResponse(
|
|
215
|
+
success=False,
|
|
216
|
+
content="",
|
|
217
|
+
model=self.model_name,
|
|
218
|
+
error=str(e)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def analyze_code(
|
|
222
|
+
self,
|
|
223
|
+
code: str,
|
|
224
|
+
language: str = "auto",
|
|
225
|
+
focus: str = "general"
|
|
226
|
+
) -> AIResponse:
|
|
227
|
+
"""
|
|
228
|
+
分析程式碼
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
code: 要分析的程式碼
|
|
232
|
+
language: 程式語言 (auto 自動偵測)
|
|
233
|
+
focus: 分析重點 (general/security/performance/quality)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
AIResponse 物件
|
|
237
|
+
"""
|
|
238
|
+
focus_prompts = {
|
|
239
|
+
"general": "請全面分析這段程式碼,包括可讀性、潛在問題、最佳實踐建議。",
|
|
240
|
+
"security": "請從安全性角度分析這段程式碼,找出潛在的安全漏洞和風險。",
|
|
241
|
+
"performance": "請從效能角度分析這段程式碼,找出可能的效能瓶頸和優化建議。",
|
|
242
|
+
"quality": "請從程式碼品質角度分析,包括命名規範、結構設計、可維護性。"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
system_prompt = """你是一位資深的軟體工程師和程式碼審查專家。
|
|
246
|
+
請用正體中文(台灣用語)回答。
|
|
247
|
+
回答要具體、有建設性,並提供改善範例。"""
|
|
248
|
+
|
|
249
|
+
prompt = f"""
|
|
250
|
+
{focus_prompts.get(focus, focus_prompts["general"])}
|
|
251
|
+
|
|
252
|
+
程式語言: {language}
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
{code}
|
|
256
|
+
```
|
|
257
|
+
"""
|
|
258
|
+
return self.generate(prompt, system_prompt, temperature=0.3)
|
|
259
|
+
|
|
260
|
+
def suggest_fix(
|
|
261
|
+
self,
|
|
262
|
+
code: str,
|
|
263
|
+
error_message: str,
|
|
264
|
+
language: str = "auto"
|
|
265
|
+
) -> AIResponse:
|
|
266
|
+
"""
|
|
267
|
+
建議修復方案
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
code: 有問題的程式碼
|
|
271
|
+
error_message: 錯誤訊息
|
|
272
|
+
language: 程式語言
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
AIResponse 物件包含修復建議
|
|
276
|
+
"""
|
|
277
|
+
system_prompt = """你是一位除錯專家。
|
|
278
|
+
請用正體中文(台灣用語)回答。
|
|
279
|
+
請提供:
|
|
280
|
+
1. 問題原因分析
|
|
281
|
+
2. 修復後的完整程式碼
|
|
282
|
+
3. 預防類似問題的建議"""
|
|
283
|
+
|
|
284
|
+
prompt = f"""
|
|
285
|
+
請幫我修復以下程式碼的問題:
|
|
286
|
+
|
|
287
|
+
錯誤訊息:
|
|
288
|
+
```
|
|
289
|
+
{error_message}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
程式碼 ({language}):
|
|
293
|
+
```
|
|
294
|
+
{code}
|
|
295
|
+
```
|
|
296
|
+
"""
|
|
297
|
+
return self.generate(prompt, system_prompt, temperature=0.2)
|
|
298
|
+
|
|
299
|
+
def generate_tests(
|
|
300
|
+
self,
|
|
301
|
+
code: str,
|
|
302
|
+
framework: str = "auto",
|
|
303
|
+
coverage: str = "comprehensive"
|
|
304
|
+
) -> AIResponse:
|
|
305
|
+
"""
|
|
306
|
+
生成測試程式碼
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
code: 要測試的程式碼
|
|
310
|
+
framework: 測試框架 (auto/pytest/jest/vitest)
|
|
311
|
+
coverage: 覆蓋範圍 (basic/comprehensive/edge-cases)
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
AIResponse 物件包含測試程式碼
|
|
315
|
+
"""
|
|
316
|
+
coverage_desc = {
|
|
317
|
+
"basic": "基本功能測試",
|
|
318
|
+
"comprehensive": "全面測試,包括正常流程和邊界情況",
|
|
319
|
+
"edge-cases": "專注於邊界條件和異常處理"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
system_prompt = f"""你是一位測試專家。
|
|
323
|
+
請用正體中文註解。
|
|
324
|
+
測試框架: {framework}
|
|
325
|
+
覆蓋範圍: {coverage_desc.get(coverage, coverage_desc["comprehensive"])}"""
|
|
326
|
+
|
|
327
|
+
prompt = f"""
|
|
328
|
+
請為以下程式碼生成測試:
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
{code}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
要求:
|
|
335
|
+
- 使用 {framework} 框架
|
|
336
|
+
- 包含 {coverage_desc.get(coverage, coverage)} 的測試案例
|
|
337
|
+
- 每個測試案例都要有清楚的名稱和註解
|
|
338
|
+
"""
|
|
339
|
+
return self.generate(prompt, system_prompt, temperature=0.3)
|
|
340
|
+
|
|
341
|
+
def explain_code(
|
|
342
|
+
self,
|
|
343
|
+
code: str,
|
|
344
|
+
detail_level: str = "medium"
|
|
345
|
+
) -> AIResponse:
|
|
346
|
+
"""
|
|
347
|
+
解釋程式碼
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
code: 要解釋的程式碼
|
|
351
|
+
detail_level: 詳細程度 (brief/medium/detailed)
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
AIResponse 物件包含解釋
|
|
355
|
+
"""
|
|
356
|
+
detail_prompts = {
|
|
357
|
+
"brief": "請簡潔說明這段程式碼的功能(2-3 句話)。",
|
|
358
|
+
"medium": "請說明這段程式碼的功能、主要邏輯和使用方式。",
|
|
359
|
+
"detailed": "請詳細說明這段程式碼,包括每個函數的作用、資料流、設計模式等。"
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
system_prompt = """你是一位技術文件撰寫專家。
|
|
363
|
+
請用正體中文(台灣用語)回答。
|
|
364
|
+
使用清晰的結構化格式。"""
|
|
365
|
+
|
|
366
|
+
prompt = f"""
|
|
367
|
+
{detail_prompts.get(detail_level, detail_prompts["medium"])}
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
{code}
|
|
371
|
+
```
|
|
372
|
+
"""
|
|
373
|
+
return self.generate(prompt, system_prompt, temperature=0.3)
|
|
374
|
+
|
|
375
|
+
def review_commit(
|
|
376
|
+
self,
|
|
377
|
+
diff: str,
|
|
378
|
+
commit_message: str = ""
|
|
379
|
+
) -> AIResponse:
|
|
380
|
+
"""
|
|
381
|
+
審查 Git Commit
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
diff: Git diff 內容
|
|
385
|
+
commit_message: Commit 訊息
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
AIResponse 物件包含審查結果
|
|
389
|
+
"""
|
|
390
|
+
system_prompt = """你是一位資深的程式碼審查員。
|
|
391
|
+
請用正體中文(台灣用語)回答。
|
|
392
|
+
審查要點:
|
|
393
|
+
1. 程式碼品質
|
|
394
|
+
2. 潛在問題
|
|
395
|
+
3. 安全性考量
|
|
396
|
+
4. Commit 訊息是否清楚描述變更"""
|
|
397
|
+
|
|
398
|
+
prompt = f"""
|
|
399
|
+
請審查這個 commit:
|
|
400
|
+
|
|
401
|
+
Commit 訊息: {commit_message or "(無)"}
|
|
402
|
+
|
|
403
|
+
變更內容:
|
|
404
|
+
```diff
|
|
405
|
+
{diff}
|
|
406
|
+
```
|
|
407
|
+
"""
|
|
408
|
+
return self.generate(prompt, system_prompt, temperature=0.3)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# 快速存取函數
|
|
412
|
+
def get_ai(model: AIModel = AIModel.GEMINI_FLASH) -> AIEngine:
|
|
413
|
+
"""
|
|
414
|
+
取得 AI 引擎實例
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
model: 使用的模型
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
AIEngine 實例
|
|
421
|
+
"""
|
|
422
|
+
return AIEngine(model=model)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# CLI 整合
|
|
426
|
+
def ai_analyze_command(path: str, focus: str = "general") -> None:
|
|
427
|
+
"""CLI 分析指令"""
|
|
428
|
+
try:
|
|
429
|
+
ai = get_ai()
|
|
430
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
431
|
+
code = f.read()
|
|
432
|
+
|
|
433
|
+
response = ai.analyze_code(code, focus=focus)
|
|
434
|
+
if response.success:
|
|
435
|
+
print(response.content)
|
|
436
|
+
else:
|
|
437
|
+
print(f"[!] 錯誤: {response.error}")
|
|
438
|
+
except FileNotFoundError:
|
|
439
|
+
print(f"[!] 找不到檔案: {path}")
|
|
440
|
+
except Exception as e:
|
|
441
|
+
print(f"[!] 錯誤: {e}")
|