ltcai 2.2.2 → 3.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.
- package/README.md +66 -27
- package/codex_telegram_bot.py +6 -2
- package/docs/CHANGELOG.md +154 -0
- package/docs/V3_BACKEND_ARCHITECTURE.md +138 -0
- package/docs/V3_FRONTEND.md +136 -0
- package/knowledge_graph.py +649 -21
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +47 -0
- package/latticeai/api/agents.py +54 -31
- package/latticeai/api/auth.py +1 -1
- package/latticeai/api/chat.py +10 -2
- package/latticeai/api/search.py +236 -0
- package/latticeai/api/static_routes.py +21 -2
- package/latticeai/core/config.py +16 -0
- package/latticeai/core/embedding_providers.py +502 -0
- package/latticeai/core/local_embeddings.py +86 -0
- package/latticeai/core/logging_safety.py +62 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +49 -1
- package/latticeai/services/agent_runtime.py +245 -0
- package/latticeai/services/search_service.py +346 -0
- package/package.json +8 -4
- package/static/account.html +9 -4
- package/static/activity.html +4 -4
- package/static/admin.html +8 -3
- package/static/agents.html +4 -4
- package/static/chat.html +16 -11
- package/static/css/reference/account.css +439 -0
- package/static/css/reference/admin.css +610 -0
- package/static/css/reference/base.css +1658 -0
- package/static/{lattice-reference.css → css/reference/chat.css} +271 -3633
- package/static/css/reference/graph.css +1016 -0
- package/static/css/responsive.css +248 -1
- package/static/css/tokens.css +132 -126
- package/static/favicon.ico +0 -0
- package/static/graph.html +9 -4
- package/static/manifest.json +3 -3
- package/static/platform.css +1 -1
- package/static/plugins.html +4 -4
- package/static/scripts/account.js +4 -4
- package/static/scripts/chat.js +227 -77
- package/static/scripts/workspace.js +78 -0
- package/static/sw.js +5 -3
- package/static/v3/css/lattice.base.css +128 -0
- package/static/v3/css/lattice.components.css +447 -0
- package/static/v3/css/lattice.shell.css +407 -0
- package/static/v3/css/lattice.tokens.css +132 -0
- package/static/v3/css/lattice.views.css +277 -0
- package/static/v3/index.html +40 -0
- package/static/v3/js/app.js +26 -0
- package/static/v3/js/core/api.js +327 -0
- package/static/v3/js/core/components.js +215 -0
- package/static/v3/js/core/dom.js +148 -0
- package/static/v3/js/core/fixtures.js +171 -0
- package/static/v3/js/core/router.js +37 -0
- package/static/v3/js/core/routes.js +73 -0
- package/static/v3/js/core/shell.js +363 -0
- package/static/v3/js/core/store.js +113 -0
- package/static/v3/js/views/admin-audit.js +185 -0
- package/static/v3/js/views/admin-permissions.js +178 -0
- package/static/v3/js/views/admin-policies.js +103 -0
- package/static/v3/js/views/admin-private-vpc.js +138 -0
- package/static/v3/js/views/admin-security.js +181 -0
- package/static/v3/js/views/admin-users.js +168 -0
- package/static/v3/js/views/agents.js +194 -0
- package/static/v3/js/views/chat.js +450 -0
- package/static/v3/js/views/files.js +180 -0
- package/static/v3/js/views/home.js +119 -0
- package/static/v3/js/views/hybrid-search.js +195 -0
- package/static/v3/js/views/knowledge-graph.js +238 -0
- package/static/v3/js/views/models.js +247 -0
- package/static/v3/js/views/my-computer.js +237 -0
- package/static/v3/js/views/pipeline.js +161 -0
- package/static/v3/js/views/settings.js +258 -0
- package/static/workflows.html +4 -4
- package/static/workspace.css +408 -14
- package/static/workspace.html +43 -24
- package/telegram_bot.py +18 -14
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Backend search orchestration for Lattice AI v3.
|
|
2
|
+
|
|
3
|
+
The service composes the existing knowledge graph, the local vector index, and
|
|
4
|
+
keyword search into UI-ready contracts without tying routers to store internals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Dict, List, Mapping, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DEFAULT_HYBRID_WEIGHTS = {
|
|
14
|
+
"keyword": 0.35,
|
|
15
|
+
"vector": 0.40,
|
|
16
|
+
"graph": 0.25,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _clean(text: Any, limit: int = 1000) -> str:
|
|
21
|
+
return " ".join(str(text or "").split())[:limit]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _result_key(result: Mapping[str, Any]) -> str:
|
|
25
|
+
return str(result.get("id") or result.get("node_id") or "")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SearchService:
|
|
30
|
+
graph_store: Any
|
|
31
|
+
|
|
32
|
+
def _require_graph(self) -> Any:
|
|
33
|
+
if self.graph_store is None:
|
|
34
|
+
raise ValueError("knowledge graph is disabled")
|
|
35
|
+
return self.graph_store
|
|
36
|
+
|
|
37
|
+
def keyword_search(self, query: str, *, limit: int = 30) -> Dict[str, Any]:
|
|
38
|
+
graph = self._require_graph()
|
|
39
|
+
payload = graph.search(query, limit)
|
|
40
|
+
matches = []
|
|
41
|
+
for rank, match in enumerate(payload.get("matches", []), start=1):
|
|
42
|
+
matches.append({
|
|
43
|
+
"id": match["id"],
|
|
44
|
+
"node_id": match["id"],
|
|
45
|
+
"item_type": "node",
|
|
46
|
+
"type": match.get("type"),
|
|
47
|
+
"title": match.get("title"),
|
|
48
|
+
"summary": _clean(match.get("summary")),
|
|
49
|
+
"score": round(1.0 / rank, 6),
|
|
50
|
+
"rank": rank,
|
|
51
|
+
"sources": ["keyword"],
|
|
52
|
+
"source_scores": {"keyword": round(1.0 / rank, 6)},
|
|
53
|
+
"metadata": match.get("metadata") or {},
|
|
54
|
+
"updated_at": match.get("updated_at"),
|
|
55
|
+
})
|
|
56
|
+
return {"query": query, "mode": "keyword", "matches": matches}
|
|
57
|
+
|
|
58
|
+
def vector_search(self, query: str, *, limit: int = 30, min_score: float = 0.0) -> Dict[str, Any]:
|
|
59
|
+
graph = self._require_graph()
|
|
60
|
+
payload = graph.vector_search(query, limit=limit, min_score=min_score)
|
|
61
|
+
matches = []
|
|
62
|
+
for rank, match in enumerate(payload.get("matches", []), start=1):
|
|
63
|
+
score = float(match.get("score") or 0.0)
|
|
64
|
+
matches.append({
|
|
65
|
+
"id": match.get("id"),
|
|
66
|
+
"node_id": match.get("node_id"),
|
|
67
|
+
"item_type": match.get("item_type"),
|
|
68
|
+
"type": match.get("type"),
|
|
69
|
+
"title": match.get("title"),
|
|
70
|
+
"summary": _clean(match.get("summary")),
|
|
71
|
+
"score": round(score, 6),
|
|
72
|
+
"rank": rank,
|
|
73
|
+
"sources": ["vector"],
|
|
74
|
+
"source_scores": {"vector": round(score, 6)},
|
|
75
|
+
"metadata": match.get("metadata") or {},
|
|
76
|
+
"updated_at": match.get("updated_at"),
|
|
77
|
+
})
|
|
78
|
+
return {
|
|
79
|
+
"query": query,
|
|
80
|
+
"mode": "vector",
|
|
81
|
+
"embedding_model": payload.get("embedding_model"),
|
|
82
|
+
"embedding_dim": payload.get("embedding_dim"),
|
|
83
|
+
"matches": matches,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def graph_search(self, query: str, *, limit: int = 30, expand_depth: int = 1) -> Dict[str, Any]:
|
|
87
|
+
graph = self._require_graph()
|
|
88
|
+
limit = max(1, min(int(limit or 30), 100))
|
|
89
|
+
expand_depth = max(0, min(int(expand_depth or 1), 3))
|
|
90
|
+
direct = graph.search(query, limit=max(limit, 10)).get("matches", [])
|
|
91
|
+
relationships = graph.relationship_search(query=query, limit=limit).get("relationships", [])
|
|
92
|
+
by_id: Dict[str, Dict[str, Any]] = {}
|
|
93
|
+
|
|
94
|
+
def add_node(node: Mapping[str, Any], score: float, reason: str, edge: Optional[Mapping[str, Any]] = None) -> None:
|
|
95
|
+
node_id = str(node.get("id") or "")
|
|
96
|
+
if not node_id:
|
|
97
|
+
return
|
|
98
|
+
current = by_id.get(node_id)
|
|
99
|
+
if not current:
|
|
100
|
+
current = {
|
|
101
|
+
"id": node_id,
|
|
102
|
+
"node_id": node_id,
|
|
103
|
+
"item_type": "node",
|
|
104
|
+
"type": node.get("type"),
|
|
105
|
+
"title": node.get("title"),
|
|
106
|
+
"summary": _clean(node.get("summary")),
|
|
107
|
+
"score": 0.0,
|
|
108
|
+
"sources": ["graph"],
|
|
109
|
+
"source_scores": {"graph": 0.0},
|
|
110
|
+
"metadata": node.get("metadata") or {},
|
|
111
|
+
"updated_at": node.get("updated_at"),
|
|
112
|
+
"graph_context": [],
|
|
113
|
+
}
|
|
114
|
+
by_id[node_id] = current
|
|
115
|
+
current["score"] = max(float(current["score"]), score)
|
|
116
|
+
current["source_scores"]["graph"] = max(float(current["source_scores"]["graph"]), score)
|
|
117
|
+
context = {"reason": reason}
|
|
118
|
+
if edge:
|
|
119
|
+
context["relationship"] = {
|
|
120
|
+
"id": edge.get("id"),
|
|
121
|
+
"type": edge.get("type"),
|
|
122
|
+
"weight": edge.get("weight"),
|
|
123
|
+
"from": edge.get("from") or (edge.get("source") or {}).get("id"),
|
|
124
|
+
"to": edge.get("to") or (edge.get("target") or {}).get("id"),
|
|
125
|
+
}
|
|
126
|
+
current["graph_context"].append(context)
|
|
127
|
+
|
|
128
|
+
for rank, match in enumerate(direct, start=1):
|
|
129
|
+
add_node(match, 1.0 / rank, "direct_match")
|
|
130
|
+
if expand_depth <= 0:
|
|
131
|
+
continue
|
|
132
|
+
try:
|
|
133
|
+
neighborhood = graph.traverse(match["id"], depth=expand_depth, limit=limit * 3)
|
|
134
|
+
except Exception:
|
|
135
|
+
neighborhood = {"nodes": [], "edges": []}
|
|
136
|
+
edge_by_pair = {
|
|
137
|
+
(edge.get("from"), edge.get("to")): edge
|
|
138
|
+
for edge in neighborhood.get("edges", [])
|
|
139
|
+
}
|
|
140
|
+
for node in neighborhood.get("nodes", []):
|
|
141
|
+
if node.get("id") == match.get("id"):
|
|
142
|
+
continue
|
|
143
|
+
related_edge = None
|
|
144
|
+
for pair, edge in edge_by_pair.items():
|
|
145
|
+
if match.get("id") in pair and node.get("id") in pair:
|
|
146
|
+
related_edge = edge
|
|
147
|
+
break
|
|
148
|
+
add_node(node, 0.45 / rank, "neighbor_expansion", related_edge)
|
|
149
|
+
|
|
150
|
+
for rank, rel in enumerate(relationships, start=1):
|
|
151
|
+
rel_score = 0.75 / rank
|
|
152
|
+
add_node(rel.get("source") or {}, rel_score, "relationship_match", rel)
|
|
153
|
+
add_node(rel.get("target") or {}, rel_score, "relationship_match", rel)
|
|
154
|
+
|
|
155
|
+
matches = sorted(by_id.values(), key=lambda item: item["score"], reverse=True)[:limit]
|
|
156
|
+
for rank, match in enumerate(matches, start=1):
|
|
157
|
+
match["rank"] = rank
|
|
158
|
+
match["score"] = round(float(match["score"]), 6)
|
|
159
|
+
match["source_scores"]["graph"] = round(float(match["source_scores"]["graph"]), 6)
|
|
160
|
+
return {"query": query, "mode": "graph", "expand_depth": expand_depth, "matches": matches}
|
|
161
|
+
|
|
162
|
+
def hybrid_search(
|
|
163
|
+
self,
|
|
164
|
+
query: str,
|
|
165
|
+
*,
|
|
166
|
+
limit: int = 30,
|
|
167
|
+
keyword_limit: int = 30,
|
|
168
|
+
vector_limit: int = 30,
|
|
169
|
+
graph_limit: int = 30,
|
|
170
|
+
weights: Optional[Mapping[str, float]] = None,
|
|
171
|
+
) -> Dict[str, Any]:
|
|
172
|
+
weights = {**DEFAULT_HYBRID_WEIGHTS, **dict(weights or {})}
|
|
173
|
+
channels = {
|
|
174
|
+
"keyword": self.keyword_search(query, limit=keyword_limit),
|
|
175
|
+
"vector": self.vector_search(query, limit=vector_limit),
|
|
176
|
+
"graph": self.graph_search(query, limit=graph_limit),
|
|
177
|
+
}
|
|
178
|
+
fused: Dict[str, Dict[str, Any]] = {}
|
|
179
|
+
for source, payload in channels.items():
|
|
180
|
+
source_weight = float(weights.get(source, 0.0))
|
|
181
|
+
for rank, result in enumerate(payload.get("matches", []), start=1):
|
|
182
|
+
key = _result_key(result)
|
|
183
|
+
if not key:
|
|
184
|
+
continue
|
|
185
|
+
source_score = float((result.get("source_scores") or {}).get(source, result.get("score") or 0.0))
|
|
186
|
+
rank_score = 1.0 / rank
|
|
187
|
+
contribution = source_weight * max(source_score, rank_score)
|
|
188
|
+
current = fused.get(key)
|
|
189
|
+
if not current:
|
|
190
|
+
current = {
|
|
191
|
+
**result,
|
|
192
|
+
"sources": [],
|
|
193
|
+
"source_scores": {},
|
|
194
|
+
"score": 0.0,
|
|
195
|
+
}
|
|
196
|
+
fused[key] = current
|
|
197
|
+
current["score"] = float(current["score"]) + contribution
|
|
198
|
+
if source not in current["sources"]:
|
|
199
|
+
current["sources"].append(source)
|
|
200
|
+
current["source_scores"][source] = round(source_score, 6)
|
|
201
|
+
if result.get("graph_context"):
|
|
202
|
+
current.setdefault("graph_context", [])
|
|
203
|
+
current["graph_context"].extend(result.get("graph_context") or [])
|
|
204
|
+
|
|
205
|
+
matches = sorted(fused.values(), key=lambda item: item["score"], reverse=True)[: max(1, min(limit, 100))]
|
|
206
|
+
for rank, match in enumerate(matches, start=1):
|
|
207
|
+
match["rank"] = rank
|
|
208
|
+
match["score"] = round(float(match["score"]), 6)
|
|
209
|
+
match["fusion"] = {
|
|
210
|
+
"weights": weights,
|
|
211
|
+
"sources": match.get("sources", []),
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
"query": query,
|
|
215
|
+
"mode": "hybrid",
|
|
216
|
+
"weights": weights,
|
|
217
|
+
"channels": {
|
|
218
|
+
name: {
|
|
219
|
+
key: value
|
|
220
|
+
for key, value in payload.items()
|
|
221
|
+
if key not in {"matches"}
|
|
222
|
+
}
|
|
223
|
+
for name, payload in channels.items()
|
|
224
|
+
},
|
|
225
|
+
"matches": matches,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
def graph(self, *, limit: int = 300) -> Dict[str, Any]:
|
|
229
|
+
return self._require_graph().graph(limit=limit)
|
|
230
|
+
|
|
231
|
+
def node(self, node_id: str, *, include_neighbors: bool = True, depth: int = 1, limit: int = 100) -> Dict[str, Any]:
|
|
232
|
+
graph = self._require_graph()
|
|
233
|
+
payload = {"node": graph.get_node(node_id)}
|
|
234
|
+
if include_neighbors:
|
|
235
|
+
payload["neighborhood"] = graph.traverse(node_id, depth=depth, limit=limit)
|
|
236
|
+
return payload
|
|
237
|
+
|
|
238
|
+
def relationships(
|
|
239
|
+
self,
|
|
240
|
+
*,
|
|
241
|
+
query: str = "",
|
|
242
|
+
node_id: str = "",
|
|
243
|
+
relationship_type: str = "",
|
|
244
|
+
limit: int = 30,
|
|
245
|
+
) -> Dict[str, Any]:
|
|
246
|
+
return self._require_graph().relationship_search(
|
|
247
|
+
query=query,
|
|
248
|
+
node_id=node_id,
|
|
249
|
+
relationship_type=relationship_type,
|
|
250
|
+
limit=limit,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def index_status(self) -> Dict[str, Any]:
|
|
254
|
+
return self._require_graph().index_status()
|
|
255
|
+
|
|
256
|
+
def embeddings_status(
|
|
257
|
+
self,
|
|
258
|
+
*,
|
|
259
|
+
resolved: Optional[Mapping[str, Any]] = None,
|
|
260
|
+
refresh: bool = False,
|
|
261
|
+
) -> Dict[str, Any]:
|
|
262
|
+
"""Report the active embedding provider for the Models → Embeddings UI.
|
|
263
|
+
|
|
264
|
+
Combines the resolved-provider info (requested vs active, fallback,
|
|
265
|
+
health) with the vector index's identity and last build time. The
|
|
266
|
+
``state`` is one of ``production`` | ``fallback`` | ``unavailable`` so
|
|
267
|
+
the UI never shows a down provider as live.
|
|
268
|
+
"""
|
|
269
|
+
resolved = dict(resolved or {})
|
|
270
|
+
graph = self.graph_store
|
|
271
|
+
embedder = getattr(graph, "_embedding_model", None)
|
|
272
|
+
|
|
273
|
+
meta: Dict[str, Any] = {}
|
|
274
|
+
if embedder is not None and hasattr(embedder, "metadata"):
|
|
275
|
+
try:
|
|
276
|
+
meta = dict(embedder.metadata())
|
|
277
|
+
except Exception:
|
|
278
|
+
meta = {}
|
|
279
|
+
else: # legacy LocalEmbeddingModel
|
|
280
|
+
meta = {
|
|
281
|
+
"provider": "hash",
|
|
282
|
+
"model": getattr(embedder, "model_id", "lattice-local-hash-v1"),
|
|
283
|
+
"model_id": getattr(embedder, "model_id", "lattice-local-hash-v1"),
|
|
284
|
+
"dim": getattr(embedder, "dim", 384),
|
|
285
|
+
"grade": "fallback",
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
health = resolved.get("health") or {"status": "unknown", "detail": ""}
|
|
289
|
+
if refresh and embedder is not None and hasattr(embedder, "health"):
|
|
290
|
+
try:
|
|
291
|
+
health = embedder.health()
|
|
292
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
293
|
+
health = {"status": "unavailable", "detail": str(exc)}
|
|
294
|
+
|
|
295
|
+
fell_back = bool(resolved.get("fell_back"))
|
|
296
|
+
grade = str(meta.get("grade") or ("fallback" if fell_back else "production"))
|
|
297
|
+
if fell_back or health.get("status") == "unavailable":
|
|
298
|
+
state = "unavailable" if fell_back else "fallback"
|
|
299
|
+
else:
|
|
300
|
+
state = "fallback" if grade == "fallback" else "production"
|
|
301
|
+
|
|
302
|
+
index: Dict[str, Any] = {}
|
|
303
|
+
last_indexed_at = None
|
|
304
|
+
if graph is not None:
|
|
305
|
+
try:
|
|
306
|
+
status = graph.index_status()
|
|
307
|
+
index = {
|
|
308
|
+
"status": status.get("status"),
|
|
309
|
+
"source_items": status.get("source_items"),
|
|
310
|
+
"indexed_items": status.get("indexed_items"),
|
|
311
|
+
"ready_items": status.get("ready_items"),
|
|
312
|
+
"pending_items": status.get("pending_items"),
|
|
313
|
+
"stale_items": status.get("stale_items"),
|
|
314
|
+
"embedding_model": (status.get("storage") or {}).get("embedding_model"),
|
|
315
|
+
"embedding_dim": (status.get("storage") or {}).get("embedding_dim"),
|
|
316
|
+
}
|
|
317
|
+
for op in status.get("operations", []):
|
|
318
|
+
if op.get("status") == "completed" and op.get("completed_at"):
|
|
319
|
+
last_indexed_at = op.get("completed_at")
|
|
320
|
+
break
|
|
321
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
322
|
+
index = {"error": str(exc)}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"provider": meta.get("provider"),
|
|
326
|
+
"requested_provider": resolved.get("requested_provider") or meta.get("provider"),
|
|
327
|
+
"active_provider": resolved.get("active_provider") or meta.get("provider"),
|
|
328
|
+
"model": meta.get("model"),
|
|
329
|
+
"model_id": meta.get("model_id"),
|
|
330
|
+
"dimensions": meta.get("dim"),
|
|
331
|
+
"grade": grade,
|
|
332
|
+
"state": state,
|
|
333
|
+
"fell_back": fell_back,
|
|
334
|
+
"health": health,
|
|
335
|
+
"detail": resolved.get("detail", ""),
|
|
336
|
+
"last_indexed_at": last_indexed_at,
|
|
337
|
+
"index": index,
|
|
338
|
+
"available_providers": list(resolved.get("available_providers") or []),
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
def rebuild_index(self, *, full: bool = False, include_nodes: bool = True, include_chunks: bool = True) -> Dict[str, Any]:
|
|
342
|
+
return self._require_graph().rebuild_vector_index(
|
|
343
|
+
full=full,
|
|
344
|
+
include_nodes=include_nodes,
|
|
345
|
+
include_chunks=include_chunks,
|
|
346
|
+
)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Lattice AI v3 local-first AI workspace platform with knowledge graph, vector index, hybrid search, agents, and workspace modes.",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"dev": "python3 ltcai_cli.py --reload",
|
|
20
20
|
"build": "npm run build:python",
|
|
21
21
|
"build:python": "python3 -m build",
|
|
22
|
-
"check:python": "python3 -m py_compile ltcai_cli.py server.py latticeai/server_app.py latticeai/api/chat.py latticeai/api/computer_use.py latticeai/api/deps.py latticeai/api/garden.py latticeai/api/local_files.py latticeai/api/permissions.py latticeai/api/setup.py latticeai/api/static_routes.py latticeai/api/tools.py latticeai/api/plugins.py latticeai/api/workflow_designer.py latticeai/api/agents.py latticeai/api/realtime.py latticeai/api/marketplace.py latticeai/services/app_context.py latticeai/services/model_runtime.py latticeai/services/model_catalog.py latticeai/services/model_recommendation.py latticeai/services/tool_dispatch.py latticeai/services/upload_service.py latticeai/core/tool_registry.py latticeai/core/enterprise.py latticeai/core/enterprise_admin.py latticeai/core/agent_prompts.py latticeai/core/workspace_os.py latticeai/core/plugins.py latticeai/core/marketplace.py latticeai/core/workflow_engine.py latticeai/core/multi_agent.py latticeai/core/realtime.py knowledge_graph.py knowledge_graph_api.py local_knowledge_api.py llm_router.py p_reinforce.py telegram_bot.py tools.py codex_telegram_bot.py",
|
|
22
|
+
"check:python": "python3 -m py_compile ltcai_cli.py server.py latticeai/server_app.py latticeai/api/chat.py latticeai/api/computer_use.py latticeai/api/deps.py latticeai/api/garden.py latticeai/api/local_files.py latticeai/api/permissions.py latticeai/api/setup.py latticeai/api/static_routes.py latticeai/api/tools.py latticeai/api/plugins.py latticeai/api/workflow_designer.py latticeai/api/agents.py latticeai/api/realtime.py latticeai/api/marketplace.py latticeai/api/search.py latticeai/services/search_service.py latticeai/core/local_embeddings.py latticeai/core/embedding_providers.py latticeai/services/agent_runtime.py latticeai/core/config.py latticeai/api/admin.py latticeai/services/app_context.py latticeai/services/model_runtime.py latticeai/services/model_catalog.py latticeai/services/model_recommendation.py latticeai/services/tool_dispatch.py latticeai/services/upload_service.py latticeai/core/tool_registry.py latticeai/core/enterprise.py latticeai/core/enterprise_admin.py latticeai/core/agent_prompts.py latticeai/core/workspace_os.py latticeai/core/plugins.py latticeai/core/marketplace.py latticeai/core/workflow_engine.py latticeai/core/multi_agent.py latticeai/core/realtime.py knowledge_graph.py knowledge_graph_api.py local_knowledge_api.py llm_router.py p_reinforce.py telegram_bot.py tools.py codex_telegram_bot.py",
|
|
23
|
+
"lint": "node --check static/scripts/account.js && node --check static/scripts/admin.js && node --check static/scripts/chat.js && node --check static/scripts/graph.js && node --check static/scripts/platform.js && node --check static/scripts/ux.js && node --check static/scripts/workspace.js && node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:v3",
|
|
24
|
+
"lint:v3": "node scripts/lint_v3.mjs",
|
|
25
|
+
"typecheck": "cd vscode-extension && npm run build",
|
|
23
26
|
"test": "python3 -m pytest tests/ -v",
|
|
24
27
|
"test:unit": "python3 -m pytest tests/unit/ -v",
|
|
25
28
|
"test:integration": "python3 -m pytest tests/integration/ -v",
|
|
@@ -80,13 +83,14 @@
|
|
|
80
83
|
"static/workflows.html",
|
|
81
84
|
"static/agents.html",
|
|
82
85
|
"static/activity.html",
|
|
86
|
+
"static/favicon.ico",
|
|
83
87
|
"static/manifest.json",
|
|
84
88
|
"static/sw.js",
|
|
85
|
-
"static/lattice-reference.css",
|
|
86
89
|
"static/workspace.css",
|
|
87
90
|
"static/platform.css",
|
|
88
91
|
"static/scripts/",
|
|
89
92
|
"static/css/",
|
|
93
|
+
"static/v3/",
|
|
90
94
|
"static/icons/",
|
|
91
95
|
"plugins/",
|
|
92
96
|
"docs/",
|
package/static/account.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
6
6
|
<title>Lattice AI</title>
|
|
7
|
-
<script src="/static/scripts/ux.js?v=
|
|
7
|
+
<script src="/static/scripts/ux.js?v=3.0.0"></script>
|
|
8
8
|
<link rel="manifest" href="/manifest.json">
|
|
9
9
|
<meta name="theme-color" content="#f3ecff">
|
|
10
10
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
15
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap">
|
|
16
16
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
|
17
|
-
<link rel="stylesheet" href="/static/
|
|
18
|
-
<link rel="stylesheet" href="/static/css/
|
|
17
|
+
<link rel="stylesheet" href="/static/css/tokens.css?v=3.0.0">
|
|
18
|
+
<link rel="stylesheet" href="/static/css/reference/base.css?v=3.0.0">
|
|
19
|
+
<link rel="stylesheet" href="/static/css/reference/account.css?v=3.0.0">
|
|
20
|
+
<link rel="stylesheet" href="/static/css/reference/admin.css?v=3.0.0">
|
|
21
|
+
<link rel="stylesheet" href="/static/css/reference/graph.css?v=3.0.0">
|
|
22
|
+
<link rel="stylesheet" href="/static/css/reference/chat.css?v=3.0.0">
|
|
23
|
+
<link rel="stylesheet" href="/static/css/responsive.css?v=3.0.0">
|
|
19
24
|
</head>
|
|
20
25
|
<body class="lattice-ref-auth">
|
|
21
26
|
<div class="orb orb-1"></div>
|
|
@@ -105,6 +110,6 @@
|
|
|
105
110
|
<a href="#" onclick="return false;" id="privacy-link">개인정보 처리방침</a>
|
|
106
111
|
</footer>
|
|
107
112
|
|
|
108
|
-
<script src="/static/scripts/account.js?v=0.
|
|
113
|
+
<script src="/static/scripts/account.js?v=3.0.0"></script>
|
|
109
114
|
</body>
|
|
110
115
|
</html>
|
package/static/activity.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Realtime Activity — Lattice AI</title>
|
|
7
|
-
<script src="/static/scripts/ux.js?v=
|
|
8
|
-
<link rel="stylesheet" href="/static/css/tokens.css?v=
|
|
9
|
-
<link rel="stylesheet" href="/static/platform.css?v=
|
|
10
|
-
<link rel="stylesheet" href="/static/css/responsive.css?v=
|
|
7
|
+
<script src="/static/scripts/ux.js?v=3.0.0"></script>
|
|
8
|
+
<link rel="stylesheet" href="/static/css/tokens.css?v=3.0.0" />
|
|
9
|
+
<link rel="stylesheet" href="/static/platform.css?v=3.0.0" />
|
|
10
|
+
<link rel="stylesheet" href="/static/css/responsive.css?v=3.0.0" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<main>
|
package/static/admin.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
7
7
|
<title>Lattice AI Admin</title>
|
|
8
|
-
<script src="/static/scripts/ux.js?v=
|
|
8
|
+
<script src="/static/scripts/ux.js?v=3.0.0"></script>
|
|
9
9
|
<link rel="manifest" href="/manifest.json">
|
|
10
10
|
<meta name="theme-color" content="#f3ecff">
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
@@ -15,8 +15,13 @@
|
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap">
|
|
17
17
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
|
18
|
-
<link rel="stylesheet" href="/static/
|
|
19
|
-
<link rel="stylesheet" href="/static/css/
|
|
18
|
+
<link rel="stylesheet" href="/static/css/tokens.css?v=3.0.0">
|
|
19
|
+
<link rel="stylesheet" href="/static/css/reference/base.css?v=3.0.0">
|
|
20
|
+
<link rel="stylesheet" href="/static/css/reference/account.css?v=3.0.0">
|
|
21
|
+
<link rel="stylesheet" href="/static/css/reference/admin.css?v=3.0.0">
|
|
22
|
+
<link rel="stylesheet" href="/static/css/reference/graph.css?v=3.0.0">
|
|
23
|
+
<link rel="stylesheet" href="/static/css/reference/chat.css?v=3.0.0">
|
|
24
|
+
<link rel="stylesheet" href="/static/css/responsive.css?v=3.0.0">
|
|
20
25
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
21
26
|
</head>
|
|
22
27
|
|
package/static/agents.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Multi-Agent Runtime — Lattice AI</title>
|
|
7
|
-
<script src="/static/scripts/ux.js?v=
|
|
8
|
-
<link rel="stylesheet" href="/static/css/tokens.css?v=
|
|
9
|
-
<link rel="stylesheet" href="/static/platform.css?v=
|
|
10
|
-
<link rel="stylesheet" href="/static/css/responsive.css?v=
|
|
7
|
+
<script src="/static/scripts/ux.js?v=3.0.0"></script>
|
|
8
|
+
<link rel="stylesheet" href="/static/css/tokens.css?v=3.0.0" />
|
|
9
|
+
<link rel="stylesheet" href="/static/platform.css?v=3.0.0" />
|
|
10
|
+
<link rel="stylesheet" href="/static/css/responsive.css?v=3.0.0" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<main>
|
package/static/chat.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
7
7
|
<title>Lattice AI — All-in-One Multimodal Workspace</title>
|
|
8
|
-
<script src="/static/scripts/ux.js?v=
|
|
8
|
+
<script src="/static/scripts/ux.js?v=3.0.0"></script>
|
|
9
9
|
|
|
10
10
|
<!-- PWA -->
|
|
11
11
|
<link rel="manifest" href="/manifest.json">
|
|
@@ -24,8 +24,13 @@
|
|
|
24
24
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
25
25
|
|
|
26
26
|
<!-- ── Setup Wizard Styles ──────────────────────────────────────────── -->
|
|
27
|
-
<link rel="stylesheet" href="/static/
|
|
28
|
-
<link rel="stylesheet" href="/static/css/
|
|
27
|
+
<link rel="stylesheet" href="/static/css/tokens.css?v=3.0.0">
|
|
28
|
+
<link rel="stylesheet" href="/static/css/reference/base.css?v=3.0.0">
|
|
29
|
+
<link rel="stylesheet" href="/static/css/reference/account.css?v=3.0.0">
|
|
30
|
+
<link rel="stylesheet" href="/static/css/reference/admin.css?v=3.0.0">
|
|
31
|
+
<link rel="stylesheet" href="/static/css/reference/graph.css?v=3.0.0">
|
|
32
|
+
<link rel="stylesheet" href="/static/css/reference/chat.css?v=3.0.0">
|
|
33
|
+
<link rel="stylesheet" href="/static/css/responsive.css?v=3.0.0">
|
|
29
34
|
</head>
|
|
30
35
|
|
|
31
36
|
<body class="lattice-ref-chat">
|
|
@@ -414,7 +419,7 @@
|
|
|
414
419
|
<!-- Planning -->
|
|
415
420
|
<div class="pipeline-phase-row">
|
|
416
421
|
<div class="pipeline-phase-label">
|
|
417
|
-
<span class="pipeline-phase-badge" style="background:rgba(99,102,241,0.15);color
|
|
422
|
+
<span class="pipeline-phase-badge" style="background:rgba(99,102,241,0.15);color:var(--accent)">📋 Planning</span>
|
|
418
423
|
<span style="color:var(--muted);font-size:11px">계획 수립 · 유저와 함께 검토</span>
|
|
419
424
|
</div>
|
|
420
425
|
<select id="pipeline-planning-select" class="pipeline-select">
|
|
@@ -425,7 +430,7 @@
|
|
|
425
430
|
<!-- Executing -->
|
|
426
431
|
<div class="pipeline-phase-row">
|
|
427
432
|
<div class="pipeline-phase-label">
|
|
428
|
-
<span class="pipeline-phase-badge" style="background:rgba(34,197,94,0.12);color
|
|
433
|
+
<span class="pipeline-phase-badge" style="background:rgba(34,197,94,0.12);color:var(--success)">⚙️ Executing</span>
|
|
429
434
|
<span style="color:var(--muted);font-size:11px">코드 작성 · 파일 생성 · 툴 호출</span>
|
|
430
435
|
</div>
|
|
431
436
|
<select id="pipeline-executing-select" class="pipeline-select">
|
|
@@ -436,7 +441,7 @@
|
|
|
436
441
|
<!-- Reviewing -->
|
|
437
442
|
<div class="pipeline-phase-row">
|
|
438
443
|
<div class="pipeline-phase-label">
|
|
439
|
-
<span class="pipeline-phase-badge" style="background:rgba(251,146,60,0.12);color
|
|
444
|
+
<span class="pipeline-phase-badge" style="background:rgba(251,146,60,0.12);color:var(--warning)">🔍 Reviewing</span>
|
|
440
445
|
<span style="color:var(--muted);font-size:11px">결과 검증 · 최종 답변 생성</span>
|
|
441
446
|
</div>
|
|
442
447
|
<select id="pipeline-reviewing-select" class="pipeline-select">
|
|
@@ -501,7 +506,7 @@
|
|
|
501
506
|
<button class="admin-action" onclick="sendFileToChat()" style="flex:1;background:rgba(111,66,232,0.12);border-color:rgba(111,66,232,0.28)">
|
|
502
507
|
<i class="ti ti-send"></i> AI에게 보내기
|
|
503
508
|
</button>
|
|
504
|
-
<button class="status-btn" onclick="closeFileEditor();
|
|
509
|
+
<button class="status-btn" onclick="closeFileEditor();showModalLayer('local-browser-overlay')" style="flex:1">
|
|
505
510
|
<i class="ti ti-arrow-left"></i> 탐색기로
|
|
506
511
|
</button>
|
|
507
512
|
</div>
|
|
@@ -736,13 +741,13 @@
|
|
|
736
741
|
<div id="cu-screenshot-meta" style="font-size:11px;color:var(--faint);text-align:center"></div>
|
|
737
742
|
</div>
|
|
738
743
|
<!-- Controls panel -->
|
|
739
|
-
<div style="
|
|
744
|
+
<div class="cu-controls-panel" style="padding:14px;display:flex;flex-direction:column;gap:12px;overflow-y:auto">
|
|
740
745
|
<!-- Agent task input -->
|
|
741
746
|
<div>
|
|
742
747
|
<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">AI 자동 작업</div>
|
|
743
748
|
<textarea id="cu-task-input" placeholder="예: Safari 열고 apple.com 접속해줘 예: 현재 화면에서 버튼 찾아서 클릭해줘" style="width:100%;background:var(--surface-3);border:1px solid var(--border);border-radius:8px;padding:10px;color:var(--text);font-size:13px;resize:vertical;min-height:80px;font-family:inherit;outline:none"></textarea>
|
|
744
749
|
<div style="display:flex;gap:6px;margin-top:6px">
|
|
745
|
-
<button id="cu-run-btn" onclick="cuRunAgent()" style="flex:1;background:
|
|
750
|
+
<button id="cu-run-btn" onclick="cuRunAgent()" style="flex:1;background:var(--accent);border:none;color:#fff;padding:8px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:5px"><i class="ti ti-player-play"></i> 실행</button>
|
|
746
751
|
<button id="cu-stop-btn" onclick="cuStopAgent()" style="background:rgba(248,113,113,0.12);border:1px solid rgba(248,113,113,0.25);color:var(--danger);padding:8px 14px;border-radius:8px;font-size:13px;cursor:pointer;display:none"><i class="ti ti-player-stop"></i></button>
|
|
747
752
|
</div>
|
|
748
753
|
</div>
|
|
@@ -774,7 +779,7 @@
|
|
|
774
779
|
<div id="cu-log" style="flex:1;background:var(--surface-3);border:1px solid var(--border);border-radius:8px;padding:8px;font-size:11px;font-family:monospace;color:var(--muted);overflow-y:auto;max-height:200px;min-height:80px"></div>
|
|
775
780
|
</div>
|
|
776
781
|
<!-- Permission note -->
|
|
777
|
-
<div style="background:rgba(240,168,50,.07);border:1px solid rgba(240,168,50,.2);border-radius:8px;padding:9px 11px;font-size:11px;color:
|
|
782
|
+
<div style="background:rgba(240,168,50,.07);border:1px solid rgba(240,168,50,.2);border-radius:8px;padding:9px 11px;font-size:11px;color:var(--warning)">
|
|
778
783
|
<i class="ti ti-alert-triangle"></i> macOS 손쉬운 사용 권한 필요<br>
|
|
779
784
|
<span style="opacity:.7">시스템 설정 → 개인 정보 보호 → 손쉬운 사용</span>
|
|
780
785
|
</div>
|
|
@@ -833,7 +838,7 @@
|
|
|
833
838
|
</div>
|
|
834
839
|
|
|
835
840
|
|
|
836
|
-
<script src="/static/scripts/chat.js?v=0.
|
|
841
|
+
<script src="/static/scripts/chat.js?v=3.0.0"></script>
|
|
837
842
|
</body>
|
|
838
843
|
|
|
839
844
|
</html>
|