refactor: Remove libs directory

This commit is contained in:
tukuaiai
2025-12-18 13:22:45 +08:00
parent 8487ece766
commit 21aa110e95
639 changed files with 1 additions and 110809 deletions
-87
View File
@@ -1,87 +0,0 @@
# 提示词库同步配置 - 基于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]
-549
View File
@@ -1,549 +0,0 @@
#!/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()
-118
View File
@@ -1,118 +0,0 @@
#!/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()
-33
View File
@@ -1,33 +0,0 @@
#!/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()
-11
View File
@@ -1,11 +0,0 @@
# 提示词库管理系统依赖包
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
-188
View File
@@ -1,188 +0,0 @@
#!/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()