ltcai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/p_reinforce.py ADDED
@@ -0,0 +1,148 @@
1
+ """
2
+ P-Reinforce Knowledge Gardener
3
+ Raw 데이터를 자동으로 분석해서 구조화된 마크다운 위키로 정리
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import re
9
+ import time
10
+ import shutil
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ BRAIN_DIR = Path(
16
+ os.getenv("LATTICEAI_OBSIDIAN_VAULT_DIR")
17
+ or os.getenv("LATTICEAI_BRAIN_DIR")
18
+ or Path.home() / ".ltcai-brain"
19
+ )
20
+
21
+ STRUCTURE = {
22
+ "10_Wiki": "검증된 지식, 개념 설명, 레퍼런스",
23
+ "00_Raw": "정제되지 않은 원시 데이터, 아이디어 메모",
24
+ "20_Skills": "재사용 가능한 코드 스니펫, 프롬프트, 워크플로",
25
+ "30_Projects": "프로젝트별 컨텍스트, 진행 상황",
26
+ "40_Log": "날짜별 작업 로그",
27
+ }
28
+
29
+
30
+ class PReinforceGardener:
31
+ def __init__(self):
32
+ self._ensure_structure()
33
+
34
+ def _ensure_structure(self):
35
+ for folder in STRUCTURE:
36
+ (BRAIN_DIR / folder).mkdir(parents=True, exist_ok=True)
37
+ # 인덱스 파일
38
+ index_path = BRAIN_DIR / "INDEX.md"
39
+ if not index_path.exists():
40
+ index_path.write_text(self._render_index())
41
+
42
+ def _render_index(self) -> str:
43
+ lines = ["# 🧠 Lattice AI Brain — P-Reinforce Index\n"]
44
+ lines.append(f"*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n")
45
+ lines.append("\nThis folder is an Obsidian-compatible Markdown vault.\n")
46
+ for folder, desc in STRUCTURE.items():
47
+ lines.append(f"## [{folder}](./{folder}/)\n_{desc}_\n")
48
+ lines.append("## Connector Status\n")
49
+ lines.append(f"- OCR engine: `{'tesseract' if shutil.which('tesseract') else 'not installed'}`\n")
50
+ return "\n".join(lines)
51
+
52
+ # ── Classify ──────────────────────────────────────────────────────────────
53
+
54
+ def _classify(self, text: str) -> str:
55
+ """간단한 규칙 기반 분류 (LLM 없이도 동작)"""
56
+ text_lower = text.lower()
57
+
58
+ code_signals = ["def ", "class ", "import ", "```", "function ", "const ", "let ", "var "]
59
+ if any(s in text for s in code_signals):
60
+ return "20_Skills"
61
+
62
+ wiki_signals = ["개념", "원리", "이란", "what is", "how does", "definition", "explanation"]
63
+ if any(s in text_lower for s in wiki_signals):
64
+ return "10_Wiki"
65
+
66
+ project_signals = ["project", "프로젝트", "todo", "task", "작업", "기능", "feature"]
67
+ if any(s in text_lower for s in project_signals):
68
+ return "30_Projects"
69
+
70
+ return "00_Raw"
71
+
72
+ # ── File Naming ───────────────────────────────────────────────────────────
73
+
74
+ def _make_filename(self, text: str, folder: str) -> str:
75
+ # 첫 줄을 제목으로
76
+ first_line = text.strip().split("\n")[0][:60]
77
+ # 파일명 안전하게
78
+ safe = re.sub(r"[^\w\s-]", "", first_line).strip()
79
+ safe = re.sub(r"\s+", "_", safe)
80
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
81
+ return f"{timestamp}_{safe or 'note'}.md"
82
+
83
+ # ── Process ───────────────────────────────────────────────────────────────
84
+
85
+ async def process(self, raw_data: str, category: Optional[str] = None) -> dict:
86
+ folder = category if category in STRUCTURE else self._classify(raw_data)
87
+ filename = self._make_filename(raw_data, folder)
88
+ filepath = BRAIN_DIR / folder / filename
89
+
90
+ # 마크다운 래핑
91
+ content = self._wrap_markdown(raw_data, folder)
92
+ filepath.write_text(content, encoding="utf-8")
93
+
94
+ # 오늘 로그에도 기록
95
+ self._append_log(raw_data[:200], folder, filename)
96
+
97
+ return {
98
+ "status": "saved",
99
+ "folder": folder,
100
+ "filename": filename,
101
+ "path": str(filepath),
102
+ "classified_as": folder,
103
+ "description": STRUCTURE[folder],
104
+ }
105
+
106
+ def _wrap_markdown(self, raw: str, folder: str) -> str:
107
+ now = datetime.now().strftime("%Y-%m-%d %H:%M")
108
+ first_line = raw.strip().split("\n")[0][:80]
109
+ lines = [
110
+ f"# {first_line}",
111
+ f"\n> 📁 `{folder}` | 🕐 {now} | Lattice AI MLX\n",
112
+ "---\n",
113
+ raw,
114
+ "\n\n---",
115
+ f"*Auto-organized by P-Reinforce Gardener*",
116
+ ]
117
+ return "\n".join(lines)
118
+
119
+ def _append_log(self, preview: str, folder: str, filename: str):
120
+ today = datetime.now().strftime("%Y-%m-%d")
121
+ log_path = BRAIN_DIR / "40_Log" / f"{today}.md"
122
+ entry = f"\n- [{datetime.now().strftime('%H:%M')}] → `{folder}/{filename}`\n > {preview[:100]}\n"
123
+ with open(log_path, "a", encoding="utf-8") as f:
124
+ if log_path.stat().st_size == 0 if log_path.exists() else True:
125
+ f.write(f"# 📅 Log — {today}\n")
126
+ f.write(entry)
127
+
128
+ # ── Tree ──────────────────────────────────────────────────────────────────
129
+
130
+ def get_relevant_context(self, query: str, limit: int = 3) -> str:
131
+ """질문과 관련된 지식을 검색하여 컨텍스트 문자열을 반환합니다."""
132
+ results = []
133
+ # 모든 마크다운 파일 탐색 (INDEX.md 및 Log 제외)
134
+ for file_path in BRAIN_DIR.rglob("*.md"):
135
+ if file_path.name == "INDEX.md" or "40_Log" in str(file_path):
136
+ continue
137
+
138
+ try:
139
+ content = file_path.read_text(encoding="utf-8")
140
+ keywords = [k for k in re.split(r'\s+', query) if len(k) > 1]
141
+ if any(k.lower() in content.lower() for k in keywords):
142
+ results.append(f"--- Document: {file_path.name} ---\n{content[:800]}")
143
+ if len(results) >= limit:
144
+ break
145
+ except Exception:
146
+ continue
147
+
148
+ return "\n\n".join(results)
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "ltcai",
3
+ "version": "0.1.0",
4
+ "description": "Lattice AI local MLX/cloud LLM workspace server",
5
+ "bin": {
6
+ "ltcai": "bin/ltcai.js",
7
+ "LTCAI": "bin/ltcai.js"
8
+ },
9
+ "scripts": {
10
+ "start": "LTCAI",
11
+ "dev": "python3 ltcai_cli.py --reload",
12
+ "build:python": "python3 -m build",
13
+ "check:python": "python3 -m py_compile ltcai_cli.py server.py llm_router.py p_reinforce.py telegram_bot.py tools.py codex_telegram_bot.py",
14
+ "publish:npm": "npm publish --access public",
15
+ "publish:pypi": "python3 -m twine upload dist/*"
16
+ },
17
+ "keywords": [
18
+ "ltcai",
19
+ "llm",
20
+ "mlx",
21
+ "LTCAI"
22
+ ],
23
+ "license": "MIT",
24
+ "private": false,
25
+ "files": [
26
+ "bin/ltcai.js",
27
+ "LICENSE",
28
+ "ltcai_cli.py",
29
+ "server.py",
30
+ "llm_router.py",
31
+ "p_reinforce.py",
32
+ "telegram_bot.py",
33
+ "tools.py",
34
+ "codex_telegram_bot.py",
35
+ "static/index.html",
36
+ "static/indexd.html",
37
+ "static/admin.html",
38
+ "requirements.txt",
39
+ "README.md"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -0,0 +1,11 @@
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
+ httpx
5
+ pillow
6
+ openai
7
+ python-docx
8
+ openpyxl
9
+ python-pptx
10
+ python-multipart
11
+ keyring