mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-13 02:48:05 +00:00
chore: migrate repository to standard knowledge base layout
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
jsonl_file = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
report = []
|
||||
|
||||
def check_md_syntax(text, info_str):
|
||||
lines = text.split('\n')
|
||||
errors = []
|
||||
|
||||
# 1. 检查分隔符 (--- 或 ***)
|
||||
# 规范:应独占一行,前后建议有空行
|
||||
# 正则匹配:行首开始,至少3个-或*,行尾结束,允许行尾有空白
|
||||
separator_pattern = re.compile(r'^\s*([-*]{3,})\s*$')
|
||||
|
||||
# 2. 检查标题 (#)
|
||||
# 规范:#后必须有空格
|
||||
header_pattern = re.compile(r'^(#+)([^ \n].*)') # 捕获 #后紧跟非空格的
|
||||
|
||||
# 3. 代码块 (```)
|
||||
code_block_count = 0
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# 检查分隔符
|
||||
if separator_pattern.match(line):
|
||||
# 检查长度(虽然md规范>=3即可,但有些习惯是用3个)
|
||||
# 检查前后空行(非强制,但推荐)
|
||||
pass # 暂时只检查基本正则,如果夹杂在文本中通常不会独占一行
|
||||
|
||||
# 检查错误标题: #Title
|
||||
m = header_pattern.match(line)
|
||||
if m:
|
||||
# 排除掉特殊的Shebang或注释,比如 #!/bin/bash 或 #_Role (这个文件里的Title字段用了#_)
|
||||
# 但这里是content字段,应该遵循MD规范
|
||||
# 检查是否在代码块内
|
||||
if code_block_count % 2 == 0:
|
||||
# 忽略一些特定的meta标记,比如 # Role (有些prompt习惯)
|
||||
# 实际上标准MD里 #Role 也是不规范的标题
|
||||
# 允许一些特殊情况? 暂时严格检查
|
||||
errors.append(f"Line {i+1}: 标题格式可能错误 (缺少空格): '{line[:20]}...'" )
|
||||
|
||||
# 检查代码块闭合
|
||||
if line.strip().startswith('```'):
|
||||
code_block_count += 1
|
||||
|
||||
if code_block_count % 2 != 0:
|
||||
errors.append("代码块 (```) 未闭合")
|
||||
|
||||
if errors:
|
||||
report.append(f"\n📄 {info_str}")
|
||||
for e in errors:
|
||||
report.append(f" - {e}")
|
||||
|
||||
def analyze():
|
||||
print("正在检查 Markdown 语法...")
|
||||
try:
|
||||
with open(jsonl_file, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
if not line.strip(): continue
|
||||
try:
|
||||
item = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
print(f"❌ JSON 解析错误在第 {line_num} 行")
|
||||
continue
|
||||
|
||||
cat = item.get('category', 'Unknown')
|
||||
row = item.get('row', '?')
|
||||
title = item.get('title', 'No Title')
|
||||
content = item.get('content', '')
|
||||
|
||||
if not content:
|
||||
report.append(f"\n⚠️ {cat} | Row {row} | {title}: 内容为空")
|
||||
continue
|
||||
|
||||
info = f"[{cat}] Row {row}: {title}"
|
||||
check_md_syntax(content, info)
|
||||
|
||||
except FileNotFoundError:
|
||||
print("文件未找到")
|
||||
return
|
||||
|
||||
if not report:
|
||||
print("✅ 未发现明显的 Markdown 语法问题。 ")
|
||||
else:
|
||||
print(f"⚠️ 发现潜在问题 ({len(report)} 处):")
|
||||
for msg in report:
|
||||
print(msg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze()
|
||||
@@ -0,0 +1,87 @@
|
||||
# 提示词库同步配置 - 基于Excel完整数据
|
||||
source:
|
||||
excel_file: "prompt (2).xlsx"
|
||||
total_rows: 18
|
||||
total_cols: 3
|
||||
processed_date: "2025-02-02"
|
||||
|
||||
google_sheets:
|
||||
sheet_id: "1ngoQOhJqdguwNAilCl1joNwTje7FWWN9WiI2bo5VhpU"
|
||||
credentials_path: "./credentials.json"
|
||||
|
||||
output:
|
||||
prompts_dir: "./prompts"
|
||||
use_timestamp: true
|
||||
|
||||
naming:
|
||||
max_title_length: 30
|
||||
row_col_format: "({row},{col})"
|
||||
separator: "_"
|
||||
|
||||
sync:
|
||||
skip_rows: [] # 不跳过任何行,完整处理
|
||||
skip_keywords: [] # 完整保留所有内容
|
||||
|
||||
# Excel原始数据映射
|
||||
excel_mapping:
|
||||
prompts:
|
||||
- row: 0
|
||||
title: "提示词1a"
|
||||
versions: [1, 2, 3]
|
||||
content: ["提示词1a", "提示词1b", "提示词1c"]
|
||||
- row: 1
|
||||
title: "提示词2a"
|
||||
versions: [1, 2]
|
||||
content: ["提示词2a", "提示词2b"]
|
||||
- row: 3
|
||||
title: "提示词ya"
|
||||
versions: [1]
|
||||
content: ["提示词ya"]
|
||||
|
||||
tools:
|
||||
openai_optimizer:
|
||||
row: 5
|
||||
url: "https://platform.openai.com/chat/edit?models=gpt-5&optimize=true"
|
||||
description: "openai提示词优化网站"
|
||||
|
||||
social_media:
|
||||
twitter:
|
||||
row: 7
|
||||
url: "https://x.com/123olp"
|
||||
description: "点击关注我的推特,获取最新动态,首页接广告位"
|
||||
|
||||
support:
|
||||
title_row: 9
|
||||
title: "礼貌要饭地址"
|
||||
crypto_wallets:
|
||||
tron:
|
||||
row: 10
|
||||
address: "TQtBXCSTwLFHjBqTS4rNUp7ufiGx51BRey"
|
||||
solana:
|
||||
row: 11
|
||||
address: "HjYhozVf9AQmfv7yv79xSNs6uaEU5oUk2USasYQfUYau"
|
||||
ethereum:
|
||||
row: 12
|
||||
address: "0xa396923a71ee7D9480b346a17dDeEb2c0C287BBC"
|
||||
bsc:
|
||||
row: 13
|
||||
address: "0xa396923a71ee7D9480b346a17dDeEb2c0C287BBC"
|
||||
bitcoin:
|
||||
row: 14
|
||||
address: "bc1plslluj3zq3snpnnczplu7ywf37h89dyudqua04pz4txwh8z5z5vsre7nlm"
|
||||
sui:
|
||||
row: 15
|
||||
address: "0xb720c98a48c77f2d49d375932b2867e793029e6337f1562522640e4f84203d2e"
|
||||
|
||||
misc:
|
||||
warning:
|
||||
row: 17
|
||||
content: "广告位(注意识别风险)"
|
||||
|
||||
# 数据验证规则
|
||||
validation:
|
||||
prompt_rows: [0, 1, 3]
|
||||
tool_rows: [5]
|
||||
social_rows: [7]
|
||||
crypto_rows: [10, 11, 12, 13, 14, 15]
|
||||
warning_rows: [17]
|
||||
@@ -0,0 +1,549 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
convert_local.py
|
||||
|
||||
Reads a local Excel file and converts its contents into a structured prompt library
|
||||
under `prompt-library/` per the development guide. It generates:
|
||||
- prompts/<category>/ (one file per non-empty cell across columns for each prompt row)
|
||||
- prompts/index.json (summary + traceability)
|
||||
- prompts/<category>/index.md (table + version matrix)
|
||||
- docs/tools.md, docs/support.md, docs/excel-data.md
|
||||
- README.md (top-level for prompt-library)
|
||||
|
||||
Usage:
|
||||
python prompt-library/scripts/convert_local.py \
|
||||
[--excel "/absolute/or/relative/path/to/prompt (2).xlsx"] \
|
||||
[--config prompt-library/scripts/config.yaml] \
|
||||
[--category-name prompt-category]
|
||||
|
||||
If no arguments are provided, it will:
|
||||
- load config from prompt-library/scripts/config.yaml (if present)
|
||||
- resolve Excel path from config.source.excel_file relative to project root
|
||||
- default category to "prompt-category"
|
||||
|
||||
Dependencies: pandas, openpyxl, PyYAML
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
except Exception: # pragma: no cover
|
||||
yaml = None # Optional; script still works without YAML if no config provided
|
||||
|
||||
|
||||
@dataclass
|
||||
class RowClassification:
|
||||
row_index: int # zero-based excel index
|
||||
kind: str # prompt|tool|social|wallet_header|wallet|warning|other
|
||||
data: Dict
|
||||
|
||||
|
||||
class ExcelPromptConverter:
|
||||
def __init__(
|
||||
self,
|
||||
project_root: Path,
|
||||
prompt_library_dir: Path,
|
||||
excel_path: Path,
|
||||
category_name: str = "prompt-category",
|
||||
config_path: Optional[Path] = None,
|
||||
output_root: Optional[Path] = None,
|
||||
) -> None:
|
||||
self.project_root = project_root
|
||||
self.prompt_library_dir = prompt_library_dir
|
||||
# If an output_root is provided, write into that snapshot directory
|
||||
# rather than the in-repo prompts/docs locations.
|
||||
if output_root is not None:
|
||||
self.output_root = output_root
|
||||
self.prompts_dir = output_root / "prompts"
|
||||
self.docs_dir = output_root / "docs"
|
||||
self.readme_target_root = output_root
|
||||
else:
|
||||
self.output_root = None
|
||||
self.prompts_dir = prompt_library_dir / "prompts"
|
||||
self.docs_dir = prompt_library_dir / "docs"
|
||||
self.readme_target_root = prompt_library_dir
|
||||
self.scripts_dir = prompt_library_dir / "scripts"
|
||||
self.category_name = category_name # fallback if single sheet
|
||||
self.category_dir = self.prompts_dir / self.category_name
|
||||
self.excel_path = excel_path
|
||||
self.config_path = config_path
|
||||
self.config = self._load_config(config_path)
|
||||
self.now = datetime.now()
|
||||
|
||||
# Per-sheet prompts map: {sheet_name: {excel_row -> {title, versions{col->file}}}}
|
||||
self.prompts_info_by_sheet: Dict[str, Dict[int, Dict]] = {}
|
||||
self.tools: List[Dict] = []
|
||||
self.social: List[Dict] = []
|
||||
self.wallets: Dict[str, Dict] = {}
|
||||
self.misc: List[Dict] = []
|
||||
self.total_rows = 0
|
||||
self.total_cols = 0
|
||||
self.sheet_names_order: List[str] = []
|
||||
|
||||
def _load_config(self, config_path: Optional[Path]) -> Dict:
|
||||
if config_path and config_path.exists() and yaml is not None:
|
||||
with config_path.open("r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
return {}
|
||||
|
||||
def _sanitize_filename(self, text: str, max_length: int = 60) -> str:
|
||||
if not text:
|
||||
return "untitled"
|
||||
text = str(text).strip()
|
||||
text = re.sub(r"[\\/:*?\"<>|\r\n]+", "", text)
|
||||
text = text.replace(" ", "_")
|
||||
if len(text) > max_length:
|
||||
text = text[:max_length].rstrip("_-")
|
||||
return text or "untitled"
|
||||
|
||||
def _extract_title(self, contents: List[str]) -> str:
|
||||
for c in contents:
|
||||
if c and c.strip():
|
||||
first_line = c.strip().splitlines()[0]
|
||||
words = first_line.split()
|
||||
candidate = " ".join(words[:6])
|
||||
return self._sanitize_filename(candidate)
|
||||
return "untitled"
|
||||
|
||||
def _read_excel_sheets(self) -> Dict[str, pd.DataFrame]:
|
||||
# Read all sheets; if workbook has single sheet, still returns dict with one entry
|
||||
sheets: Dict[str, pd.DataFrame] = pd.read_excel(self.excel_path, header=None, engine="openpyxl", sheet_name=None) # type: ignore
|
||||
normalized: Dict[str, pd.DataFrame] = {}
|
||||
for sheet_name, df in sheets.items():
|
||||
try:
|
||||
df = df.map(lambda v: v.strip() if isinstance(v, str) else v) # pandas >=2.1
|
||||
except Exception:
|
||||
df = df.applymap(lambda v: v.strip() if isinstance(v, str) else v) # fallback
|
||||
normalized[sheet_name] = df
|
||||
# preserve order of sheets
|
||||
self.sheet_names_order = list(normalized.keys())
|
||||
# set global rows/cols to first sheet for summary; detailed per-sheet handled later
|
||||
if normalized:
|
||||
any_df = normalized[self.sheet_names_order[0]]
|
||||
self.total_rows, self.total_cols = any_df.shape
|
||||
return normalized
|
||||
|
||||
def _classify_rows(self, df: pd.DataFrame) -> List[RowClassification]:
|
||||
classifications: List[RowClassification] = []
|
||||
wallet_mode = False
|
||||
|
||||
for r in range(df.shape[0]):
|
||||
row_vals = [df.iloc[r, c] if c < df.shape[1] else None for c in range(df.shape[1])]
|
||||
non_empty = [v for v in row_vals if isinstance(v, str) and v.strip()]
|
||||
any_http = any(isinstance(v, str) and v.startswith("http") for v in row_vals)
|
||||
|
||||
if not non_empty:
|
||||
classifications.append(RowClassification(r, "other", {"empty": True}))
|
||||
continue
|
||||
|
||||
# Wallet header detection (e.g., contains "网络" and a label like "礼貌要饭地址")
|
||||
joined = " ".join([v for v in non_empty])
|
||||
if any(k in joined for k in ["网络", "网络名称"]) and any(
|
||||
k in joined for k in ["礼貌要饭地址", "钱包", "地址"]
|
||||
):
|
||||
wallet_mode = True
|
||||
classifications.append(RowClassification(r, "wallet_header", {"raw": row_vals}))
|
||||
continue
|
||||
|
||||
if wallet_mode:
|
||||
# If the row still looks like wallet data (two columns: network, address)
|
||||
first, second = row_vals[0] if len(row_vals) > 0 else None, row_vals[1] if len(row_vals) > 1 else None
|
||||
if (first and isinstance(first, str)) and (second and isinstance(second, str)):
|
||||
classifications.append(
|
||||
RowClassification(
|
||||
r,
|
||||
"wallet",
|
||||
{
|
||||
"network": first,
|
||||
"address": second,
|
||||
"raw": row_vals,
|
||||
},
|
||||
)
|
||||
)
|
||||
continue
|
||||
else:
|
||||
wallet_mode = False # end wallet section if pattern breaks
|
||||
|
||||
# Tools and social heuristics
|
||||
if any_http:
|
||||
url = next(v for v in row_vals if isinstance(v, str) and v.startswith("http"))
|
||||
desc = None
|
||||
for v in row_vals:
|
||||
if v and isinstance(v, str) and not v.startswith("http"):
|
||||
desc = v
|
||||
break
|
||||
kind = "social" if ("x.com" in url or "twitter.com" in url) else "tool"
|
||||
classifications.append(RowClassification(r, kind, {"url": url, "description": desc or "", "raw": row_vals}))
|
||||
continue
|
||||
|
||||
# Warnings or misc markers
|
||||
if any("广告位" in v for v in non_empty if isinstance(v, str)):
|
||||
classifications.append(RowClassification(r, "warning", {"content": joined, "raw": row_vals}))
|
||||
continue
|
||||
|
||||
# Placeholder rows to ignore as prompts
|
||||
if any(v in {"...", "….", "...."} for v in non_empty):
|
||||
classifications.append(RowClassification(r, "other", {"placeholder": True, "raw": row_vals}))
|
||||
continue
|
||||
|
||||
# Otherwise: treat as prompt row (one logical prompt per row with multiple versions across columns)
|
||||
prompt_versions: Dict[int, str] = {}
|
||||
for c in range(df.shape[1]):
|
||||
cell = df.iloc[r, c] if c < df.shape[1] else None
|
||||
if isinstance(cell, str) and cell.strip():
|
||||
prompt_versions[c + 1] = cell.strip()
|
||||
if prompt_versions:
|
||||
classifications.append(RowClassification(r, "prompt", {"versions": prompt_versions}))
|
||||
else:
|
||||
classifications.append(RowClassification(r, "other", {"raw": row_vals}))
|
||||
|
||||
return classifications
|
||||
|
||||
def _ensure_dirs(self) -> None:
|
||||
self.prompts_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.category_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.docs_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _write_prompt_file(self, row_num: int, col_num: int, title: str, content: str, versions_in_row: List[int]) -> str:
|
||||
"""Write a prompt file containing ONLY the prompt text, nothing else."""
|
||||
row_col = f"({row_num},{col_num})"
|
||||
filename = f"{row_col}_{title}.md"
|
||||
filepath = self.category_dir / filename
|
||||
# Ensure content ends with newline and contains no surrounding fences/headers added by us
|
||||
pure = (content or "").rstrip("\n") + "\n"
|
||||
filepath.write_text(pure, encoding="utf-8")
|
||||
return filename
|
||||
|
||||
def _generate_category_index(self, sheet_name: str, category_dir: Path, prompts_info: Dict[int, Dict]) -> None:
|
||||
index_path = category_dir / "index.md"
|
||||
total_prompts = len(prompts_info)
|
||||
total_versions = sum(len(meta["versions"]) for meta in prompts_info.values())
|
||||
avg_versions = total_versions / total_prompts if total_prompts else 0
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(f"# 📂 提示词分类 - {sheet_name}(基于Excel原始数据)\n")
|
||||
lines.append(f"最后同步: {self.now.strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
lines.append("\n## 📊 统计\n")
|
||||
lines.append(f"- 提示词总数: {total_prompts}\n")
|
||||
lines.append(f"- 版本总数: {total_versions} \n")
|
||||
lines.append(f"- 平均版本数: {avg_versions:.1f}\n\n")
|
||||
lines.append("## 📋 提示词列表\n")
|
||||
lines.append("\n| 序号 | 标题 | 版本数 | 查看 |\n|------|------|--------|------|\n")
|
||||
for row in sorted(prompts_info.keys()):
|
||||
info = prompts_info[row]
|
||||
title = info["title"]
|
||||
versions = info["versions"]
|
||||
links = " / ".join([f"[v{v}](./({row},{v})_{title}.md)" for v in sorted(versions.keys())])
|
||||
lines.append(f"| {row} | {title} | {len(versions)} | {links} |\n")
|
||||
|
||||
# Version matrix
|
||||
max_col = 0
|
||||
for info in prompts_info.values():
|
||||
if info["versions"]:
|
||||
max_col = max(max_col, max(info["versions"].keys()))
|
||||
lines.append("\n## 🗂️ 版本矩阵\n")
|
||||
header = ["行"] + [f"v{i}" for i in range(1, max_col + 1)] + ["备注"]
|
||||
lines.append("\n| " + " | ".join(header) + " |\n" + "|" + "---|" * len(header) + "\n")
|
||||
for row in sorted(prompts_info.keys()):
|
||||
info = prompts_info[row]
|
||||
row_cells = [str(row)]
|
||||
for c in range(1, max_col + 1):
|
||||
row_cells.append("✅" if c in info["versions"] else "—")
|
||||
row_cells.append("")
|
||||
lines.append("| " + " | ".join(row_cells) + " |\n")
|
||||
|
||||
index_path.write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
def _generate_prompts_index_json(self) -> None:
|
||||
index_json_path = self.prompts_dir / "index.json"
|
||||
total_prompts = sum(len(p) for p in self.prompts_info_by_sheet.values())
|
||||
total_versions = sum(sum(len(meta["versions"]) for meta in p.values()) for p in self.prompts_info_by_sheet.values())
|
||||
stats = {
|
||||
"sheets": len(self.prompts_info_by_sheet),
|
||||
"prompts": total_prompts,
|
||||
"versions": total_versions,
|
||||
"tools": len(self.tools) if self.tools else 0,
|
||||
"social_accounts": len(self.social) if self.social else 0,
|
||||
"crypto_wallets": len(self.wallets) if self.wallets else 0,
|
||||
}
|
||||
categories = []
|
||||
for sheet_name in self.sheet_names_order:
|
||||
prompts_info = self.prompts_info_by_sheet.get(sheet_name, {})
|
||||
categories.append(
|
||||
{
|
||||
"name": sheet_name,
|
||||
"prompt_count": len(prompts_info),
|
||||
"version_count": sum(len(meta["versions"]) for meta in prompts_info.values()),
|
||||
"prompts": [
|
||||
{
|
||||
"row": row,
|
||||
"title": info["title"],
|
||||
"versions": sorted(list(info["versions"].keys())),
|
||||
"files": [info["versions"][v] for v in sorted(info["versions"].keys())],
|
||||
}
|
||||
for row, info in sorted(prompts_info.items())
|
||||
],
|
||||
}
|
||||
)
|
||||
excel_data = {
|
||||
"total_rows": self.total_rows,
|
||||
"total_cols": self.total_cols,
|
||||
"sheets": list(self.prompts_info_by_sheet.keys()),
|
||||
}
|
||||
tools = {}
|
||||
if self.tools:
|
||||
for t in self.tools:
|
||||
name = t.get("name") or "tool"
|
||||
tools[name] = {k: v for k, v in t.items() if k != "name"}
|
||||
social_media = {}
|
||||
if self.social:
|
||||
for s in self.social:
|
||||
name = s.get("name") or "social"
|
||||
social_media[name] = {k: v for k, v in s.items() if k != "name"}
|
||||
support = {
|
||||
"description": "礼貌要饭地址",
|
||||
"crypto_wallets": self.wallets,
|
||||
}
|
||||
data = {
|
||||
"last_updated": self.now.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"source": self.excel_path.name,
|
||||
"stats": stats,
|
||||
"categories": categories,
|
||||
"excel_data": excel_data,
|
||||
"tools": tools,
|
||||
"social_media": social_media,
|
||||
"support": support,
|
||||
"misc": self.misc,
|
||||
}
|
||||
index_json_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
def _generate_docs(self, sheets: Dict[str, pd.DataFrame]) -> None:
|
||||
# docs/excel-data.md (full table)
|
||||
excel_doc_path = self.docs_dir / "excel-data.md"
|
||||
lines: List[str] = []
|
||||
lines.append("# 📊 Excel原始数据完整记录\n")
|
||||
lines.append("## 数据来源\n")
|
||||
lines.append(f"- **文件**: {self.excel_path.name}\n")
|
||||
lines.append(f"- **处理时间**: {self.now.strftime('%Y-%m-%d')}\n")
|
||||
lines.append(f"- **工作表数量**: {len(sheets)}\n\n")
|
||||
for sheet_name, df in sheets.items():
|
||||
rows, cols = df.shape
|
||||
lines.append(f"## 工作表: {sheet_name} ({rows}行×{cols}列)\n")
|
||||
lines.append("\n| 行号 | 列1 | 列2 | 列3 |\n|-----:|-----|-----|-----|\n")
|
||||
for r in range(rows):
|
||||
c1 = df.iloc[r, 0] if cols > 0 else ""
|
||||
c2 = df.iloc[r, 1] if cols > 1 else ""
|
||||
c3 = df.iloc[r, 2] if cols > 2 else ""
|
||||
def fmt(x) -> str:
|
||||
try:
|
||||
if x is None or (isinstance(x, float) and pd.isna(x)) or (hasattr(pd, 'isna') and pd.isna(x)):
|
||||
return ""
|
||||
except Exception:
|
||||
pass
|
||||
s = str(x)
|
||||
return s.replace("|", "\\|")
|
||||
lines.append(f"| {r} | {fmt(c1)} | {fmt(c2)} | {fmt(c3)} |\n")
|
||||
lines.append("\n")
|
||||
lines.append("\n---\n*完整数据提取自 {0}*\n".format(self.excel_path.name))
|
||||
excel_doc_path.write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
# docs/tools.md
|
||||
tools_path = self.docs_dir / "tools.md"
|
||||
t_lines: List[str] = []
|
||||
t_lines.append("# 🛠️ 工具与资源(从Excel提取)\n")
|
||||
if self.tools:
|
||||
t_lines.append("\n## AI优化工具\n")
|
||||
for t in self.tools:
|
||||
t_lines.append("\n### {0}\n- **URL**: {1}\n- **描述**: {2}\n- **数据来源**: Excel表格第{3}行\n".format(
|
||||
t.get("name") or "工具",
|
||||
t.get("url", ""),
|
||||
t.get("description", ""),
|
||||
(t.get("excel_row") or 0) + 1,
|
||||
))
|
||||
if self.social:
|
||||
t_lines.append("\n## 社交媒体\n")
|
||||
for s in self.social:
|
||||
t_lines.append("\n### {0}\n- **URL**: {1}\n- **描述**: {2}\n- **数据来源**: Excel表格第{3}行\n".format(
|
||||
s.get("name") or "社交账号",
|
||||
s.get("url", ""),
|
||||
s.get("description", ""),
|
||||
(s.get("excel_row") or 0) + 1,
|
||||
))
|
||||
t_lines.append("\n## 使用建议\n\n1. **OpenAI优化器**: 可以用来测试和改进本库中的提示词\n2. **社交媒体**: 关注获取项目更新和使用技巧\n3. **集成方式**: 可以将这些工具集成到自动化工作流中\n\n---\n*数据来源: {0}*\n".format(self.excel_path.name))
|
||||
tools_path.write_text("\n".join(t_lines), encoding="utf-8")
|
||||
|
||||
# docs/support.md
|
||||
support_path = self.docs_dir / "support.md"
|
||||
s_lines: List[str] = []
|
||||
s_lines.append("# 💰 项目支持(从Excel提取)\n")
|
||||
s_lines.append("\n## 支持说明\n**礼貌要饭地址** - 如果这个项目对您有帮助,欢迎通过以下方式支持\n")
|
||||
if self.wallets:
|
||||
s_lines.append("\n## 加密货币钱包地址\n\n### 主流网络支持\n")
|
||||
s_lines.append("\n| 网络名称 | 钱包地址 | Excel行号 |\n|----------|----------|-----------|\n")
|
||||
for net, data in self.wallets.items():
|
||||
s_lines.append("| **{0}** | `{1}` | 第{2}行 |\n".format(net.upper(), data.get("address", ""), (data.get("excel_row") or 0) + 1))
|
||||
if self.misc:
|
||||
for m in self.misc:
|
||||
if m.get("type") == "warning" or "广告位" in m.get("content", ""):
|
||||
s_lines.append("\n⚠️ **重要提醒**: {0}\n".format(m.get("content")))
|
||||
s_lines.append("\n### 使用建议\n1. 请确认钱包地址的准确性\n2. 建议小额测试后再进行大额转账\n3. 不同网络的转账费用不同,请选择合适的网络\n\n---\n*钱包地址来源: {0}*\n".format(self.excel_path.name))
|
||||
support_path.write_text("\n".join(s_lines), encoding="utf-8")
|
||||
|
||||
def _generate_readme(self) -> None:
|
||||
readme_path = self.readme_target_root / "README.md"
|
||||
total_prompts = sum(len(p) for p in self.prompts_info_by_sheet.values())
|
||||
total_versions = sum(sum(len(meta["versions"]) for meta in p.values()) for p in self.prompts_info_by_sheet.values())
|
||||
readme = []
|
||||
readme.append("# 📚 提示词库(Excel转换版)\n")
|
||||
readme.append("")
|
||||
readme.append(f"")
|
||||
readme.append(f"")
|
||||
readme.append(f"\n")
|
||||
readme.append(f"最后更新: {self.now.strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
readme.append("\n## 📊 总览\n")
|
||||
readme.append(f"- **数据来源**: {self.excel_path.name}\n")
|
||||
readme.append(f"- **分类数量**: {len(self.prompts_info_by_sheet)} \n- **提示词总数**: {total_prompts}\n- **版本总数**: {total_versions}\n")
|
||||
readme.append("\n## 📂 分类导航\n")
|
||||
for i, sheet_name in enumerate(self.sheet_names_order, start=1):
|
||||
prompts_info = self.prompts_info_by_sheet.get(sheet_name, {})
|
||||
folder = f"({i})_{self._sanitize_filename(sheet_name)}"
|
||||
ver_count = sum(len(meta["versions"]) for meta in prompts_info.values())
|
||||
readme.append(f"- [{sheet_name}](./prompts/{folder}/) - {len(prompts_info)} 个提示词, {ver_count} 个版本\n")
|
||||
readme.append("\n## 🔄 同步信息\n")
|
||||
readme.append(f"- **数据源**: {self.excel_path.name}\n- **处理时间**: {self.now.strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
readme.append("\n## 📝 许可证\n本项目采用 MIT 许可证\n")
|
||||
readme.append("\n---\n*完全基于 Excel 表格自动生成*\n")
|
||||
readme_path.write_text("\n".join(readme), encoding="utf-8")
|
||||
|
||||
def convert(self) -> None:
|
||||
self._ensure_dirs()
|
||||
sheets = self._read_excel_sheets()
|
||||
# If no sheets returned (shouldn't happen), fallback to empty
|
||||
for idx, sheet_name in enumerate(self.sheet_names_order, start=1):
|
||||
df = sheets[sheet_name]
|
||||
# Prepare per-sheet folder
|
||||
folder_name = f"({idx})_{self._sanitize_filename(sheet_name)}"
|
||||
category_dir = self.prompts_dir / folder_name
|
||||
category_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Classify rows
|
||||
rows = self._classify_rows(df)
|
||||
prompts_info: Dict[int, Dict] = {}
|
||||
|
||||
# Build prompt files for this sheet
|
||||
for rc in rows:
|
||||
if rc.kind == "prompt":
|
||||
excel_row_number = rc.row_index + 1
|
||||
versions: Dict[int, str] = rc.data["versions"]
|
||||
title = self._extract_title(list(versions.values()))
|
||||
prompts_info[excel_row_number] = {"title": title, "versions": {}}
|
||||
# Rewrite files directly into category_dir
|
||||
for col_num, content in versions.items():
|
||||
row_col = f"({excel_row_number},{col_num})"
|
||||
filename = f"{row_col}_{title}.md"
|
||||
(category_dir / filename).write_text((content or "").rstrip("\n") + "\n", encoding="utf-8")
|
||||
prompts_info[excel_row_number]["versions"][col_num] = filename
|
||||
elif rc.kind == "tool":
|
||||
url = rc.data.get("url", "")
|
||||
self.tools.append({
|
||||
"name": "OpenAI 提示词优化平台" if "openai" in url else "工具",
|
||||
"url": url,
|
||||
"description": rc.data.get("description", ""),
|
||||
"excel_row": rc.row_index,
|
||||
"sheet": sheet_name,
|
||||
})
|
||||
elif rc.kind == "social":
|
||||
url = rc.data.get("url", "")
|
||||
name = "Twitter/X 账号" if ("x.com" in url or "twitter.com" in url) else "社交账号"
|
||||
self.social.append({
|
||||
"name": name,
|
||||
"url": url,
|
||||
"description": rc.data.get("description", ""),
|
||||
"excel_row": rc.row_index,
|
||||
"sheet": sheet_name,
|
||||
})
|
||||
elif rc.kind == "wallet":
|
||||
network = str(rc.data.get("network", "")).strip()
|
||||
address = str(rc.data.get("address", "")).strip()
|
||||
if network and address:
|
||||
self.wallets[network.lower()] = {
|
||||
"address": address,
|
||||
"excel_row": rc.row_index,
|
||||
"sheet": sheet_name,
|
||||
}
|
||||
elif rc.kind == "warning":
|
||||
self.misc.append({"type": "warning", "excel_row": rc.row_index, "content": rc.data.get("content", ""), "sheet": sheet_name})
|
||||
|
||||
# Save per-sheet prompts map and index
|
||||
self.prompts_info_by_sheet[sheet_name] = prompts_info
|
||||
self._generate_category_index(sheet_name, category_dir, prompts_info)
|
||||
|
||||
# Global indices and docs
|
||||
self._generate_prompts_index_json()
|
||||
self._generate_docs(sheets)
|
||||
self._generate_readme()
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Convert local Excel into prompt library structure")
|
||||
parser.add_argument("--excel", type=str, default=None, help="Path to the Excel file (default from config)")
|
||||
parser.add_argument("--config", type=str, default=None, help="Path to config.yaml (optional)")
|
||||
parser.add_argument("--category-name", type=str, default="prompt-category", help="Output category folder name")
|
||||
parser.add_argument("--out-dir", type=str, default=None, help="Optional snapshot output root. If set, writes to <out-dir>/prompts and <out-dir>/docs")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
script_path = Path(__file__).resolve()
|
||||
prompt_library_dir = script_path.parent.parent
|
||||
project_root = prompt_library_dir.parent
|
||||
|
||||
config_path = Path(args.config).resolve() if args.config else (prompt_library_dir / "scripts" / "config.yaml")
|
||||
|
||||
# Resolve Excel path
|
||||
if args.excel:
|
||||
excel_path = Path(args.excel)
|
||||
if not excel_path.is_absolute():
|
||||
excel_path = (project_root / excel_path).resolve()
|
||||
else:
|
||||
# Try config
|
||||
cfg_excel = None
|
||||
if config_path.exists() and yaml is not None:
|
||||
with config_path.open("r", encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
cfg_excel = ((cfg.get("source") or {}).get("excel_file") or None)
|
||||
excel_path = (project_root / cfg_excel).resolve() if cfg_excel else (project_root / "prompt (2).xlsx").resolve()
|
||||
|
||||
if not excel_path.exists():
|
||||
raise FileNotFoundError(f"Excel file not found: {excel_path}")
|
||||
|
||||
out_dir = Path(args.out_dir).resolve() if args.out_dir else None
|
||||
|
||||
converter = ExcelPromptConverter(
|
||||
project_root=project_root,
|
||||
prompt_library_dir=prompt_library_dir,
|
||||
excel_path=excel_path,
|
||||
category_name=args.category_name,
|
||||
config_path=config_path if config_path.exists() else None,
|
||||
output_root=out_dir,
|
||||
)
|
||||
converter.convert()
|
||||
target = out_dir if out_dir else prompt_library_dir
|
||||
print(f"✅ Conversion complete. Output under: {target}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
docs_to_excel.py
|
||||
|
||||
Documents → Excel converter: rebuild a workbook from prompts folders.
|
||||
|
||||
Rules (per STRUCTURE_AND_CONVERSION_SPEC.md):
|
||||
- Each folder under prompt-library/prompts that matches "(N)_<name>" or any folder is a sheet
|
||||
- For each file matching "(r,c)_*.md", write its full text to Excel cell (r,c), 1-based
|
||||
- Title part in filename is ignored for cell value
|
||||
- Non-matching files are ignored
|
||||
- Optionally clears existing workbook or merges (default: overwrite generate new)
|
||||
|
||||
Usage:
|
||||
python prompt-library/scripts/docs_to_excel.py --out "rebuilt.xlsx"
|
||||
# optional: --prompts-dir prompt-library/prompts --clear
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple
|
||||
|
||||
import pandas as pd
|
||||
from openpyxl import Workbook
|
||||
|
||||
FOLDER_PREFIX_RE = re.compile(r"^\((\d+)\)_")
|
||||
FILE_NAME_RE = re.compile(r"^\((\d+),(\d+)\)_.*\.md$")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(description="Rebuild Excel workbook from prompt folders")
|
||||
p.add_argument("--prompts-dir", type=str, default="prompt-library/prompts", help="Prompts root directory")
|
||||
p.add_argument("--out", type=str, required=True, help="Output Excel file path")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def list_sheet_folders(prompts_root: Path) -> Dict[str, Path]:
|
||||
sheets: Dict[str, Path] = {}
|
||||
for child in sorted(prompts_root.iterdir()):
|
||||
if not child.is_dir():
|
||||
continue
|
||||
if child.name == "prompt-category":
|
||||
# legacy; skip auto-generated category
|
||||
continue
|
||||
sheets[child.name] = child
|
||||
return sheets
|
||||
|
||||
|
||||
def extract_rc(name: str) -> Tuple[int, int] | None:
|
||||
m = FILE_NAME_RE.match(name)
|
||||
if not m:
|
||||
return None
|
||||
r = int(m.group(1))
|
||||
c = int(m.group(2))
|
||||
return r, c
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
prompts_root = Path(args.prompts_dir).resolve()
|
||||
out_path = Path(args.out).resolve()
|
||||
|
||||
if not prompts_root.exists():
|
||||
raise FileNotFoundError(f"Prompts directory not found: {prompts_root}")
|
||||
|
||||
sheet_folders = list_sheet_folders(prompts_root)
|
||||
if not sheet_folders:
|
||||
raise RuntimeError("No sheet folders found under prompts root")
|
||||
|
||||
wb = Workbook()
|
||||
# remove default sheet
|
||||
default = wb.active
|
||||
wb.remove(default)
|
||||
|
||||
for folder_name, folder_path in sheet_folders.items():
|
||||
# Recover original sheet name (try to drop ordering prefix "(N)_")
|
||||
m = FOLDER_PREFIX_RE.match(folder_name)
|
||||
sheet_name = folder_name[m.end():] if m else folder_name
|
||||
if not sheet_name:
|
||||
sheet_name = folder_name
|
||||
ws = wb.create_sheet(title=sheet_name)
|
||||
|
||||
# Aggregate cells
|
||||
max_row = 0
|
||||
max_col = 0
|
||||
cells: Dict[Tuple[int, int], str] = {}
|
||||
for file in folder_path.iterdir():
|
||||
if not file.is_file() or not file.name.endswith('.md'):
|
||||
continue
|
||||
rc = extract_rc(file.name)
|
||||
if not rc:
|
||||
continue
|
||||
r, c = rc
|
||||
text = file.read_text(encoding='utf-8')
|
||||
# Trim a single trailing newline for cell value aesthetics
|
||||
if text.endswith("\n"):
|
||||
text = text[:-1]
|
||||
cells[(r, c)] = text
|
||||
if r > max_row:
|
||||
max_row = r
|
||||
if c > max_col:
|
||||
max_col = c
|
||||
|
||||
# Write into sheet
|
||||
for (r, c), val in cells.items():
|
||||
ws.cell(row=r, column=c, value=val)
|
||||
|
||||
# Save workbook
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
wb.save(str(out_path))
|
||||
print(f"✅ Rebuilt Excel saved to: {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
excel_to_docs.py
|
||||
|
||||
Thin wrapper that invokes the Excel → Documents converter implemented
|
||||
in convert_local.py, keeping a clearer entrypoint name.
|
||||
|
||||
Usage:
|
||||
python prompt-library/scripts/excel_to_docs.py --excel "prompt (2).xlsx"
|
||||
# optional:
|
||||
# --category-name <fallback> --config prompt-library/scripts/config.yaml
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
script = Path(__file__).resolve().parent / "convert_local.py"
|
||||
spec = importlib.util.spec_from_file_location("convert_local", str(script))
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("Unable to load convert_local.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["convert_local"] = module
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
# Delegate to its CLI
|
||||
module.main() # type: ignore
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
import shutil
|
||||
from collections import defaultdict
|
||||
|
||||
input_file = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
output_file = "prompt_jsonl/prompt_docs_refactored_clean.jsonl"
|
||||
backup_file = "prompt_jsonl/prompt_docs_refactored.jsonl.bak"
|
||||
|
||||
def filter_versions():
|
||||
# 1. Backup
|
||||
shutil.copy(input_file, backup_file)
|
||||
print(f"Backup created: {backup_file}")
|
||||
|
||||
# 2. Group by (category, row) and find max col
|
||||
latest_versions = {} # Key: (category, row), Value: item_dict
|
||||
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if not line.strip(): continue
|
||||
item = json.loads(line)
|
||||
|
||||
cat = item.get('category', 'Uncategorized')
|
||||
row = item.get('row', 0)
|
||||
col = item.get('col', 0)
|
||||
|
||||
key = (cat, row)
|
||||
|
||||
if key not in latest_versions:
|
||||
latest_versions[key] = item
|
||||
else:
|
||||
# If current item has higher col, replace it
|
||||
if col > latest_versions[key].get('col', 0):
|
||||
latest_versions[key] = item
|
||||
|
||||
# 3. Write filtered data
|
||||
count = 0
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
# Sort by category then row for tidiness
|
||||
sorted_keys = sorted(latest_versions.keys(), key=lambda x: (x[0], x[1]))
|
||||
for key in sorted_keys:
|
||||
item = latest_versions[key]
|
||||
f.write(json.dumps(item, ensure_ascii=False) + '\n')
|
||||
count += 1
|
||||
|
||||
print(f"Filtered file written: {output_file}")
|
||||
print(f"Total prompts retained: {count}")
|
||||
|
||||
# Overwrite original for downstream scripts
|
||||
shutil.move(output_file, input_file)
|
||||
print(f"Overwritten original file: {input_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
filter_versions()
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
使用 Gemini CLI 按固定系统提示词,将指定目录下的 .md 提示词批量转换为 JSONL。
|
||||
|
||||
特点:
|
||||
- 内置系统提示词,与《Gemini 无头模式 JSONL 规范化指引》一致
|
||||
- 禁用工具调用 (--allowed-tools ''), 输出纯文本,每个文件生成一行 JSON
|
||||
- 默认输入目录为仓库根下的 `2/`,输出为 `2/prompts.jsonl`
|
||||
|
||||
用法示例:
|
||||
python3 gemini_jsonl_batch.py
|
||||
python3 gemini_jsonl_batch.py --input 2 --output 2/prompts.jsonl --model gemini-2.5-flash
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ==================== 固定系统提示词 ====================
|
||||
SYS_PROMPT = """{"category_id": 1, "category": "JSONL规范化", "row": 2, "col": 1, "title": "# JSONL 提示词转换器 - 系统提示词", "content": "# JSONL 提示词转换器 - 系统提示词\n\n你是一个专业 的提示词格式转换器。将用户提供的提示词内容转换为标准 JSONL 格式。\n\n## 输出格式\n\n```json\n{\\"title\\\": \\"<标题>\\", \\"content\\\": \\"<完整内容>\\"}\n```\n\n### 字段说明\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `title` | string | 提示词标题,取内容的第一行或前 50 字符 |\n| `content` | string | 完整的提示词内容 |\n\n## 转换规则\n\n1. **标题提取**:\n - 若内容以 `#` 开头,取第一个标题作为 title\n - 否则取前 50 字符(去除换行)\n2. **内容转义**:\n - 换行符 转为 `\\\\n`\n - 双引号转为 `\\\\\"`\n - 反斜杠转为 `\\\\\\\\`\n\n## 输出要求\n\n- 每行一个完整的 JSON 对象\n- 不要添加任何解释、注释或额外文字\n- 不要用 ```json 代码块包裹\n- 直接输出纯 JSONL 内容\n\n## 示例\n\n### 输入\n```\n# Role:智能文档助手\n\n## Background\n用户需要一个能够处理文档的 AI 助手。\n\n## Skills\n- 文档解析\n- 格式转换\n```\n\n### 输出\n```\n{\\"title\\\": \\"# Role:智能文档助手\\", \\"content\\\": \\"# Role:智能文档助手\\\\n\\\\n## Background\\\\n用户需要一个能够处 理文档的 AI 助手。\\\\n\\\\n## Skills\\\\n- 文档解析\\\\n- 格式转换\\"}\n```\n\n---\n\n现在,请将用户提供的内容转换为标准 JSONL 格式。"}"""
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="使用 Gemini CLI 批量将 .md 提示词转换为 JSONL(固定系统提示词)。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
type=Path,
|
||||
default=Path("2"),
|
||||
help="输入目录,遍历其中的 .md 文件(默认:仓库根目录下的 2/)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="输出 JSONL 文件路径,默认写入 <input>/prompts.jsonl",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--model",
|
||||
default="gemini-2.5-flash",
|
||||
help="Gemini 模型名称(默认:gemini-2.5-flash)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gemini-cmd",
|
||||
default="gemini",
|
||||
help="Gemini CLI 可执行文件名或路径(默认:gemini)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="输出处理中的详细信息",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run_gemini(content: str, model: str, cmd: str) -> str:
|
||||
"""调用 Gemini CLI,将单个文本转换为一行 JSON。"""
|
||||
proc = subprocess.run(
|
||||
[
|
||||
cmd,
|
||||
"-m",
|
||||
model,
|
||||
"--output-format",
|
||||
"text",
|
||||
"--allowed-tools",
|
||||
"",
|
||||
SYS_PROMPT,
|
||||
],
|
||||
input=content.encode("utf-8"),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
)
|
||||
|
||||
stdout = proc.stdout.decode("utf-8", errors="replace").strip()
|
||||
stderr = proc.stderr.decode("utf-8", errors="replace").strip()
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"Gemini 调用失败 (code={proc.returncode}): {stderr or '无错误输出'}")
|
||||
|
||||
if stderr:
|
||||
# 某些 CLI 可能在 stderr 打印警告,保留但不中断
|
||||
print(f"⚠️ Gemini 警告: {stderr}", file=sys.stderr)
|
||||
|
||||
if not stdout:
|
||||
raise RuntimeError("Gemini 未返回内容")
|
||||
|
||||
# 去除多余行,只保留非空行并合并
|
||||
lines = [ln for ln in stdout.splitlines() if ln.strip()]
|
||||
return " ".join(lines).strip()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
input_dir = args.input.resolve()
|
||||
if not input_dir.exists() or not input_dir.is_dir():
|
||||
print(f"❌ 输入目录不存在: {input_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
output_path = args.output or (input_dir / "prompts.jsonl")
|
||||
output_path = output_path.resolve()
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
md_files = sorted(f for f in input_dir.iterdir() if f.suffix == ".md")
|
||||
if not md_files:
|
||||
print(f"⚠️ 未找到任何 .md 文件: {input_dir}")
|
||||
sys.exit(0)
|
||||
|
||||
results = []
|
||||
for md in md_files:
|
||||
content = md.read_text(encoding="utf-8")
|
||||
if args.verbose:
|
||||
print(f"→ 处理 {md.name}")
|
||||
try:
|
||||
json_line = run_gemini(content, args.model, args.gemini_cmd)
|
||||
results.append(json_line)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"❌ 处理失败 {md.name}: {exc}", file=sys.stderr)
|
||||
|
||||
with output_path.open("w", encoding="utf-8") as f:
|
||||
for line in results:
|
||||
f.write(line + "\n")
|
||||
|
||||
print(f"✅ 完成:{len(results)} 条 → {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
input_file = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
output_file = "prompt_excel/prompt_docs_refactored.xlsx"
|
||||
|
||||
def process():
|
||||
data_by_cat = {}
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if not line.strip(): continue
|
||||
item = json.loads(line)
|
||||
cat = item['category']
|
||||
if cat not in data_by_cat:
|
||||
data_by_cat[cat] = []
|
||||
|
||||
# Reconstruct the JSON string for the cell as it was in original Excel
|
||||
cell_data = {
|
||||
"title": item.get('title', ''),
|
||||
"content": item.get('content', '')
|
||||
}
|
||||
data_by_cat[cat].append(json.dumps(cell_data, ensure_ascii=False))
|
||||
|
||||
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
|
||||
# Sort categories to keep a consistent order
|
||||
sorted_cats = sorted(data_by_cat.keys())
|
||||
for cat in sorted_cats:
|
||||
items = data_by_cat[cat]
|
||||
# Each item in its own row, column 0
|
||||
df = pd.DataFrame(items)
|
||||
df.to_excel(writer, sheet_name=cat, index=False, header=False)
|
||||
|
||||
print(f"Excel created: {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
process()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将 prompt_docs 目录下的 md 文件转换为 JSONL 格式
|
||||
|
||||
用法:
|
||||
python md_to_jsonl.py <prompt_docs目录>
|
||||
python md_to_jsonl.py prompt_docs/prompt_docs_2025_1222_004537
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = REPO_ROOT / "prompt_jsonl"
|
||||
|
||||
def convert(docs_dir: Path):
|
||||
prompts_dir = docs_dir / "prompts"
|
||||
if not prompts_dir.exists():
|
||||
print(f"❌ 找不到 prompts 目录: {prompts_dir}")
|
||||
return
|
||||
|
||||
# 输出文件名基于输入目录名
|
||||
output_file = OUTPUT_DIR / f"{docs_dir.name}.jsonl"
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
records = []
|
||||
for category_dir in sorted(prompts_dir.iterdir()):
|
||||
if not category_dir.is_dir():
|
||||
continue
|
||||
|
||||
m = re.match(r'\((\d+)\)_(.+)', category_dir.name)
|
||||
cat_id, cat_name = (m.groups() if m else (0, category_dir.name))
|
||||
|
||||
for md_file in sorted(category_dir.glob("*.md")):
|
||||
if md_file.name == "index.md":
|
||||
continue
|
||||
|
||||
fm = re.match(r'\((\d+),(\d+)\)_(.+)\.md', md_file.name)
|
||||
if not fm:
|
||||
continue
|
||||
|
||||
row, col, title = fm.groups()
|
||||
content = md_file.read_text(encoding='utf-8')
|
||||
|
||||
records.append({
|
||||
"category_id": int(cat_id),
|
||||
"category": cat_name,
|
||||
"row": int(row),
|
||||
"col": int(col),
|
||||
"title": title[:80],
|
||||
"content": content
|
||||
})
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
for r in records:
|
||||
f.write(json.dumps(r, ensure_ascii=False) + '\n')
|
||||
|
||||
print(f"✅ 转换完成: {len(records)} 条 → {output_file}")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
docs_dir = Path(sys.argv[1])
|
||||
if not docs_dir.is_absolute():
|
||||
docs_dir = REPO_ROOT / docs_dir
|
||||
|
||||
convert(docs_dir)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,154 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
mapping = {
|
||||
# 编程技术
|
||||
"软件工程,glue_coding_用提示词": "编程技术",
|
||||
"前端复刻流程": "编程技术",
|
||||
"输入转单行JSON": "编程技术",
|
||||
"序列图生成": "编程技术",
|
||||
"流程图": "编程技术",
|
||||
"函数化万物": "编程技术",
|
||||
"编程知识库": "编程技术",
|
||||
"网页UI逆向分析提示词": "编程技术",
|
||||
"用户优化前端设计": "编程技术",
|
||||
"图像特征提取": "编程技术",
|
||||
"前端通用设计": "编程技术",
|
||||
|
||||
# 逻辑工具箱
|
||||
"哲学工具箱": "逻辑工具箱",
|
||||
"逻辑工具箱": "逻辑工具箱",
|
||||
"批判性思维分析": "逻辑工具箱",
|
||||
"思维模型": "逻辑工具箱",
|
||||
"政治批判工具箱": "逻辑工具箱",
|
||||
"未来视角": "逻辑工具箱",
|
||||
"层级结构分析": "逻辑工具箱",
|
||||
"问题分类识别": "逻辑工具箱",
|
||||
"分析": "逻辑工具箱",
|
||||
"终极本质分析": "逻辑工具箱",
|
||||
"事实核查": "逻辑工具箱",
|
||||
"关键词图谱": "逻辑工具箱",
|
||||
"语言分析元prompt": "逻辑工具箱",
|
||||
"逻辑分析": "逻辑工具箱",
|
||||
"黄金圈解释": "逻辑工具箱",
|
||||
"谋士": "逻辑工具箱",
|
||||
"经验": "逻辑工具箱",
|
||||
"道": "逻辑工具箱",
|
||||
"法": "逻辑工具箱",
|
||||
"术": "逻辑工具箱",
|
||||
"器": "逻辑工具箱",
|
||||
"心经口诀创作提示词": "逻辑工具箱",
|
||||
"临界知识": "逻辑工具箱",
|
||||
"项目分析": "逻辑工具箱",
|
||||
"对话提问": "逻辑工具箱",
|
||||
"思维导图": "逻辑工具箱",
|
||||
|
||||
# 内容创作
|
||||
"文案逆向": "内容创作",
|
||||
"x_prompt收集": "内容创作",
|
||||
"x提示词收集": "内容创作",
|
||||
"x爆款文案生成器": "内容创作",
|
||||
"推文制作提示词": "内容创作",
|
||||
"李继刚文选": "内容创作",
|
||||
"解释提示词": "内容创作",
|
||||
"一句话描述任何内容": "内容创作",
|
||||
"子弹总结": "内容创作",
|
||||
"文本转md语法电子书处理": "内容创作",
|
||||
"排版和图片,视频转文本": "内容创作",
|
||||
"艺术风格描述": "内容创作",
|
||||
"视频生成提示词": "内容创作",
|
||||
"图片逆向": "内容创作",
|
||||
"排版": "内容创作",
|
||||
"内容提炼": "内容创作",
|
||||
"简讯提示词": "内容创作",
|
||||
"艺术": "内容创作",
|
||||
"人话写作": "内容创作",
|
||||
"小红书": "内容创作",
|
||||
"组织语言": "内容创作",
|
||||
"正向人物生平报告官方文案": "内容创作",
|
||||
"gemini字幕处理": "内容创作",
|
||||
|
||||
# 学习教育
|
||||
"学习提示词": "学习教育",
|
||||
"学习用提示词": "学习教育",
|
||||
"ai学习用提示词": "学习教育",
|
||||
"书籍结构化分析": "学习教育",
|
||||
"典籍句子学习": "学习教育",
|
||||
"anki卡片格式输出": "学习教育",
|
||||
"notebookllm用提示词": "学习教育",
|
||||
"英文学习": "学习教育",
|
||||
"速成学习": "学习教育",
|
||||
"论文解读": "学习教育",
|
||||
"真传一句话": "学习教育",
|
||||
"学习音频": "学习教育",
|
||||
"豆包听书": "学习教育",
|
||||
"最小知识框架": "学习教育",
|
||||
|
||||
# 商业分析
|
||||
"grok商业金融分析提示词": "商业分析",
|
||||
"投资调研": "商业分析",
|
||||
"行业分析": "商业分析",
|
||||
"需求对齐": "商业分析",
|
||||
"需求结构化描述": "商业分析",
|
||||
"麦肯锡行业分析": "商业分析",
|
||||
"产品策略": "商业分析",
|
||||
"行业咨询": "商业分析",
|
||||
"需求解析": "商业分析",
|
||||
"SOP制作": "商业分析",
|
||||
|
||||
# 提示词工程
|
||||
"元提示词": "提示词工程",
|
||||
"提示词模块": "提示词工程",
|
||||
"根据内容逆向提示词": "提示词工程",
|
||||
"系统提示词": "提示词工程",
|
||||
"AI使用思维": "提示词工程",
|
||||
"使用ai的思维": "提示词工程",
|
||||
"最小字数系统提示词": "提示词工程",
|
||||
"ChatGPT": "提示词工程",
|
||||
"Reddit提示词": "提示词工程",
|
||||
"好prompt生成器": "提示词工程",
|
||||
"思维协议": "提示词工程",
|
||||
"grok抓取提示词": "提示词工程",
|
||||
|
||||
# 其他
|
||||
"AI_交易系统提示词": "综合杂项",
|
||||
"面向CZ": "综合杂项",
|
||||
}
|
||||
|
||||
id_map = {
|
||||
"编程技术": 1,
|
||||
"逻辑工具箱": 2,
|
||||
"内容创作": 3,
|
||||
"学习教育": 4,
|
||||
"商业分析": 5,
|
||||
"提示词工程": 6,
|
||||
"综合杂项": 7
|
||||
}
|
||||
|
||||
input_file = "prompt_jsonl/prompt_docs_2025_1222_004537.jsonl"
|
||||
output_file = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
|
||||
def process():
|
||||
stats = {}
|
||||
with open(input_file, 'r', encoding='utf-8') as fin, \
|
||||
open(output_file, 'w', encoding='utf-8') as fout:
|
||||
for line in fin:
|
||||
if not line.strip(): continue
|
||||
data = json.loads(line)
|
||||
old_cat = data.get('category', '')
|
||||
new_cat = mapping.get(old_cat, "综合杂项")
|
||||
|
||||
# Keep original category in tags if it doesn't exist?
|
||||
# Or just replace. The user said "只调整 'category'"
|
||||
data['category'] = new_cat
|
||||
data['category_id'] = id_map.get(new_cat, 7)
|
||||
|
||||
fout.write(json.dumps(data, ensure_ascii=False) + '\n')
|
||||
stats[new_cat] = stats.get(new_cat, 0) + 1
|
||||
|
||||
print("Refactor complete.")
|
||||
for cat, count in stats.items():
|
||||
print(f"{cat}: {count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
process()
|
||||
@@ -0,0 +1,49 @@
|
||||
import json
|
||||
import shutil
|
||||
from collections import defaultdict
|
||||
|
||||
input_file = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
output_file = "prompt_jsonl/prompt_docs_refactored_reindexed.jsonl"
|
||||
backup_file = "prompt_jsonl/prompt_docs_refactored_before_reindex.jsonl.bak"
|
||||
|
||||
def reindex_rows():
|
||||
# 1. Backup
|
||||
shutil.copy(input_file, backup_file)
|
||||
print(f"Backup created: {backup_file}")
|
||||
|
||||
# 2. Load and Group
|
||||
items_by_cat = defaultdict(list)
|
||||
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if not line.strip(): continue
|
||||
item = json.loads(line)
|
||||
cat = item.get('category', 'Uncategorized')
|
||||
items_by_cat[cat].append(item)
|
||||
|
||||
# 3. Sort and Reindex
|
||||
total_items = 0
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
# Sort categories for consistent file order
|
||||
for cat in sorted(items_by_cat.keys()):
|
||||
items = items_by_cat[cat]
|
||||
# Sort items by their OLD row to preserve relative order
|
||||
items.sort(key=lambda x: x.get('row', 0))
|
||||
|
||||
# Reassign row numbers starting from 1
|
||||
for i, item in enumerate(items):
|
||||
item['row'] = i + 1
|
||||
f.write(json.dumps(item, ensure_ascii=False) + '\n')
|
||||
total_items += 1
|
||||
|
||||
print(f"Category '{cat}': re-indexed {len(items)} items.")
|
||||
|
||||
print(f"Re-indexed file written: {output_file}")
|
||||
print(f"Total items: {total_items}")
|
||||
|
||||
# Overwrite original
|
||||
shutil.move(output_file, input_file)
|
||||
print(f"Overwritten original file: {input_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
reindex_rows()
|
||||
@@ -0,0 +1,11 @@
|
||||
# 提示词库管理系统依赖包
|
||||
pandas==2.1.4
|
||||
openpyxl==3.1.2
|
||||
google-auth==2.22.0
|
||||
google-auth-oauthlib==1.0.0
|
||||
google-auth-httplib2==0.1.0
|
||||
google-api-python-client==2.96.0
|
||||
PyYAML==6.0.1
|
||||
python-dotenv==1.0.0
|
||||
rich==13.7.1
|
||||
InquirerPy==0.3.4
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
start_convert.py
|
||||
|
||||
Launcher that orchestrates conversions between Excel workbooks and prompt documents
|
||||
using the following conventions:
|
||||
|
||||
Input locations (relative to repo root):
|
||||
- ./prompt_excel/ # place .xlsx files here for Excel → Docs
|
||||
- ./prompt_docs/ # place prompt folders here for Docs → Excel
|
||||
|
||||
Output locations (under repo root, named by source file/folder mtime):
|
||||
- ./prompt_docs_YYYYMMDD_HHMMSS/ # Excel → Docs results (copies of prompts/*)
|
||||
- ./prompt_excel_YYYYMMDD_HHMMSS/ # Docs → Excel results (rebuilt.xlsx)
|
||||
|
||||
Usage:
|
||||
# Auto mode: if there are .xlsx under prompt_excel, run Excel→Docs;
|
||||
# if there is a docs set under prompt_docs, run Docs→Excel.
|
||||
python prompt-library/scripts/start_convert.py
|
||||
|
||||
# Force a mode:
|
||||
python prompt-library/scripts/start_convert.py --mode excel2docs
|
||||
python prompt-library/scripts/start_convert.py --mode docs2excel
|
||||
|
||||
Notes:
|
||||
- No interactive prompts; behavior is driven by the file presence and CLI flags
|
||||
- Requires pandas, openpyxl, PyYAML (see scripts/requirements.txt)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def ts_from_path(p: Path) -> str:
|
||||
st = p.stat()
|
||||
# Prefer creation/birth time when available; fall back to mtime
|
||||
ts = getattr(st, "st_birthtime", None)
|
||||
if ts is None:
|
||||
# On Windows, st_ctime is creation; on Linux it's inode change time
|
||||
# We still prefer mtime for consistency if birthtime is unavailable.
|
||||
ts = st.st_mtime
|
||||
# Format: YYYY_MMDD_HHMMSS per requirement example 2025_0102_2309
|
||||
return datetime.fromtimestamp(ts).strftime("%Y_%m%d_%H%M%S")
|
||||
|
||||
|
||||
def load_module(py_path: Path, module_name: str):
|
||||
spec = importlib.util.spec_from_file_location(module_name, str(py_path))
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Unable to load module: {py_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
return module
|
||||
|
||||
|
||||
def run_excel_to_docs_for_file(excel_path: Path, prompt_library_dir: Path, out_root: Path) -> Path:
|
||||
convert_path = prompt_library_dir / "scripts" / "convert_local.py"
|
||||
mod = load_module(convert_path, "convert_local")
|
||||
|
||||
project_root = prompt_library_dir.parent
|
||||
# Prepare snapshot output directory under repo_root/prompt_docs/
|
||||
base_dir = out_root / "prompt_docs"
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_dir = base_dir / f"prompt_docs_{ts_from_path(excel_path)}"
|
||||
if out_dir.exists():
|
||||
shutil.rmtree(out_dir)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
converter = mod.ExcelPromptConverter(
|
||||
project_root=project_root,
|
||||
prompt_library_dir=prompt_library_dir,
|
||||
excel_path=excel_path,
|
||||
category_name="prompt-category",
|
||||
config_path=None,
|
||||
output_root=out_dir,
|
||||
)
|
||||
converter.convert()
|
||||
|
||||
return out_dir
|
||||
|
||||
|
||||
def run_docs_to_excel_for_dir(prompts_dir: Path, scripts_dir: Path, out_root: Path) -> Path:
|
||||
docs2excel_path = scripts_dir / "docs_to_excel.py"
|
||||
mod = load_module(docs2excel_path, "docs_to_excel")
|
||||
|
||||
# Determine timestamp from folder creation (or mtime fallback)
|
||||
base_dir = out_root / "prompt_excel"
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
ts_fmt = ts_from_path(prompts_dir)
|
||||
out_dir = base_dir / f"prompt_excel_{ts_fmt}"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_path = out_dir / "rebuilt.xlsx"
|
||||
|
||||
# Resolve actual prompts root (support either the prompts/ subfolder or direct sheet folders)
|
||||
prompts_root = prompts_dir / "prompts" if (prompts_dir / "prompts").exists() else prompts_dir
|
||||
# Invoke module's main via argparse emulation
|
||||
sys.argv = [str(docs2excel_path), "--prompts-dir", str(prompts_root), "--out", str(out_path)]
|
||||
mod.main() # type: ignore
|
||||
|
||||
return out_dir
|
||||
|
||||
|
||||
def find_xlsx_files(input_excel_dir: Path) -> List[Path]:
|
||||
if not input_excel_dir.exists():
|
||||
return []
|
||||
return sorted([p for p in input_excel_dir.iterdir() if p.is_file() and p.suffix.lower() in {".xlsx"}], key=lambda p: p.stat().st_mtime)
|
||||
|
||||
|
||||
def has_prompt_files(input_docs_dir: Path) -> bool:
|
||||
if not input_docs_dir.exists():
|
||||
return False
|
||||
for p in input_docs_dir.rglob("*.md"):
|
||||
if p.name.startswith("(") and ")_" in p.name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Start conversion between Excel and prompt docs")
|
||||
parser.add_argument("--mode", choices=["auto", "excel2docs", "docs2excel"], default="auto")
|
||||
parser.add_argument("--excel-dir", default="prompt_excel", help="Input directory containing .xlsx files")
|
||||
parser.add_argument("--docs-dir", default="prompt_docs", help="Input directory containing prompt folders")
|
||||
parser.add_argument("--select", type=str, default=None, help="Optional path to a specific Excel file or prompts folder to convert")
|
||||
args = parser.parse_args()
|
||||
|
||||
script_path = Path(__file__).resolve()
|
||||
prompt_library_dir = script_path.parent.parent # repo root (prompt-library)
|
||||
project_root = prompt_library_dir # use prompt-library as root for I/O
|
||||
|
||||
input_excel_dir = (prompt_library_dir / args.excel_dir).resolve()
|
||||
input_docs_dir = (prompt_library_dir / args.docs_dir).resolve()
|
||||
|
||||
ran_any = False
|
||||
|
||||
if args.mode in ("auto", "excel2docs"):
|
||||
# If user explicitly selected a file, prefer it
|
||||
if args.select:
|
||||
sel = Path(args.select)
|
||||
if not sel.is_absolute():
|
||||
sel = (project_root / sel).resolve()
|
||||
if sel.is_file() and sel.suffix.lower() == ".xlsx":
|
||||
out_dir = run_excel_to_docs_for_file(sel, prompt_library_dir, project_root)
|
||||
rel = out_dir.relative_to(prompt_library_dir)
|
||||
print(f"✅ Excel→Docs OK: {sel.name} → {rel}")
|
||||
ran_any = True
|
||||
else:
|
||||
xlsx_files = find_xlsx_files(input_excel_dir)
|
||||
for xlsx in xlsx_files:
|
||||
out_dir = run_excel_to_docs_for_file(xlsx, prompt_library_dir, project_root)
|
||||
rel = out_dir.relative_to(prompt_library_dir)
|
||||
print(f"✅ Excel→Docs OK: {xlsx.name} → {rel}")
|
||||
ran_any = True
|
||||
|
||||
if args.mode in ("auto", "docs2excel"):
|
||||
if args.select:
|
||||
sel = Path(args.select)
|
||||
if not sel.is_absolute():
|
||||
sel = (project_root / sel).resolve()
|
||||
if sel.exists() and sel.is_dir():
|
||||
out_dir = run_docs_to_excel_for_dir(sel, prompt_library_dir / "scripts", project_root)
|
||||
rel = out_dir.relative_to(prompt_library_dir)
|
||||
# show sel relative as well when possible
|
||||
try:
|
||||
sel_rel = Path(sel).relative_to(prompt_library_dir)
|
||||
except Exception:
|
||||
sel_rel = Path(sel)
|
||||
print(f"✅ Docs→Excel OK: {sel_rel} → {rel}")
|
||||
ran_any = True
|
||||
else:
|
||||
if has_prompt_files(input_docs_dir):
|
||||
out_dir = run_docs_to_excel_for_dir(input_docs_dir, prompt_library_dir / "scripts", project_root)
|
||||
rel = out_dir.relative_to(prompt_library_dir)
|
||||
print(f"✅ Docs→Excel OK: {args.docs_dir} → {rel}")
|
||||
ran_any = True
|
||||
|
||||
if not ran_any:
|
||||
print("ℹ️ Nothing to do. Place .xlsx under ./prompt_excel or prompt docs under ./prompt_docs, or use --mode to force.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
jsonl_path = "prompt_jsonl/prompt_docs_refactored.jsonl"
|
||||
docs_root = "prompt_docs/prompt_docs_refactored/prompts"
|
||||
|
||||
def verify():
|
||||
print("=== 开始全面完整性检查 ===\n")
|
||||
|
||||
# 1. JSONL 数据加载与基础检查
|
||||
if not os.path.exists(jsonl_path):
|
||||
print(f"❌ 错误: JSONL 文件不存在: {jsonl_path}")
|
||||
return
|
||||
|
||||
data = []
|
||||
with open(jsonl_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if line.strip():
|
||||
try:
|
||||
data.append(json.loads(line))
|
||||
except json.JSONDecodeError:
|
||||
print(f"❌ 错误: 发现无效的 JSON 行: {line[:50]}...")
|
||||
|
||||
total_items = len(data)
|
||||
print(f"✅ JSONL 读取成功,共 {total_items} 条数据。")
|
||||
|
||||
# 2. 规则验证
|
||||
errors = []
|
||||
categories = defaultdict(list)
|
||||
|
||||
expected_categories = {
|
||||
"内容创作", "商业分析", "学习教育", "提示词工程", "综合杂项", "编程技术", "逻辑工具箱"
|
||||
}
|
||||
|
||||
for item in data:
|
||||
cat = item.get('category')
|
||||
row = item.get('row')
|
||||
col = item.get('col')
|
||||
title = item.get('title')
|
||||
content = item.get('content')
|
||||
|
||||
# 收集分类数据用于后续分析
|
||||
categories[cat].append(row)
|
||||
|
||||
# 检查 1: 分类合法性
|
||||
if cat not in expected_categories:
|
||||
errors.append(f"❌ 未知分类: '{cat}' (Title: {title[:20]}...)")
|
||||
|
||||
# 检查 2: 列归位 (col == 1)
|
||||
if col != 1:
|
||||
errors.append(f"❌ 列未归位: Category '{cat}', Row {row}, Col {col} (应为 1)")
|
||||
|
||||
# 检查 3: 内容完整性 (简单检查)
|
||||
if not title:
|
||||
errors.append(f"⚠️ 警告: 标题为空 (Category '{cat}', Row {row})")
|
||||
if not content or len(content) < 5:
|
||||
errors.append(f"⚠️ 警告: 内容过短或为空 (Category '{cat}', Row {row}, Content len: {len(content) if content else 0})")
|
||||
|
||||
# 检查 4: 行连续性
|
||||
print("\n--- 分类与行号连续性检查 ---")
|
||||
for cat, rows in categories.items():
|
||||
rows.sort()
|
||||
count = len(rows)
|
||||
if count == 0:
|
||||
print(f"⚠️ 分类 '{cat}' 为空")
|
||||
continue
|
||||
|
||||
max_row = rows[-1]
|
||||
expected_rows = list(range(1, count + 1))
|
||||
|
||||
status = "✅ 正常"
|
||||
if rows != expected_rows:
|
||||
status = "❌ 异常 (行号不连续或重复)"
|
||||
errors.append(f"行号错误: {cat} (Expect 1-{count}, Got max {max_row})")
|
||||
|
||||
print(f"{cat.ljust(10)}: {count} 条 | Max Row: {max_row} | {status}")
|
||||
|
||||
# 3. 文件系统同步检查
|
||||
print("\n--- 文档文件同步检查 ---")
|
||||
files_found = 0
|
||||
if os.path.exists(docs_root):
|
||||
for root, dirs, files in os.walk(docs_root):
|
||||
for file in files:
|
||||
if file.endswith(".md") and not file.startswith("index"):
|
||||
files_found += 1
|
||||
else:
|
||||
print(f"❌ 文档目录不存在: {docs_root}")
|
||||
|
||||
print(f"JSONL 条目数: {total_items}")
|
||||
print(f"Markdown 文件数: {files_found}")
|
||||
|
||||
if total_items == files_found:
|
||||
print("✅ 文件数量一致")
|
||||
else:
|
||||
print(f"❌ 文件数量不匹配! (差值: {files_found - total_items})")
|
||||
errors.append("文件系统数量与 JSONL 不一致")
|
||||
|
||||
# 4. 总结
|
||||
print("\n=== 检查总结 ===")
|
||||
if not errors:
|
||||
print("🎉 完美!所有检查通过。数据结构完整、规范。")
|
||||
else:
|
||||
print(f"发现 {len(errors)} 个问题,请检视:")
|
||||
for err in errors:
|
||||
print(err)
|
||||
|
||||
if __name__ == "__main__":
|
||||
verify()
|
||||
Reference in New Issue
Block a user