ltcai 4.0.0 → 4.0.1

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.
Files changed (108) hide show
  1. package/README.md +37 -33
  2. package/docs/CHANGELOG.md +64 -0
  3. package/docs/REALTIME_COLLABORATION.md +3 -3
  4. package/docs/V3_FRONTEND.md +9 -8
  5. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +86 -43
  6. package/docs/kg-schema.md +6 -2
  7. package/docs/spec-vs-impl.md +10 -10
  8. package/kg_schema.py +2 -603
  9. package/knowledge_graph.py +37 -4958
  10. package/latticeai/__init__.py +1 -1
  11. package/latticeai/api/admin.py +15 -16
  12. package/latticeai/api/agents.py +13 -6
  13. package/latticeai/api/auth.py +19 -11
  14. package/latticeai/api/invitations.py +100 -0
  15. package/latticeai/api/knowledge_graph.py +4 -11
  16. package/latticeai/api/plugins.py +3 -6
  17. package/latticeai/api/realtime.py +4 -7
  18. package/latticeai/api/static_routes.py +9 -12
  19. package/latticeai/api/ui_redirects.py +26 -0
  20. package/latticeai/api/workflow_designer.py +39 -6
  21. package/latticeai/api/workspace.py +24 -10
  22. package/latticeai/app_factory.py +88 -17
  23. package/latticeai/brain/_kg_common.py +1123 -0
  24. package/latticeai/brain/discovery.py +1455 -0
  25. package/latticeai/brain/documents.py +218 -0
  26. package/latticeai/brain/ingest.py +644 -0
  27. package/latticeai/brain/projection.py +561 -0
  28. package/latticeai/brain/provenance.py +401 -0
  29. package/latticeai/brain/retrieval.py +1316 -0
  30. package/latticeai/brain/schema.py +640 -0
  31. package/latticeai/brain/store.py +216 -0
  32. package/latticeai/brain/write_master.py +225 -0
  33. package/latticeai/core/invitations.py +131 -0
  34. package/latticeai/core/marketplace.py +1 -1
  35. package/latticeai/core/multi_agent.py +1 -1
  36. package/latticeai/core/policy.py +54 -0
  37. package/latticeai/core/realtime.py +65 -44
  38. package/latticeai/core/sessions.py +31 -5
  39. package/latticeai/core/users.py +147 -0
  40. package/latticeai/core/workspace_os.py +420 -20
  41. package/latticeai/services/agent_runtime.py +242 -4
  42. package/latticeai/services/run_executor.py +328 -0
  43. package/latticeai/services/workspace_service.py +27 -19
  44. package/package.json +2 -14
  45. package/scripts/lint_v3.mjs +23 -0
  46. package/static/v3/asset-manifest.json +21 -14
  47. package/static/v3/js/{app.356e6452.js → app.c5c80c46.js} +1 -1
  48. package/static/v3/js/core/{api.7a308b89.js → api.ba0fbf14.js} +58 -1
  49. package/static/v3/js/core/api.js +57 -0
  50. package/static/v3/js/core/i18n.880e1fec.js +575 -0
  51. package/static/v3/js/core/i18n.js +575 -0
  52. package/static/v3/js/core/routes.37522821.js +101 -0
  53. package/static/v3/js/core/routes.js +71 -63
  54. package/static/v3/js/core/{shell.a1657f20.js → shell.e3f6bbfa.js} +67 -38
  55. package/static/v3/js/core/shell.js +65 -36
  56. package/static/v3/js/core/{store.204a08b2.js → store.7b2aa044.js} +10 -0
  57. package/static/v3/js/core/store.js +10 -0
  58. package/static/v3/js/views/account.eff40715.js +143 -0
  59. package/static/v3/js/views/account.js +143 -0
  60. package/static/v3/js/views/activity.0d271ef9.js +67 -0
  61. package/static/v3/js/views/activity.js +67 -0
  62. package/static/v3/js/views/{admin-users.03bac88c.js → admin-users.f7ac7b43.js} +4 -6
  63. package/static/v3/js/views/admin-users.js +4 -6
  64. package/static/v3/js/views/{agents.014d0b74.js → agents.17c5288d.js} +35 -12
  65. package/static/v3/js/views/agents.js +35 -12
  66. package/static/v3/js/views/{chat.e6dd7dd0.js → chat.e250e2cc.js} +23 -0
  67. package/static/v3/js/views/chat.js +23 -0
  68. package/static/v3/js/views/{knowledge-graph.5e40cbeb.js → knowledge-graph.4d09c537.js} +27 -7
  69. package/static/v3/js/views/knowledge-graph.js +27 -7
  70. package/static/v3/js/views/network.52a4f181.js +97 -0
  71. package/static/v3/js/views/network.js +97 -0
  72. package/static/v3/js/views/{planning.9ac3e313.js → planning.4876fd77.js} +26 -5
  73. package/static/v3/js/views/planning.js +26 -5
  74. package/static/v3/js/views/runs.b63b2afa.js +144 -0
  75. package/static/v3/js/views/runs.js +144 -0
  76. package/static/v3/js/views/{settings.8631fa5e.js → settings.b7140634.js} +7 -8
  77. package/static/v3/js/views/settings.js +7 -8
  78. package/static/v3/js/views/snapshots.6f5db095.js +135 -0
  79. package/static/v3/js/views/snapshots.js +135 -0
  80. package/static/v3/js/views/{workflows.26c57290.js → workflows.7752225a.js} +87 -2
  81. package/static/v3/js/views/workflows.js +87 -2
  82. package/static/v3/js/views/workspace-admin.c466029b.js +156 -0
  83. package/static/v3/js/views/workspace-admin.js +156 -0
  84. package/static/account.html +0 -113
  85. package/static/activity.html +0 -73
  86. package/static/admin.html +0 -486
  87. package/static/agents.html +0 -139
  88. package/static/chat.html +0 -841
  89. package/static/css/reference/account.css +0 -439
  90. package/static/css/reference/admin.css +0 -610
  91. package/static/css/reference/base.css +0 -1661
  92. package/static/css/reference/chat.css +0 -4623
  93. package/static/css/reference/graph.css +0 -1016
  94. package/static/css/responsive.css +0 -861
  95. package/static/graph.html +0 -122
  96. package/static/platform.css +0 -104
  97. package/static/plugins.html +0 -136
  98. package/static/scripts/account.js +0 -238
  99. package/static/scripts/admin.js +0 -1614
  100. package/static/scripts/chat.js +0 -5081
  101. package/static/scripts/graph.js +0 -1804
  102. package/static/scripts/platform.js +0 -64
  103. package/static/scripts/ux.js +0 -167
  104. package/static/scripts/workspace.js +0 -948
  105. package/static/v3/js/core/routes.7222343d.js +0 -93
  106. package/static/workflows.html +0 -146
  107. package/static/workspace.css +0 -1121
  108. package/static/workspace.html +0 -357
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ # ruff: noqa: F403,F405
4
+
5
+ from ._kg_common import * # noqa: F403,F401
6
+
7
+
8
+ class KnowledgeGraphDocumentsMixin:
9
+ def _ingest_structure_nodes(
10
+ self,
11
+ conn: sqlite3.Connection,
12
+ file_id: str,
13
+ filename: str,
14
+ structure: Dict[str, Any],
15
+ ) -> None:
16
+ for slide in structure.get("slides") or []:
17
+ index = slide.get("index")
18
+ slide_id = f"slide:{_sha256_text(f'{file_id}:slide:{index}')[:24]}"
19
+ title = f"{filename} slide {index}"
20
+ summary = "\n".join(slide.get("texts") or [])[:800]
21
+ self._upsert_node(
22
+ conn, slide_id, "Slide", title, summary=summary, metadata=slide
23
+ )
24
+ self._upsert_edge(conn, file_id, slide_id, "has_slide")
25
+ for text in slide.get("texts") or []:
26
+ for topic in _topic_candidates(text, limit=4):
27
+ topic_id = f"topic:{_slug(topic)}"
28
+ self._upsert_node(
29
+ conn,
30
+ topic_id,
31
+ "Topic",
32
+ topic,
33
+ metadata={"auto_extracted": True},
34
+ )
35
+ self._upsert_edge(conn, slide_id, topic_id, "discusses", weight=0.6)
36
+
37
+ for page in structure.get("pages") or []:
38
+ index = page.get("index")
39
+ page_id = f"page:{_sha256_text(f'{file_id}:page:{index}')[:24]}"
40
+ title = f"{filename} page {index}"
41
+ self._upsert_node(
42
+ conn,
43
+ page_id,
44
+ "Page",
45
+ title,
46
+ summary=page.get("preview") or "",
47
+ metadata=page,
48
+ )
49
+ self._upsert_edge(conn, file_id, page_id, "has_page")
50
+ for topic in _topic_candidates(page.get("preview") or "", limit=4):
51
+ topic_id = f"topic:{_slug(topic)}"
52
+ self._upsert_node(
53
+ conn, topic_id, "Topic", topic, metadata={"auto_extracted": True}
54
+ )
55
+ self._upsert_edge(conn, page_id, topic_id, "discusses", weight=0.6)
56
+
57
+ for sheet in structure.get("sheets") or []:
58
+ sheet_title = sheet.get("title")
59
+ sheet_id = f"sheet:{_sha256_text(f'{file_id}:sheet:{sheet_title}')[:24]}"
60
+ self._upsert_node(
61
+ conn, sheet_id, "Sheet", f"{filename} / {sheet_title}", metadata=sheet
62
+ )
63
+ self._upsert_edge(conn, file_id, sheet_id, "has_sheet")
64
+
65
+ for image in structure.get("images") or []:
66
+ image_key = image.get("sha256") or _sha256_text(
67
+ json.dumps(image, ensure_ascii=False, sort_keys=True)
68
+ )
69
+ image_id = f"image:{str(image_key)[:24]}"
70
+ title_parts = [filename, "image"]
71
+ if image.get("page"):
72
+ title_parts.append(f"page {image.get('page')}")
73
+ if image.get("name"):
74
+ title_parts.append(str(image.get("name")).split("/")[-1])
75
+ self._upsert_node(
76
+ conn, image_id, "Image", " / ".join(title_parts), metadata=image
77
+ )
78
+ self._upsert_edge(conn, file_id, image_id, "contains_image")
79
+
80
+ def _document_structure(self, path: Path, ext: str) -> Dict[str, Any]:
81
+ try:
82
+ if ext == ".pptx":
83
+ return self._pptx_structure(path)
84
+ if ext == ".pdf":
85
+ return self._pdf_structure(path)
86
+ if ext == ".docx":
87
+ return self._docx_structure(path)
88
+ if ext == ".xlsx":
89
+ return self._xlsx_structure(path)
90
+ except Exception as exc:
91
+ return {"error": str(exc)}
92
+ return {}
93
+
94
+ def _pptx_structure(self, path: Path) -> Dict[str, Any]:
95
+ result: Dict[str, Any] = {"slides": [], "images": []}
96
+ try:
97
+ from PIL import Image
98
+ from pptx import Presentation
99
+
100
+ prs = Presentation(str(path))
101
+ for slide_index, slide in enumerate(prs.slides, start=1):
102
+ slide_info = {"index": slide_index, "shapes": [], "texts": []}
103
+ for shape_index, shape in enumerate(slide.shapes, start=1):
104
+ shape_info = {
105
+ "index": shape_index,
106
+ "name": getattr(shape, "name", ""),
107
+ "shape_type": str(getattr(shape, "shape_type", "")),
108
+ "bbox": {
109
+ "left": int(getattr(shape, "left", 0) or 0),
110
+ "top": int(getattr(shape, "top", 0) or 0),
111
+ "width": int(getattr(shape, "width", 0) or 0),
112
+ "height": int(getattr(shape, "height", 0) or 0),
113
+ },
114
+ }
115
+ if getattr(shape, "has_text_frame", False):
116
+ text = shape.text_frame.text.strip()
117
+ if text:
118
+ shape_info["text"] = text[:1000]
119
+ slide_info["texts"].append(text)
120
+ slide_info["shapes"].append(shape_info)
121
+ result["slides"].append(slide_info)
122
+ with zipfile.ZipFile(path) as zf:
123
+ for name in zf.namelist():
124
+ if not name.startswith("ppt/media/"):
125
+ continue
126
+ data = zf.read(name)
127
+ image_info: Dict[str, Any] = {
128
+ "name": name,
129
+ "bytes": len(data),
130
+ "sha256": _sha256_bytes(data),
131
+ }
132
+ try:
133
+ from io import BytesIO
134
+
135
+ with Image.open(BytesIO(data)) as img:
136
+ image_info.update(
137
+ {
138
+ "width": img.width,
139
+ "height": img.height,
140
+ "format": img.format,
141
+ }
142
+ )
143
+ except Exception:
144
+ pass
145
+ result["images"].append(image_info)
146
+ except Exception as exc:
147
+ result["error"] = str(exc)
148
+ return result
149
+
150
+ def _pdf_structure(self, path: Path) -> Dict[str, Any]:
151
+ result: Dict[str, Any] = {"pages": [], "images": []}
152
+ try:
153
+ import pdfplumber
154
+
155
+ with pdfplumber.open(str(path)) as pdf:
156
+ metadata = dict(pdf.metadata or {})
157
+ result["metadata"] = {str(k): str(v) for k, v in metadata.items()}
158
+ for page_index, page in enumerate(pdf.pages, start=1):
159
+ text = page.extract_text() or ""
160
+ page_info = {
161
+ "index": page_index,
162
+ "width": float(page.width or 0),
163
+ "height": float(page.height or 0),
164
+ "chars": len(text),
165
+ "preview": _clean_text(text)[:500],
166
+ "image_count": len(page.images or []),
167
+ }
168
+ result["pages"].append(page_info)
169
+ for image_index, image in enumerate(page.images or [], start=1):
170
+ result["images"].append(
171
+ {
172
+ "page": page_index,
173
+ "index": image_index,
174
+ "name": image.get("name"),
175
+ "width": image.get("width"),
176
+ "height": image.get("height"),
177
+ "bbox": {
178
+ "x0": image.get("x0"),
179
+ "top": image.get("top"),
180
+ "x1": image.get("x1"),
181
+ "bottom": image.get("bottom"),
182
+ },
183
+ }
184
+ )
185
+ except Exception as exc:
186
+ result["error"] = str(exc)
187
+ return result
188
+
189
+ def _docx_structure(self, path: Path) -> Dict[str, Any]:
190
+ from docx import Document
191
+
192
+ doc = Document(str(path))
193
+ headings = []
194
+ paragraphs = 0
195
+ for p in doc.paragraphs:
196
+ text = p.text.strip()
197
+ if not text:
198
+ continue
199
+ paragraphs += 1
200
+ style = getattr(p.style, "name", "")
201
+ if style.lower().startswith("heading"):
202
+ headings.append({"style": style, "text": text[:240]})
203
+ return {
204
+ "paragraphs": paragraphs,
205
+ "headings": headings[:80],
206
+ "tables": len(doc.tables),
207
+ }
208
+
209
+ def _xlsx_structure(self, path: Path) -> Dict[str, Any]:
210
+ from openpyxl import load_workbook
211
+
212
+ wb = load_workbook(str(path), read_only=True, data_only=True)
213
+ sheets = []
214
+ for ws in wb.worksheets:
215
+ sheets.append(
216
+ {"title": ws.title, "max_row": ws.max_row, "max_column": ws.max_column}
217
+ )
218
+ return {"sheets": sheets}