""" Feishu 文档索引管理器 负责管理 memory/feishu-docs-index.md 文件 """ import os import re import json from datetime import datetime from typing import List, Dict, Optional, Tuple class IndexManager: """飞书文档索引管理器""" DEFAULT_INDEX_PATH = os.path.expanduser("~/.openclaw/workspace/memory/feishu-docs-index.md") def __init__(self, index_path: str = None): self.index_path = index_path or self.DEFAULT_INDEX_PATH self._ensure_index_exists() def _ensure_index_exists(self): """确保索引文件存在""" if not os.path.exists(self.index_path): os.makedirs(os.path.dirname(self.index_path), exist_ok=True) self._create_empty_index() def _create_empty_index(self): """创建空索引文件""" content = """# 飞书云文档索引 **用途:** 快速定位和管理所有飞书云文档 --- ## 📊 文档列表 | 序号 | 文档名 | 类型 | 链接 | 内容摘要 | 状态 | 最后更新 | 标签 | 所有者 | |------|--------|------|------|----------|------|----------|------|--------| --- ## 📂 按类型分类 ### 项目管理 ### 技术文档 ### 每日归档 ### 更新记录 ### 数据分析 --- ## 🔍 快速查找 *暂无关键词索引* --- ## 📝 使用说明 **添加新文档时:** 1. 复制表格中的一行 2. 填写所有字段 3. 更新分类索引 **查找文档时:** 1. 先搜索本文档中的关键词 2. 找到对应链接 3. 用 `feishu_doc` 工具读取内容 --- *创建时间:{date}* *最后更新:{date}* """.format(date=datetime.now().strftime("%Y-%m-%d")) with open(self.index_path, 'w', encoding='utf-8') as f: f.write(content) def add_or_update_doc(self, name: str, doc_type: str, url: str, token: str, summary: str = "", status: str = "已完成", tags: List[str] = None, owner: str = "") -> bool: """ 添加或更新文档到索引 Args: name: 文档名称 doc_type: 文档类型 (docx, sheet, bitable 等) url: 文档链接 token: 文档token summary: 内容摘要 status: 文档状态 tags: 标签列表 owner: 所有者 Returns: 是否成功 """ try: # 读取现有索引 with open(self.index_path, 'r', encoding='utf-8') as f: content = f.read() # 检查文档是否已存在(通过token判断) existing_index = self._find_doc_index(content, token) # 获取当前序号 if existing_index: doc_index = existing_index else: doc_index = self._get_next_index(content) # 准备标签字符串 tags_str = ", ".join(tags) if tags else "" # 准备表格行 now = datetime.now().strftime("%Y-%m-%d") new_row = f"| {doc_index} | {name} | {doc_type} | {url} | {summary} | {status} | {now} | {tags_str} | {owner} |" # 更新表格 if existing_index: # 更新现有行 content = self._replace_table_row(content, token, new_row) else: # 添加新行 content = self._insert_table_row(content, new_row) # 更新分类(如果新文档) if not existing_index and tags: content = self._update_categories(content, name, tags) # 更新关键词索引 if not existing_index: content = self._update_keywords(content, name, summary, tags) # 更新最后更新时间 content = self._update_last_modified(content) # 写回文件 with open(self.index_path, 'w', encoding='utf-8') as f: f.write(content) return True except Exception as e: print(f"更新索引失败: {e}") return False def search_docs(self, keyword: str, search_in: List[str] = None) -> List[Dict]: """ 搜索文档 Args: keyword: 搜索关键词 search_in: 在哪些字段中搜索 (name, summary, tags) Returns: 匹配的文档列表 """ if search_in is None: search_in = ["name", "summary", "tags"] results = [] docs = self._parse_table() keyword_lower = keyword.lower() for doc in docs: match = False if "name" in search_in and keyword_lower in doc.get("name", "").lower(): match = True if "summary" in search_in and keyword_lower in doc.get("summary", "").lower(): match = True if "tags" in search_in and keyword_lower in doc.get("tags", "").lower(): match = True if match: results.append(doc) return results def list_docs(self, tag: str = None, status: str = None, limit: int = None) -> List[Dict]: """ 列出文档 Args: tag: 按标签筛选 status: 按状态筛选 limit: 限制数量 Returns: 文档列表 """ docs = self._parse_table() # 筛选 if tag: docs = [d for d in docs if tag in d.get("tags", "")] if status: docs = [d for d in docs if d.get("status") == status] # 限制数量 if limit: docs = docs[:limit] return docs def get_doc_by_token(self, token: str) -> Optional[Dict]: """通过token获取文档信息""" docs = self._parse_table() for doc in docs: if token in doc.get("link", ""): return doc return None def _parse_table(self) -> List[Dict]: """解析索引表格""" docs = [] try: with open(self.index_path, 'r', encoding='utf-8') as f: content = f.read() # 找到表格部分 lines = content.split('\n') in_table = False for line in lines: if line.startswith('| 序号 '): in_table = True continue if in_table and line.startswith('|---'): continue if in_table and line.startswith('|') and not line.startswith('|------'): # 解析表格行 parts = [p.strip() for p in line.split('|')[1:-1]] if len(parts) >= 8: # 兼容8列表格格式(序号、文档名、类型、链接、摘要、状态、更新时间、备注) docs.append({ "index": parts[0], "name": parts[1], "type": parts[2], "link": parts[3], "summary": parts[4], "status": parts[5], "updated": parts[6], "tags": parts[7], # 使用"备注"列作为标签 "owner": "" # 所有者信息暂时为空 }) except Exception as e: print(f"解析索引失败: {e}") return docs def _find_doc_index(self, content: str, token: str) -> Optional[str]: """查找文档是否已存在,返回序号""" pattern = r'\| (\d+) \| [^|]+ \| [^|]+ \| [^/]+/docx/' + re.escape(token) + r'[^|]* \|' match = re.search(pattern, content) if match: return match.group(1) return None def _get_next_index(self, content: str) -> int: """获取下一个序号""" pattern = r'\| (\d+) \|' matches = re.findall(pattern, content) if matches: return max([int(m) for m in matches]) + 1 return 1 def _replace_table_row(self, content: str, token: str, new_row: str) -> str: """替换表格中的某一行""" pattern = r'(\| \d+ \| [^|]+ \| [^|]+ \| [^/]+/docx/' + re.escape(token) + r'[^|]* \|[^\n]+)' return re.sub(pattern, new_row, content) def _insert_table_row(self, content: str, new_row: str) -> str: """插入新行到表格""" # 在表格头部后插入 lines = content.split('\n') insert_index = -1 for i, line in enumerate(lines): if line.startswith('| 序号 '): insert_index = i + 2 # 跳过表头和分隔线 break if insert_index > 0: lines.insert(insert_index, new_row) return '\n'.join(lines) def _update_categories(self, content: str, name: str, tags: List[str]) -> str: """更新分类列表""" # 简单实现:在对应分类下添加文档名 # 这里可以扩展更复杂的逻辑 return content def _update_keywords(self, content: str, name: str, summary: str, tags: List[str]) -> str: """更新关键词索引""" # 提取关键词 keywords = [] if tags: keywords.extend(tags) # 这里可以添加更智能的关键词提取 # 暂时保持简单 return content def _update_last_modified(self, content: str) -> str: """更新最后修改时间""" now = datetime.now().strftime("%Y-%m-%d") pattern = r'\*最后更新:[^*]+\*' replacement = f'*最后更新:{now}*' return re.sub(pattern, replacement, content) # 便捷函数 def add_doc_to_index(name: str, url: str, token: str, summary: str = "", tags: List[str] = None, owner: str = "") -> bool: """便捷函数:添加文档到索引""" manager = IndexManager() return manager.add_or_update_doc( name=name, doc_type="docx", url=url, token=token, summary=summary, status="已完成", tags=tags, owner=owner ) def search_docs(keyword: str) -> List[Dict]: """便捷函数:搜索文档""" manager = IndexManager() return manager.search_docs(keyword) def list_all_docs(tag: str = None) -> List[Dict]: """便捷函数:列出所有文档""" manager = IndexManager() return manager.list_docs(tag=tag)