chore: migrate repository to standard knowledge base layout

This commit is contained in:
tukuaiai
2026-05-02 03:29:06 +08:00
parent 40a721c24d
commit 628a3bc832
565 changed files with 687 additions and 711 deletions
@@ -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()
+87
View File
@@ -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("![同步状态](https://img.shields.io/badge/status-synced-green)")
readme.append(f"![提示词数量](https://img.shields.io/badge/prompts-{total_prompts}-blue)")
readme.append(f"![版本总数](https://img.shields.io/badge/versions-{total_versions}-orange)")
readme.append(f"![数据来源](https://img.shields.io/badge/source-Excel-yellow)\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()