jarvis-ai-assistant 0.2.4__py3-none-any.whl → 0.2.6__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +80 -16
- jarvis/jarvis_agent/edit_file_handler.py +5 -6
- jarvis/jarvis_code_agent/code_agent.py +17 -24
- jarvis/jarvis_data/config_schema.json +2 -19
- jarvis/jarvis_multi_agent/main.py +1 -0
- jarvis/jarvis_stats/cli.py +72 -5
- jarvis/jarvis_stats/stats.py +175 -70
- jarvis/jarvis_stats/storage.py +53 -1
- jarvis/jarvis_stats/visualizer.py +63 -224
- jarvis/jarvis_tools/cli/main.py +7 -9
- jarvis/jarvis_tools/registry.py +2 -5
- jarvis/jarvis_tools/retrieve_memory.py +206 -0
- jarvis/jarvis_tools/save_memory.py +142 -0
- jarvis/jarvis_utils/config.py +6 -8
- jarvis/jarvis_utils/globals.py +120 -1
- jarvis/jarvis_utils/methodology.py +74 -67
- jarvis/jarvis_utils/utils.py +362 -121
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/METADATA +11 -2
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/RECORD +24 -22
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
@@ -396,7 +396,7 @@ class Agent:
|
|
396
396
|
"""
|
397
397
|
return execute_tool_call(response, self)
|
398
398
|
|
399
|
-
def _complete_task(self) -> str:
|
399
|
+
def _complete_task(self, auto_completed: bool = False) -> str:
|
400
400
|
"""完成任务并生成总结(如果需要)
|
401
401
|
|
402
402
|
返回:
|
@@ -407,8 +407,26 @@ class Agent:
|
|
407
407
|
2. 对于子Agent: 可能会生成总结(如果启用)
|
408
408
|
3. 使用spinner显示生成状态
|
409
409
|
"""
|
410
|
+
satisfaction_feedback = ""
|
411
|
+
|
412
|
+
if not auto_completed and self.use_analysis:
|
413
|
+
if user_confirm("您对本次任务的完成是否满意?", True):
|
414
|
+
satisfaction_feedback = "\n\n用户对本次任务的完成表示满意。"
|
415
|
+
else:
|
416
|
+
feedback = self.multiline_inputer(
|
417
|
+
"请提供您的反馈意见(可留空直接回车):"
|
418
|
+
)
|
419
|
+
if feedback:
|
420
|
+
satisfaction_feedback = (
|
421
|
+
f"\n\n用户对本次任务的完成不满意,反馈意见如下:\n{feedback}"
|
422
|
+
)
|
423
|
+
else:
|
424
|
+
satisfaction_feedback = (
|
425
|
+
"\n\n用户对本次任务的完成不满意,未提供具体反馈意见。"
|
426
|
+
)
|
427
|
+
|
410
428
|
if self.use_analysis:
|
411
|
-
self._analysis_task()
|
429
|
+
self._analysis_task(satisfaction_feedback)
|
412
430
|
if self.need_summary:
|
413
431
|
print("📄 正在生成总结...")
|
414
432
|
self.session.prompt = self.summary_prompt
|
@@ -420,11 +438,13 @@ class Agent:
|
|
420
438
|
|
421
439
|
return "任务完成"
|
422
440
|
|
423
|
-
def _analysis_task(self):
|
441
|
+
def _analysis_task(self, satisfaction_feedback: str = ""):
|
424
442
|
print("🔍 正在分析任务...")
|
425
443
|
try:
|
426
444
|
# 让模型判断是否需要生成方法论
|
427
445
|
analysis_prompt = TASK_ANALYSIS_PROMPT
|
446
|
+
if satisfaction_feedback:
|
447
|
+
analysis_prompt += satisfaction_feedback
|
428
448
|
|
429
449
|
self.session.prompt = analysis_prompt
|
430
450
|
if not self.model:
|
@@ -452,6 +472,23 @@ class Agent:
|
|
452
472
|
else ""
|
453
473
|
)
|
454
474
|
|
475
|
+
# 检查工具列表并添加记忆工具相关提示
|
476
|
+
memory_prompts = ""
|
477
|
+
tool_registry = self.get_tool_registry()
|
478
|
+
if tool_registry:
|
479
|
+
tool_names = [tool.name for tool in tool_registry.tools.values()]
|
480
|
+
|
481
|
+
# 如果有save_memory工具,添加相关提示
|
482
|
+
if "save_memory" in tool_names:
|
483
|
+
memory_prompts += "\n - 如果有关键信息需要记忆,请调用save_memory工具进行记忆:"
|
484
|
+
memory_prompts += "\n * project_long_term: 保存与当前项目相关的长期信息"
|
485
|
+
memory_prompts += "\n * global_long_term: 保存通用的信息、用户喜好、知识、方法等"
|
486
|
+
memory_prompts += "\n * short_term: 保存当前任务相关的临时信息"
|
487
|
+
|
488
|
+
# 如果有retrieve_memory工具,添加相关提示
|
489
|
+
if "retrieve_memory" in tool_names:
|
490
|
+
memory_prompts += "\n - 如果需要检索相关记忆信息,请调用retrieve_memory工具"
|
491
|
+
|
455
492
|
addon_prompt = f"""
|
456
493
|
<system_prompt>
|
457
494
|
请判断是否已经完成任务,如果已经完成:
|
@@ -461,7 +498,7 @@ class Agent:
|
|
461
498
|
- 仅包含一个操作
|
462
499
|
- 如果信息不明确,请请求用户补充
|
463
500
|
- 如果执行过程中连续失败5次,请使用ask_user询问用户操作
|
464
|
-
- 操作列表:{action_handlers}
|
501
|
+
- 操作列表:{action_handlers}{memory_prompts}
|
465
502
|
</system_prompt>
|
466
503
|
|
467
504
|
请继续。
|
@@ -511,7 +548,7 @@ class Agent:
|
|
511
548
|
)
|
512
549
|
if user_input:
|
513
550
|
run_input_handlers = True
|
514
|
-
#
|
551
|
+
# 如果有工具调用且用户确认继续,则继续执行工具调用
|
515
552
|
if any(
|
516
553
|
handler.can_handle(current_response)
|
517
554
|
for handler in self.output_handler
|
@@ -519,13 +556,19 @@ class Agent:
|
|
519
556
|
if user_confirm(
|
520
557
|
"检测到有工具调用,是否继续处理工具调用?", True
|
521
558
|
):
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
559
|
+
# 先添加用户干预信息到session
|
560
|
+
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
|
561
|
+
# 继续执行下面的工具调用逻辑,不要continue跳过
|
562
|
+
else:
|
563
|
+
# 用户选择不继续处理工具调用
|
564
|
+
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
526
565
|
continue
|
527
|
-
|
528
|
-
|
566
|
+
else:
|
567
|
+
# 没有检测到工具调用
|
568
|
+
self.session.prompt = (
|
569
|
+
f"被用户中断,用户补充信息为:{user_input}"
|
570
|
+
)
|
571
|
+
continue
|
529
572
|
|
530
573
|
need_return, self.session.prompt = self._call_tools(
|
531
574
|
current_response
|
@@ -541,7 +584,7 @@ class Agent:
|
|
541
584
|
continue
|
542
585
|
|
543
586
|
if self.auto_complete and ot("!!!COMPLETE!!!") in current_response:
|
544
|
-
return self._complete_task()
|
587
|
+
return self._complete_task(auto_completed=True)
|
545
588
|
|
546
589
|
# 获取用户输入
|
547
590
|
user_input = self.multiline_inputer(
|
@@ -554,7 +597,7 @@ class Agent:
|
|
554
597
|
continue
|
555
598
|
|
556
599
|
if not user_input:
|
557
|
-
return self._complete_task()
|
600
|
+
return self._complete_task(auto_completed=False)
|
558
601
|
|
559
602
|
except Exception as e:
|
560
603
|
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
@@ -565,6 +608,23 @@ class Agent:
|
|
565
608
|
return f"Task failed: {str(e)}"
|
566
609
|
|
567
610
|
def _first_run(self):
|
611
|
+
# 获取所有记忆标签并添加到提示中
|
612
|
+
from jarvis.jarvis_utils.globals import get_all_memory_tags
|
613
|
+
|
614
|
+
memory_tags = get_all_memory_tags()
|
615
|
+
memory_tags_prompt = ""
|
616
|
+
|
617
|
+
if any(tags for tags in memory_tags.values()):
|
618
|
+
memory_tags_prompt = "\n\n系统中存在以下记忆标签,你可以使用 retrieve_memory 工具检索相关记忆:"
|
619
|
+
for memory_type, tags in memory_tags.items():
|
620
|
+
if tags:
|
621
|
+
type_name = {
|
622
|
+
"short_term": "短期记忆",
|
623
|
+
"project_long_term": "项目长期记忆",
|
624
|
+
"global_long_term": "全局长期记忆"
|
625
|
+
}.get(memory_type, memory_type)
|
626
|
+
memory_tags_prompt += f"\n- {type_name}: {', '.join(tags)}"
|
627
|
+
|
568
628
|
# 如果有上传文件,先上传文件
|
569
629
|
if self.model and self.model.support_upload_files():
|
570
630
|
if self.use_methodology:
|
@@ -577,12 +637,12 @@ class Agent:
|
|
577
637
|
msg = self.session.prompt
|
578
638
|
for handler in self.input_handler:
|
579
639
|
msg, _ = handler(msg, self)
|
580
|
-
self.session.prompt = f"{self.session.prompt}\n\n以下是历史类似问题的执行经验,可参考:\n{load_methodology(msg, self.get_tool_registry())}"
|
640
|
+
self.session.prompt = f"{self.session.prompt}\n\n以下是历史类似问题的执行经验,可参考:\n{load_methodology(msg, self.get_tool_registry())}{memory_tags_prompt}"
|
581
641
|
else:
|
582
642
|
if self.files:
|
583
|
-
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息和方法论文件,可以从中获取一些经验信息。"
|
643
|
+
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息和方法论文件,可以从中获取一些经验信息。{memory_tags_prompt}"
|
584
644
|
else:
|
585
|
-
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息,可以从中获取一些经验信息。"
|
645
|
+
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息,可以从中获取一些经验信息。{memory_tags_prompt}"
|
586
646
|
elif self.files:
|
587
647
|
if not self.model.upload_files(self.files):
|
588
648
|
PrettyOutput.print(
|
@@ -598,6 +658,10 @@ class Agent:
|
|
598
658
|
for handler in self.input_handler:
|
599
659
|
msg, _ = handler(msg, self)
|
600
660
|
self.session.prompt = f"{self.session.prompt}\n\n以下是历史类似问题的执行经验,可参考:\n{load_methodology(msg, self.get_tool_registry())}"
|
661
|
+
|
662
|
+
# 添加记忆标签提示
|
663
|
+
if memory_tags_prompt:
|
664
|
+
self.session.prompt = f"{self.session.prompt}{memory_tags_prompt}"
|
601
665
|
|
602
666
|
self.first = False
|
603
667
|
|
@@ -58,8 +58,7 @@ class EditFileHandler(OutputHandler):
|
|
58
58
|
|
59
59
|
# 记录 edit_file 工具调用统计
|
60
60
|
from jarvis.jarvis_stats.stats import StatsManager
|
61
|
-
|
62
|
-
stats_manager.increment("edit_file", group="tool")
|
61
|
+
StatsManager.increment("edit_file", group="tool")
|
63
62
|
|
64
63
|
results = []
|
65
64
|
|
@@ -205,9 +204,9 @@ class EditFileHandler(OutputHandler):
|
|
205
204
|
found = False
|
206
205
|
|
207
206
|
if exact_search in modified_content:
|
208
|
-
#
|
207
|
+
# 直接执行替换(保留所有原始格式),只替换第一个匹配
|
209
208
|
modified_content = modified_content.replace(
|
210
|
-
exact_search, replace_text
|
209
|
+
exact_search, replace_text, 1
|
211
210
|
)
|
212
211
|
print(f"✅ 补丁 #{patch_count} 应用成功")
|
213
212
|
found = True
|
@@ -223,7 +222,7 @@ class EditFileHandler(OutputHandler):
|
|
223
222
|
stripped_replace = replace_text[1:-1]
|
224
223
|
if stripped_search in modified_content:
|
225
224
|
modified_content = modified_content.replace(
|
226
|
-
stripped_search, stripped_replace
|
225
|
+
stripped_search, stripped_replace, 1
|
227
226
|
)
|
228
227
|
print(f"✅ 补丁 #{patch_count} 应用成功 (自动去除首尾换行)")
|
229
228
|
found = True
|
@@ -252,7 +251,7 @@ class EditFileHandler(OutputHandler):
|
|
252
251
|
)
|
253
252
|
if indented_search in modified_content:
|
254
253
|
modified_content = modified_content.replace(
|
255
|
-
indented_search, indented_replace
|
254
|
+
indented_search, indented_replace, 1
|
256
255
|
)
|
257
256
|
print(
|
258
257
|
f"✅ 补丁 #{patch_count} 应用成功 (自动增加 {space_count} 个空格缩进)"
|
@@ -65,6 +65,8 @@ class CodeAgent:
|
|
65
65
|
"ask_user",
|
66
66
|
"read_code",
|
67
67
|
"rewrite_file",
|
68
|
+
"save_memory",
|
69
|
+
"retrieve_memory",
|
68
70
|
]
|
69
71
|
)
|
70
72
|
code_system_prompt = """
|
@@ -269,7 +271,7 @@ class CodeAgent:
|
|
269
271
|
return
|
270
272
|
|
271
273
|
PrettyOutput.print(
|
272
|
-
"⚠️
|
274
|
+
"⚠️ 正在修改git换行符敏感设置,这会影响所有文件的换行符处理方式",
|
273
275
|
OutputType.WARNING,
|
274
276
|
)
|
275
277
|
print("将进行以下设置:")
|
@@ -277,18 +279,15 @@ class CodeAgent:
|
|
277
279
|
current = current_settings.get(key, "未设置")
|
278
280
|
print(f" {key}: {current} -> {value}")
|
279
281
|
|
280
|
-
|
281
|
-
|
282
|
-
|
282
|
+
# 直接执行设置,不需要用户确认
|
283
|
+
for key, value in target_settings.items():
|
284
|
+
subprocess.run(["git", "config", key, value], check=True)
|
283
285
|
|
284
|
-
|
285
|
-
|
286
|
-
|
286
|
+
# 对于Windows系统,提示用户可以创建.gitattributes文件
|
287
|
+
if sys.platform.startswith("win"):
|
288
|
+
self._handle_windows_line_endings()
|
287
289
|
|
288
|
-
|
289
|
-
else:
|
290
|
-
print("❌ 用户取消修改git换行符敏感设置")
|
291
|
-
sys.exit(0)
|
290
|
+
print("✅ git换行符敏感设置已更新")
|
292
291
|
|
293
292
|
def _handle_windows_line_endings(self) -> None:
|
294
293
|
"""在Windows系统上处理换行符问题,提供建议而非强制修改"""
|
@@ -348,13 +347,11 @@ class CodeAgent:
|
|
348
347
|
from jarvis.jarvis_stats.stats import StatsManager
|
349
348
|
import re
|
350
349
|
|
351
|
-
stats_manager = StatsManager()
|
352
|
-
|
353
350
|
# 匹配插入行数
|
354
351
|
insertions_match = re.search(r"(\d+)\s+insertions?\(\+\)", diff_text)
|
355
352
|
if insertions_match:
|
356
353
|
insertions = int(insertions_match.group(1))
|
357
|
-
|
354
|
+
StatsManager.increment(
|
358
355
|
"code_lines_inserted", amount=insertions, group="code_agent"
|
359
356
|
)
|
360
357
|
|
@@ -362,7 +359,7 @@ class CodeAgent:
|
|
362
359
|
deletions_match = re.search(r"(\d+)\s+deletions?\(\-\)", diff_text)
|
363
360
|
if deletions_match:
|
364
361
|
deletions = int(deletions_match.group(1))
|
365
|
-
|
362
|
+
StatsManager.increment(
|
366
363
|
"code_lines_deleted", amount=deletions, group="code_agent"
|
367
364
|
)
|
368
365
|
|
@@ -397,8 +394,7 @@ class CodeAgent:
|
|
397
394
|
# 用户确认修改,统计修改次数
|
398
395
|
from jarvis.jarvis_stats.stats import StatsManager
|
399
396
|
|
400
|
-
|
401
|
-
stats_manager.increment("code_modification_confirmed", group="code_agent")
|
397
|
+
StatsManager.increment("code_modification_confirmed", group="code_agent")
|
402
398
|
|
403
399
|
try:
|
404
400
|
confirm_add_new_files()
|
@@ -430,7 +426,7 @@ class CodeAgent:
|
|
430
426
|
)
|
431
427
|
|
432
428
|
# 统计提交次数
|
433
|
-
|
429
|
+
StatsManager.increment("code_commits_accepted", group="code_agent")
|
434
430
|
except subprocess.CalledProcessError as e:
|
435
431
|
PrettyOutput.print(f"提交失败: {str(e)}", OutputType.ERROR)
|
436
432
|
|
@@ -455,8 +451,7 @@ class CodeAgent:
|
|
455
451
|
# 统计生成的commit数量
|
456
452
|
from jarvis.jarvis_stats.stats import StatsManager
|
457
453
|
|
458
|
-
|
459
|
-
stats_manager.increment("commits_generated", group="code_agent")
|
454
|
+
StatsManager.increment("commits_generated", group="code_agent")
|
460
455
|
|
461
456
|
commit_messages = "检测到以下提交记录:\n" + "\n".join(
|
462
457
|
f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits
|
@@ -472,8 +467,7 @@ class CodeAgent:
|
|
472
467
|
# 统计接受的commit数量
|
473
468
|
from jarvis.jarvis_stats.stats import StatsManager
|
474
469
|
|
475
|
-
|
476
|
-
stats_manager.increment("commits_accepted", group="code_agent")
|
470
|
+
StatsManager.increment("commits_accepted", group="code_agent")
|
477
471
|
|
478
472
|
subprocess.run(
|
479
473
|
["git", "reset", "--mixed", str(start_commit)],
|
@@ -581,8 +575,7 @@ class CodeAgent:
|
|
581
575
|
# 统计修改次数
|
582
576
|
from jarvis.jarvis_stats.stats import StatsManager
|
583
577
|
|
584
|
-
|
585
|
-
stats_manager.increment("code_modifications", group="code_agent")
|
578
|
+
StatsManager.increment("code_modifications", group="code_agent")
|
586
579
|
|
587
580
|
# 获取提交信息
|
588
581
|
end_hash = get_latest_commit_hash()
|
@@ -111,14 +111,9 @@
|
|
111
111
|
"description": "Git提交信息生成提示模板",
|
112
112
|
"default": ""
|
113
113
|
},
|
114
|
-
"JARVIS_MAX_TOKEN_COUNT": {
|
115
|
-
"type": "number",
|
116
|
-
"description": "模型能处理的最大token数量",
|
117
|
-
"default": 960000
|
118
|
-
},
|
119
114
|
"JARVIS_MAX_INPUT_TOKEN_COUNT": {
|
120
115
|
"type": "number",
|
121
|
-
"description": "模型能处理的最大输入token
|
116
|
+
"description": "模型能处理的最大输入token数量。其他token限制基于此值计算:最大token数量=此值×100,最大大内容尺寸=此值×5",
|
122
117
|
"default": 32000
|
123
118
|
},
|
124
119
|
"JARVIS_PLATFORM": {
|
@@ -171,17 +166,9 @@
|
|
171
166
|
"type": "string",
|
172
167
|
"default": "deep_seek_v3"
|
173
168
|
},
|
174
|
-
"JARVIS_MAX_TOKEN_COUNT": {
|
175
|
-
"type": "number",
|
176
|
-
"default": 960000
|
177
|
-
},
|
178
169
|
"JARVIS_MAX_INPUT_TOKEN_COUNT": {
|
179
170
|
"type": "number",
|
180
171
|
"default": 32000
|
181
|
-
},
|
182
|
-
"JARVIS_MAX_BIG_CONTENT_SIZE": {
|
183
|
-
"type": "number",
|
184
|
-
"default": 160000
|
185
172
|
}
|
186
173
|
},
|
187
174
|
"required": [
|
@@ -206,11 +193,7 @@
|
|
206
193
|
"description": "Jarvis数据存储目录路径",
|
207
194
|
"default": "~/.jarvis"
|
208
195
|
},
|
209
|
-
|
210
|
-
"type": "number",
|
211
|
-
"description": "最大大内容尺寸",
|
212
|
-
"default": 160000
|
213
|
-
},
|
196
|
+
|
214
197
|
"JARVIS_PRETTY_OUTPUT": {
|
215
198
|
"type": "boolean",
|
216
199
|
"description": "是否启用美化输出",
|
@@ -7,6 +7,7 @@ import yaml
|
|
7
7
|
from jarvis.jarvis_multi_agent import MultiAgent
|
8
8
|
from jarvis.jarvis_utils.input import get_multiline_input
|
9
9
|
from jarvis.jarvis_utils.utils import init_env
|
10
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
10
11
|
|
11
12
|
app = typer.Typer(help="多智能体系统启动器")
|
12
13
|
|
jarvis/jarvis_stats/cli.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
使用 typer 提供友好的命令行交互
|
5
5
|
"""
|
6
6
|
|
7
|
+
import builtins
|
7
8
|
from datetime import datetime, timedelta
|
8
9
|
from typing import Optional, List
|
9
10
|
import typer
|
@@ -183,13 +184,14 @@ def list():
|
|
183
184
|
table.add_column("单位", style="green")
|
184
185
|
table.add_column("最后更新", style="yellow")
|
185
186
|
table.add_column("7天数据点", style="magenta")
|
187
|
+
table.add_column("标签", style="blue")
|
186
188
|
|
187
189
|
# 获取每个指标的信息
|
188
190
|
end_time = datetime.now()
|
189
191
|
start_time = end_time - timedelta(days=7)
|
190
192
|
|
191
193
|
for metric in metrics:
|
192
|
-
info = stats.
|
194
|
+
info = stats._get_storage().get_metric_info(metric)
|
193
195
|
if info:
|
194
196
|
unit = info.get("unit", "-")
|
195
197
|
last_updated = info.get("last_updated", "-")
|
@@ -202,11 +204,36 @@ def list():
|
|
202
204
|
except:
|
203
205
|
pass
|
204
206
|
|
205
|
-
#
|
206
|
-
records = stats.
|
207
|
+
# 获取数据点数和标签
|
208
|
+
records = stats._get_storage().get_metrics(metric, start_time, end_time)
|
207
209
|
count = len(records)
|
208
|
-
|
209
|
-
|
210
|
+
|
211
|
+
# 收集所有唯一的标签
|
212
|
+
all_tags = {}
|
213
|
+
for record in records:
|
214
|
+
tags = record.get("tags", {})
|
215
|
+
for k, v in tags.items():
|
216
|
+
if k not in all_tags:
|
217
|
+
all_tags[k] = set()
|
218
|
+
all_tags[k].add(v)
|
219
|
+
|
220
|
+
# 格式化标签显示
|
221
|
+
tag_str = ""
|
222
|
+
if all_tags:
|
223
|
+
tag_parts = []
|
224
|
+
for k, values in sorted(all_tags.items()):
|
225
|
+
# 使用内置的list函数
|
226
|
+
values_list = sorted(builtins.list(values))
|
227
|
+
if len(values_list) == 1:
|
228
|
+
tag_parts.append(f"{k}={values_list[0]}")
|
229
|
+
else:
|
230
|
+
# 转义方括号以避免Rich markup错误
|
231
|
+
tag_parts.append(f"{k}=\\[{', '.join(values_list)}\\]")
|
232
|
+
tag_str = ", ".join(tag_parts)
|
233
|
+
else:
|
234
|
+
tag_str = "-"
|
235
|
+
|
236
|
+
table.add_row(metric, unit, last_updated, str(count), tag_str)
|
210
237
|
|
211
238
|
console.print(table)
|
212
239
|
rprint(f"\n[green]总计: {len(metrics)} 个指标[/green]")
|
@@ -278,6 +305,46 @@ def export(
|
|
278
305
|
rprint("[yellow]没有找到数据[/yellow]", file=sys.stderr)
|
279
306
|
|
280
307
|
|
308
|
+
@app.command()
|
309
|
+
def remove(
|
310
|
+
metric: str = typer.Argument(..., help="要删除的指标名称"),
|
311
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="跳过确认"),
|
312
|
+
):
|
313
|
+
"""删除指定的指标及其所有数据"""
|
314
|
+
if not yes:
|
315
|
+
# 显示指标信息供用户确认
|
316
|
+
stats = StatsManager(_get_stats_dir())
|
317
|
+
metrics = stats.list_metrics()
|
318
|
+
|
319
|
+
if metric not in metrics:
|
320
|
+
rprint(f"[red]错误:指标 '{metric}' 不存在[/red]")
|
321
|
+
return
|
322
|
+
|
323
|
+
# 获取指标的基本信息
|
324
|
+
info = stats._get_storage().get_metric_info(metric)
|
325
|
+
if info:
|
326
|
+
unit = info.get("unit", "-")
|
327
|
+
last_updated = info.get("last_updated", "-")
|
328
|
+
|
329
|
+
rprint(f"\n[yellow]准备删除指标:[/yellow]")
|
330
|
+
rprint(f" 名称: {metric}")
|
331
|
+
rprint(f" 单位: {unit}")
|
332
|
+
rprint(f" 最后更新: {last_updated}")
|
333
|
+
|
334
|
+
confirm = typer.confirm(f"\n确定要删除指标 '{metric}' 及其所有数据吗?")
|
335
|
+
if not confirm:
|
336
|
+
rprint("[yellow]已取消操作[/yellow]")
|
337
|
+
return
|
338
|
+
|
339
|
+
stats = StatsManager(_get_stats_dir())
|
340
|
+
success = stats.remove_metric(metric)
|
341
|
+
|
342
|
+
if success:
|
343
|
+
rprint(f"[green]✓[/green] 已成功删除指标: {metric}")
|
344
|
+
else:
|
345
|
+
rprint(f"[red]✗[/red] 删除失败:指标 '{metric}' 不存在")
|
346
|
+
|
347
|
+
|
281
348
|
@app.command()
|
282
349
|
def demo():
|
283
350
|
"""运行演示,展示统计模块的功能"""
|