ltcai 6.4.0 → 6.6.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/README.md +31 -28
- package/docs/CHANGELOG.md +62 -0
- package/frontend/openapi.json +65 -1
- package/frontend/src/api/client.ts +2 -0
- package/frontend/src/api/openapi.ts +86 -0
- package/frontend/src/features/brain/BrainComposer.tsx +20 -1
- package/frontend/src/features/brain/BrainConversation.tsx +48 -4
- package/frontend/src/features/brain/BrainHome.tsx +61 -1
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +8 -1
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +76 -1
- package/frontend/src/features/brain/brainData.ts +125 -1
- package/frontend/src/features/brain/types.ts +52 -0
- package/frontend/src/i18n.ts +66 -0
- package/frontend/src/styles.css +261 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/ingest.py +41 -6
- package/lattice_brain/ingestion.py +1 -0
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/knowledge_graph.py +11 -0
- package/latticeai/api/memory.py +20 -0
- package/latticeai/app_factory.py +2 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/chat_wiring.py +2 -0
- package/latticeai/runtime/router_registration.py +3 -0
- package/latticeai/services/memory_service.py +188 -2
- package/latticeai/services/router_context.py +1 -0
- package/latticeai/services/upload_service.py +12 -0
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/{index-Div5vMlq.css → index-C8toxDpv.css} +1 -1
- package/static/app/assets/index-CbvcAQ6B.js +16 -0
- package/static/app/assets/index-CbvcAQ6B.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-t1jx1BR9.js +0 -16
- package/static/app/assets/index-t1jx1BR9.js.map +0 -1
package/frontend/src/styles.css
CHANGED
|
@@ -1421,7 +1421,150 @@ body {
|
|
|
1421
1421
|
line-height: 1.35;
|
|
1422
1422
|
}
|
|
1423
1423
|
|
|
1424
|
+
.brain-readiness-strip {
|
|
1425
|
+
display: grid;
|
|
1426
|
+
grid-template-columns: minmax(0, 1fr) minmax(5.6rem, 0.6fr) auto;
|
|
1427
|
+
align-items: center;
|
|
1428
|
+
gap: 0.7rem;
|
|
1429
|
+
margin-top: 0.75rem;
|
|
1430
|
+
border: 1px solid hsl(var(--brain-core) / 0.28);
|
|
1431
|
+
border-radius: 8px;
|
|
1432
|
+
background: hsl(var(--brain-core) / 0.06);
|
|
1433
|
+
padding: 0.66rem;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.brain-readiness-strip span {
|
|
1437
|
+
display: block;
|
|
1438
|
+
color: hsl(var(--fg-muted));
|
|
1439
|
+
font-size: 0.64rem;
|
|
1440
|
+
font-weight: 820;
|
|
1441
|
+
text-transform: uppercase;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.brain-readiness-strip strong {
|
|
1445
|
+
display: block;
|
|
1446
|
+
margin-top: 0.12rem;
|
|
1447
|
+
font-size: 0.82rem;
|
|
1448
|
+
line-height: 1.2;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.brain-readiness-strip button {
|
|
1452
|
+
min-height: 2rem;
|
|
1453
|
+
border: 1px solid hsl(var(--brain-core) / 0.34);
|
|
1454
|
+
border-radius: 999px;
|
|
1455
|
+
background: hsl(var(--surface) / 0.78);
|
|
1456
|
+
color: hsl(var(--fg));
|
|
1457
|
+
padding: 0 0.72rem;
|
|
1458
|
+
font-size: 0.76rem;
|
|
1459
|
+
font-weight: 760;
|
|
1460
|
+
white-space: nowrap;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.brain-readiness-meter {
|
|
1464
|
+
height: 0.42rem;
|
|
1465
|
+
overflow: hidden;
|
|
1466
|
+
border-radius: 999px;
|
|
1467
|
+
background: hsl(var(--bg) / 0.55);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.brain-readiness-meter i {
|
|
1471
|
+
display: block;
|
|
1472
|
+
height: 100%;
|
|
1473
|
+
border-radius: inherit;
|
|
1474
|
+
background: linear-gradient(90deg, hsl(var(--memory)), hsl(var(--knowledge)), hsl(var(--brain-core)));
|
|
1475
|
+
transition: width 260ms ease;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
.brain-proof-strip {
|
|
1479
|
+
display: grid;
|
|
1480
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1481
|
+
gap: 0.58rem;
|
|
1482
|
+
margin-top: 0.62rem;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.brain-proof-point {
|
|
1486
|
+
display: grid;
|
|
1487
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1488
|
+
align-items: center;
|
|
1489
|
+
gap: 0.12rem 0.44rem;
|
|
1490
|
+
min-height: 4.1rem;
|
|
1491
|
+
border: 1px solid hsl(var(--border) / 0.5);
|
|
1492
|
+
border-radius: 8px;
|
|
1493
|
+
background: hsl(var(--surface) / 0.56);
|
|
1494
|
+
padding: 0.58rem;
|
|
1495
|
+
color: hsl(var(--fg));
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
.brain-proof-point svg {
|
|
1499
|
+
grid-row: span 2;
|
|
1500
|
+
color: hsl(var(--knowledge));
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.brain-proof-point span {
|
|
1504
|
+
overflow: hidden;
|
|
1505
|
+
color: hsl(var(--fg-muted));
|
|
1506
|
+
font-size: 0.62rem;
|
|
1507
|
+
font-weight: 820;
|
|
1508
|
+
text-overflow: ellipsis;
|
|
1509
|
+
text-transform: uppercase;
|
|
1510
|
+
white-space: nowrap;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
.brain-proof-point strong {
|
|
1514
|
+
overflow: hidden;
|
|
1515
|
+
font-size: 0.75rem;
|
|
1516
|
+
line-height: 1.2;
|
|
1517
|
+
text-overflow: ellipsis;
|
|
1518
|
+
white-space: nowrap;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.brain-recall-proof {
|
|
1522
|
+
display: grid;
|
|
1523
|
+
width: 100%;
|
|
1524
|
+
gap: 0.2rem;
|
|
1525
|
+
margin-top: 0.62rem;
|
|
1526
|
+
border: 1px solid hsl(var(--memory) / 0.34);
|
|
1527
|
+
border-radius: 8px;
|
|
1528
|
+
background: hsl(var(--memory) / 0.07);
|
|
1529
|
+
color: hsl(var(--fg));
|
|
1530
|
+
padding: 0.68rem;
|
|
1531
|
+
text-align: left;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.brain-recall-proof.is-empty {
|
|
1535
|
+
border-color: hsl(var(--border) / 0.5);
|
|
1536
|
+
background: hsl(var(--surface) / 0.5);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.brain-recall-proof span {
|
|
1540
|
+
color: hsl(var(--fg-muted));
|
|
1541
|
+
font-size: 0.64rem;
|
|
1542
|
+
font-weight: 820;
|
|
1543
|
+
text-transform: uppercase;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
.brain-recall-proof strong,
|
|
1547
|
+
.brain-recall-proof small {
|
|
1548
|
+
display: block;
|
|
1549
|
+
overflow: hidden;
|
|
1550
|
+
text-overflow: ellipsis;
|
|
1551
|
+
white-space: nowrap;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.brain-recall-proof strong {
|
|
1555
|
+
font-size: 0.86rem;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.brain-recall-proof small {
|
|
1559
|
+
color: hsl(var(--fg-muted));
|
|
1560
|
+
font-size: 0.76rem;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1424
1563
|
.brain-save-feedback {
|
|
1564
|
+
display: grid;
|
|
1565
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1566
|
+
align-items: center;
|
|
1567
|
+
gap: 0.22rem 0.48rem;
|
|
1425
1568
|
border: 1px solid hsl(var(--memory) / 0.38);
|
|
1426
1569
|
border-radius: 8px;
|
|
1427
1570
|
background: hsl(var(--memory) / 0.08);
|
|
@@ -1431,6 +1574,13 @@ body {
|
|
|
1431
1574
|
font-weight: 720;
|
|
1432
1575
|
}
|
|
1433
1576
|
|
|
1577
|
+
.brain-save-feedback small {
|
|
1578
|
+
grid-column: 2;
|
|
1579
|
+
color: hsl(var(--fg-muted));
|
|
1580
|
+
font-size: 0.72rem;
|
|
1581
|
+
font-weight: 560;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1434
1584
|
.brain-message {
|
|
1435
1585
|
max-width: 78%;
|
|
1436
1586
|
margin-bottom: 1.1rem;
|
|
@@ -1911,6 +2061,59 @@ body {
|
|
|
1911
2061
|
outline: none;
|
|
1912
2062
|
}
|
|
1913
2063
|
|
|
2064
|
+
.brain-depth-rail {
|
|
2065
|
+
position: absolute;
|
|
2066
|
+
left: 50%;
|
|
2067
|
+
top: 0.85rem;
|
|
2068
|
+
z-index: 6;
|
|
2069
|
+
display: flex;
|
|
2070
|
+
width: min(92vw, 35rem);
|
|
2071
|
+
transform: translateX(-50%);
|
|
2072
|
+
gap: 0.28rem;
|
|
2073
|
+
border: 1px solid hsl(var(--border) / 0.48);
|
|
2074
|
+
border-radius: 999px;
|
|
2075
|
+
background: hsl(var(--surface-glass));
|
|
2076
|
+
padding: 0.32rem;
|
|
2077
|
+
box-shadow: 0 12px 30px hsl(200 30% 3% / 0.18);
|
|
2078
|
+
backdrop-filter: blur(18px);
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
.brain-depth-rail button {
|
|
2082
|
+
display: inline-grid;
|
|
2083
|
+
min-width: 0;
|
|
2084
|
+
flex: 1 1 0;
|
|
2085
|
+
align-items: center;
|
|
2086
|
+
gap: 0.1rem;
|
|
2087
|
+
border: 0;
|
|
2088
|
+
border-radius: 999px;
|
|
2089
|
+
background: transparent;
|
|
2090
|
+
color: hsl(var(--fg-muted));
|
|
2091
|
+
padding: 0.36rem 0.42rem;
|
|
2092
|
+
text-align: center;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
.brain-depth-rail span {
|
|
2096
|
+
font-size: 0.58rem;
|
|
2097
|
+
font-weight: 840;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
.brain-depth-rail strong {
|
|
2101
|
+
overflow: hidden;
|
|
2102
|
+
text-overflow: ellipsis;
|
|
2103
|
+
white-space: nowrap;
|
|
2104
|
+
font-size: 0.66rem;
|
|
2105
|
+
line-height: 1.05;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
.brain-depth-rail button.is-revealed {
|
|
2109
|
+
background: hsl(var(--brain-core) / 0.1);
|
|
2110
|
+
color: hsl(var(--fg));
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
.brain-depth-rail button[aria-current="step"] {
|
|
2114
|
+
box-shadow: 0 0 0 1px hsl(var(--brain-core) / 0.38) inset;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
1914
2117
|
.memory-fragment,
|
|
1915
2118
|
.concept-signal,
|
|
1916
2119
|
.graph-node {
|
|
@@ -2648,6 +2851,7 @@ body {
|
|
|
2648
2851
|
font-size: 0.88rem;
|
|
2649
2852
|
}
|
|
2650
2853
|
|
|
2854
|
+
.brain-document-input,
|
|
2651
2855
|
.brain-image-input {
|
|
2652
2856
|
display: inline-flex;
|
|
2653
2857
|
cursor: pointer;
|
|
@@ -2660,6 +2864,19 @@ body {
|
|
|
2660
2864
|
font-size: 0.78rem;
|
|
2661
2865
|
}
|
|
2662
2866
|
|
|
2867
|
+
.brain-document-input {
|
|
2868
|
+
border-color: hsl(var(--memory) / 0.42);
|
|
2869
|
+
background: hsl(var(--memory) / 0.08);
|
|
2870
|
+
color: hsl(var(--fg));
|
|
2871
|
+
font-weight: 760;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
.brain-document-input.is-disabled,
|
|
2875
|
+
.mind-empty-upload.is-disabled {
|
|
2876
|
+
cursor: wait;
|
|
2877
|
+
opacity: 0.68;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2663
2880
|
.mind-empty-kicker {
|
|
2664
2881
|
margin-bottom: 0.4rem;
|
|
2665
2882
|
font-size: 0.72rem;
|
|
@@ -2746,6 +2963,37 @@ body {
|
|
|
2746
2963
|
line-height: 1.45;
|
|
2747
2964
|
}
|
|
2748
2965
|
|
|
2966
|
+
.mind-empty-upload {
|
|
2967
|
+
display: inline-flex;
|
|
2968
|
+
cursor: pointer;
|
|
2969
|
+
align-items: center;
|
|
2970
|
+
justify-content: center;
|
|
2971
|
+
gap: 0.42rem;
|
|
2972
|
+
margin-top: 1rem;
|
|
2973
|
+
border: 1px solid hsl(var(--memory) / 0.46);
|
|
2974
|
+
border-radius: 999px;
|
|
2975
|
+
background: hsl(var(--memory) / 0.1);
|
|
2976
|
+
color: hsl(var(--fg));
|
|
2977
|
+
padding: 0.55rem 0.9rem;
|
|
2978
|
+
font-size: 0.82rem;
|
|
2979
|
+
font-weight: 820;
|
|
2980
|
+
transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
.mind-empty-upload:hover,
|
|
2984
|
+
.mind-empty-upload:focus-within {
|
|
2985
|
+
border-color: hsl(var(--memory) / 0.72);
|
|
2986
|
+
background: hsl(var(--memory) / 0.16);
|
|
2987
|
+
transform: translateY(-1px);
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
.mind-empty-upload-hint {
|
|
2991
|
+
display: block;
|
|
2992
|
+
margin-top: 0.42rem;
|
|
2993
|
+
color: hsl(var(--fg-muted));
|
|
2994
|
+
font-size: 0.72rem;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2749
2997
|
.mind-empty-prompts {
|
|
2750
2998
|
display: flex;
|
|
2751
2999
|
flex-wrap: wrap;
|
|
@@ -2833,9 +3081,22 @@ body {
|
|
|
2833
3081
|
.brain-depth-actions button {
|
|
2834
3082
|
flex: 0 0 auto;
|
|
2835
3083
|
}
|
|
3084
|
+
.brain-depth-rail {
|
|
3085
|
+
top: 0.35rem;
|
|
3086
|
+
width: min(100vw - 1rem, 25rem);
|
|
3087
|
+
}
|
|
3088
|
+
.brain-depth-rail strong {
|
|
3089
|
+
display: none;
|
|
3090
|
+
}
|
|
3091
|
+
.brain-readiness-strip {
|
|
3092
|
+
grid-template-columns: 1fr;
|
|
3093
|
+
}
|
|
2836
3094
|
.brain-overview-grid {
|
|
2837
3095
|
grid-template-columns: 1fr;
|
|
2838
3096
|
}
|
|
3097
|
+
.brain-proof-strip {
|
|
3098
|
+
grid-template-columns: 1fr;
|
|
3099
|
+
}
|
|
2839
3100
|
.brain-overview-head {
|
|
2840
3101
|
align-items: flex-start;
|
|
2841
3102
|
flex-direction: column;
|
|
@@ -15,6 +15,7 @@ class KnowledgeGraphIngestMixin:
|
|
|
15
15
|
user_nickname: Optional[str] = None,
|
|
16
16
|
source: Optional[str] = None,
|
|
17
17
|
conversation_id: Optional[str] = None,
|
|
18
|
+
workspace_id: Optional[str] = None,
|
|
18
19
|
raw: Optional[Dict[str, Any]] = None,
|
|
19
20
|
) -> Dict[str, Any]:
|
|
20
21
|
content = str(content or "")
|
|
@@ -28,6 +29,7 @@ class KnowledgeGraphIngestMixin:
|
|
|
28
29
|
"role": role,
|
|
29
30
|
"source": source,
|
|
30
31
|
"conversation_id": conversation_id,
|
|
32
|
+
"workspace_id": workspace_id,
|
|
31
33
|
"user_email": user_email,
|
|
32
34
|
"user_nickname": user_nickname,
|
|
33
35
|
"chars": len(content),
|
|
@@ -47,7 +49,9 @@ class KnowledgeGraphIngestMixin:
|
|
|
47
49
|
"Chat",
|
|
48
50
|
chat_title,
|
|
49
51
|
summary=_clean_text(content)[:400],
|
|
50
|
-
metadata={"source": source, "conversation_id": conversation_id},
|
|
52
|
+
metadata={"source": source, "conversation_id": conversation_id, "workspace_id": workspace_id},
|
|
53
|
+
owner=user_email,
|
|
54
|
+
workspace_id=workspace_id,
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
# ── 2. Person node (점: 명사 — 사람) ─────────────────────────────
|
|
@@ -61,6 +65,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
61
65
|
"Person",
|
|
62
66
|
user_nickname or user_email or "Unknown",
|
|
63
67
|
metadata={"email": user_email, "nickname": user_nickname},
|
|
68
|
+
owner=user_email,
|
|
69
|
+
workspace_id=workspace_id,
|
|
64
70
|
)
|
|
65
71
|
# 선: 동사 — Person이 Chat을 "작성함"
|
|
66
72
|
self._upsert_edge(
|
|
@@ -81,6 +87,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
81
87
|
summary=_clean_text(content)[:500],
|
|
82
88
|
metadata=metadata,
|
|
83
89
|
raw=raw or metadata,
|
|
90
|
+
owner=user_email,
|
|
91
|
+
workspace_id=workspace_id,
|
|
84
92
|
)
|
|
85
93
|
# 선: Chat이 메시지를 "포함함"
|
|
86
94
|
self._upsert_edge(
|
|
@@ -97,6 +105,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
97
105
|
f"chunk {index + 1}",
|
|
98
106
|
summary=chunk[:500],
|
|
99
107
|
metadata={"index": index, "source_node": node_id},
|
|
108
|
+
owner=user_email,
|
|
109
|
+
workspace_id=workspace_id,
|
|
100
110
|
)
|
|
101
111
|
self._upsert_chunk(
|
|
102
112
|
conn,
|
|
@@ -118,7 +128,9 @@ class KnowledgeGraphIngestMixin:
|
|
|
118
128
|
cid,
|
|
119
129
|
node_t,
|
|
120
130
|
concept,
|
|
121
|
-
metadata={"auto_extracted": True, "source": source},
|
|
131
|
+
metadata={"auto_extracted": True, "source": source, "workspace_id": workspace_id},
|
|
132
|
+
owner=user_email,
|
|
133
|
+
workspace_id=workspace_id,
|
|
122
134
|
)
|
|
123
135
|
# 선: Chat이 개념을 "언급함"
|
|
124
136
|
self._upsert_edge(
|
|
@@ -155,8 +167,10 @@ class KnowledgeGraphIngestMixin:
|
|
|
155
167
|
sem_type,
|
|
156
168
|
sem_title,
|
|
157
169
|
summary=item["summary"],
|
|
158
|
-
metadata={"auto_extracted": True, "source_node": node_id},
|
|
170
|
+
metadata={"auto_extracted": True, "source_node": node_id, "workspace_id": workspace_id},
|
|
159
171
|
raw=item,
|
|
172
|
+
owner=user_email,
|
|
173
|
+
workspace_id=workspace_id,
|
|
160
174
|
)
|
|
161
175
|
# 선: Chat이 Task/Decision을 "생성함"
|
|
162
176
|
self._upsert_edge(conn, conv_id, sem_id, "생성함", weight=0.9)
|
|
@@ -236,6 +250,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
236
250
|
summary=(text or filename)[:500],
|
|
237
251
|
metadata=metadata,
|
|
238
252
|
raw=metadata,
|
|
253
|
+
owner=owner or uploader,
|
|
254
|
+
workspace_id=workspace_id,
|
|
239
255
|
)
|
|
240
256
|
self._ingest_structure_nodes(conn, file_id, filename, doc_meta)
|
|
241
257
|
|
|
@@ -265,6 +281,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
265
281
|
"Person",
|
|
266
282
|
uploader,
|
|
267
283
|
metadata={"email": uploader},
|
|
284
|
+
owner=uploader,
|
|
285
|
+
workspace_id=workspace_id,
|
|
268
286
|
)
|
|
269
287
|
# 선: 동사 — Person이 Document를 "업로드함"
|
|
270
288
|
self._upsert_edge(conn, person_id, file_id, "업로드함", weight=1.0)
|
|
@@ -272,7 +290,15 @@ class KnowledgeGraphIngestMixin:
|
|
|
272
290
|
# ── Chat 노드와 연결 ──────────────────────────────────────────────
|
|
273
291
|
if conversation_id:
|
|
274
292
|
conv_id = f"conversation:{_slug(conversation_id)}"
|
|
275
|
-
self._upsert_node(
|
|
293
|
+
self._upsert_node(
|
|
294
|
+
conn,
|
|
295
|
+
conv_id,
|
|
296
|
+
"Chat",
|
|
297
|
+
conversation_id,
|
|
298
|
+
metadata={"conversation_id": conversation_id, "workspace_id": workspace_id},
|
|
299
|
+
owner=owner or uploader,
|
|
300
|
+
workspace_id=workspace_id,
|
|
301
|
+
)
|
|
276
302
|
# 선: 동사 — Chat이 Document를 "언급함"
|
|
277
303
|
self._upsert_edge(conn, conv_id, file_id, "언급함", weight=0.8)
|
|
278
304
|
|
|
@@ -286,7 +312,9 @@ class KnowledgeGraphIngestMixin:
|
|
|
286
312
|
"Chunk",
|
|
287
313
|
f"{filename} chunk {index + 1}",
|
|
288
314
|
summary=chunk[:500],
|
|
289
|
-
metadata={"index": index, "source_node": file_id},
|
|
315
|
+
metadata={"index": index, "source_node": file_id, "workspace_id": workspace_id},
|
|
316
|
+
owner=owner or uploader,
|
|
317
|
+
workspace_id=workspace_id,
|
|
290
318
|
)
|
|
291
319
|
self._upsert_chunk(
|
|
292
320
|
conn,
|
|
@@ -308,7 +336,9 @@ class KnowledgeGraphIngestMixin:
|
|
|
308
336
|
cid,
|
|
309
337
|
node_t,
|
|
310
338
|
concept,
|
|
311
|
-
metadata={"auto_extracted": True, "source_file": filename},
|
|
339
|
+
metadata={"auto_extracted": True, "source_file": filename, "workspace_id": workspace_id},
|
|
340
|
+
owner=owner or uploader,
|
|
341
|
+
workspace_id=workspace_id,
|
|
312
342
|
)
|
|
313
343
|
# 선: 동사 — Document가 Concept을 "포함함"
|
|
314
344
|
self._upsert_edge(conn, file_id, cid, "포함함", weight=0.8)
|
|
@@ -342,8 +372,11 @@ class KnowledgeGraphIngestMixin:
|
|
|
342
372
|
"auto_extracted": True,
|
|
343
373
|
"source_node": file_id,
|
|
344
374
|
"filename": filename,
|
|
375
|
+
"workspace_id": workspace_id,
|
|
345
376
|
},
|
|
346
377
|
raw=item,
|
|
378
|
+
owner=owner or uploader,
|
|
379
|
+
workspace_id=workspace_id,
|
|
347
380
|
)
|
|
348
381
|
# 선: Document가 Task/Decision을 "포함함"
|
|
349
382
|
self._upsert_edge(conn, file_id, sem_id, "포함함", weight=0.9)
|
|
@@ -474,6 +507,8 @@ class KnowledgeGraphIngestMixin:
|
|
|
474
507
|
label,
|
|
475
508
|
summary=str(source_uri or title or source_type)[:400],
|
|
476
509
|
metadata=meta,
|
|
510
|
+
owner=meta.get("owner"),
|
|
511
|
+
workspace_id=meta.get("workspace_id"),
|
|
477
512
|
)
|
|
478
513
|
# 선: 콘텐츠 노드가 "이 출처에서 색인됨" (indexed_from → SOURCE)
|
|
479
514
|
self._upsert_edge(
|
|
@@ -267,6 +267,7 @@ class IngestionPipeline:
|
|
|
267
267
|
user_nickname=meta.get("user_nickname"),
|
|
268
268
|
source=meta.get("source") or source_type,
|
|
269
269
|
conversation_id=item.conversation_id,
|
|
270
|
+
workspace_id=item.workspace_id,
|
|
270
271
|
raw=meta.get("raw"),
|
|
271
272
|
)
|
|
272
273
|
# ingest_message reports message/response node ids; normalize the keys
|
|
@@ -19,7 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
from typing import Any, Callable, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
MULTI_AGENT_VERSION = "6.
|
|
22
|
+
MULTI_AGENT_VERSION = "6.6.0"
|
|
23
23
|
|
|
24
24
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
25
25
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
|
@@ -27,6 +27,14 @@ class KnowledgeGraphIngestRequest(BaseModel):
|
|
|
27
27
|
metadata: Optional[Dict[str, Any]] = None
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _workspace_scope_from_request(request: Request) -> Optional[str]:
|
|
31
|
+
header = request.headers.get("X-Workspace-Id")
|
|
32
|
+
if header and header.strip():
|
|
33
|
+
return header.strip()
|
|
34
|
+
query = request.query_params.get("workspace_id")
|
|
35
|
+
return query.strip() if query and query.strip() else None
|
|
36
|
+
|
|
37
|
+
|
|
30
38
|
def _format_context(matches: list, limit: int) -> str:
|
|
31
39
|
"""Mirror ``KnowledgeGraphRetrievalMixin.context_for_query`` formatting for a
|
|
32
40
|
pre-filtered match list, so scoped callers get identical context lines minus
|
|
@@ -178,6 +186,7 @@ def create_knowledge_graph_router(
|
|
|
178
186
|
async def knowledge_graph_ingest(req: KnowledgeGraphIngestRequest, request: Request):
|
|
179
187
|
current_user = require_user(request)
|
|
180
188
|
kg = graph()
|
|
189
|
+
workspace_id = _workspace_scope_from_request(request)
|
|
181
190
|
event_type = (req.type or "").strip().lower()
|
|
182
191
|
if event_type not in {"message", "ai_response", "note"}:
|
|
183
192
|
raise HTTPException(status_code=400, detail="지원하는 type: message, ai_response, note")
|
|
@@ -189,10 +198,12 @@ def create_knowledge_graph_router(
|
|
|
189
198
|
user_nickname=req.user_nickname,
|
|
190
199
|
source=req.source or "mcp",
|
|
191
200
|
conversation_id=req.conversation_id,
|
|
201
|
+
workspace_id=workspace_id,
|
|
192
202
|
raw={
|
|
193
203
|
"type": req.type,
|
|
194
204
|
"title": req.title,
|
|
195
205
|
"content": req.content,
|
|
206
|
+
"workspace_id": workspace_id,
|
|
196
207
|
"metadata": req.metadata or {},
|
|
197
208
|
},
|
|
198
209
|
)
|
package/latticeai/api/memory.py
CHANGED
|
@@ -42,6 +42,7 @@ def create_memory_router(
|
|
|
42
42
|
gate_read: Callable[[Request], Optional[str]],
|
|
43
43
|
gate_write: Callable[[Request], Optional[str]],
|
|
44
44
|
append_audit_event: Callable[..., None],
|
|
45
|
+
active_model_getter: Callable[[], str] | None = None,
|
|
45
46
|
) -> APIRouter:
|
|
46
47
|
router = APIRouter()
|
|
47
48
|
|
|
@@ -51,6 +52,25 @@ def create_memory_router(
|
|
|
51
52
|
scope = gate_read(request)
|
|
52
53
|
return service.manager(user_email=user, workspace_id=scope)
|
|
53
54
|
|
|
55
|
+
@router.get("/api/memory/brain-quality")
|
|
56
|
+
async def brain_quality_summary(request: Request):
|
|
57
|
+
user = require_user(request)
|
|
58
|
+
scope = gate_read(request)
|
|
59
|
+
return service.brain_quality_summary(user_email=user, workspace_id=scope)
|
|
60
|
+
|
|
61
|
+
@router.get("/api/memory/brain-proof")
|
|
62
|
+
async def brain_proof(request: Request, q: str = "", limit: int = 3):
|
|
63
|
+
user = require_user(request)
|
|
64
|
+
scope = gate_read(request)
|
|
65
|
+
active_model = active_model_getter() if active_model_getter else ""
|
|
66
|
+
return service.brain_proof(
|
|
67
|
+
user_email=user,
|
|
68
|
+
workspace_id=scope,
|
|
69
|
+
active_model=active_model,
|
|
70
|
+
recall_query=q,
|
|
71
|
+
limit=limit,
|
|
72
|
+
)
|
|
73
|
+
|
|
54
74
|
@router.get("/api/memory/tiers")
|
|
55
75
|
async def memory_tiers(request: Request):
|
|
56
76
|
require_user(request)
|
package/latticeai/app_factory.py
CHANGED
|
@@ -736,6 +736,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
736
736
|
source_type="chat_message",
|
|
737
737
|
text=message,
|
|
738
738
|
owner=user_email,
|
|
739
|
+
workspace_id=workspace_id,
|
|
739
740
|
conversation_id=conversation_id,
|
|
740
741
|
metadata={
|
|
741
742
|
"role": role,
|
|
@@ -1447,6 +1448,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1447
1448
|
agent_registry=AGENT_REGISTRY,
|
|
1448
1449
|
memory_service=MEMORY_SERVICE,
|
|
1449
1450
|
platform=PLATFORM,
|
|
1451
|
+
active_model_getter=lambda: router.current_model_id or "",
|
|
1450
1452
|
)
|
|
1451
1453
|
register_interaction_routers(
|
|
1452
1454
|
app,
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
WORKSPACE_OS_VERSION = "6.
|
|
22
|
+
WORKSPACE_OS_VERSION = "6.6.0"
|
|
23
23
|
|
|
24
24
|
# Workspace types separate single-user Personal workspaces from shared
|
|
25
25
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|
|
@@ -63,6 +63,7 @@ def build_interaction_contexts(
|
|
|
63
63
|
agent_registry: Any,
|
|
64
64
|
memory_service: Any,
|
|
65
65
|
platform: Any,
|
|
66
|
+
active_model_getter: Any = None,
|
|
66
67
|
) -> tuple[ToolRouterContext, InteractionRouterContext]:
|
|
67
68
|
tool_router_context = ToolRouterContext(
|
|
68
69
|
config=config,
|
|
@@ -101,6 +102,7 @@ def build_interaction_contexts(
|
|
|
101
102
|
agent_registry=agent_registry,
|
|
102
103
|
memory_service=memory_service,
|
|
103
104
|
platform=platform,
|
|
105
|
+
active_model_getter=active_model_getter,
|
|
104
106
|
)
|
|
105
107
|
return tool_router_context, interaction_router_context
|
|
106
108
|
|
|
@@ -451,6 +451,7 @@ def register_interaction_routers(
|
|
|
451
451
|
create_memory_router: Any,
|
|
452
452
|
memory_service: Any = None,
|
|
453
453
|
platform: Any = None,
|
|
454
|
+
active_model_getter: Any = None,
|
|
454
455
|
) -> tuple[Any, ...]:
|
|
455
456
|
"""Register chat/search/tools/hooks/registry/memory routes in order."""
|
|
456
457
|
|
|
@@ -468,6 +469,7 @@ def register_interaction_routers(
|
|
|
468
469
|
agent_registry = interaction_context.agent_registry
|
|
469
470
|
memory_service = interaction_context.memory_service
|
|
470
471
|
platform = interaction_context.platform
|
|
472
|
+
active_model_getter = interaction_context.active_model_getter
|
|
471
473
|
|
|
472
474
|
return register_routers(
|
|
473
475
|
app,
|
|
@@ -521,6 +523,7 @@ def register_interaction_routers(
|
|
|
521
523
|
gate_read=platform.gate_read,
|
|
522
524
|
gate_write=platform.gate_write,
|
|
523
525
|
append_audit_event=append_audit_event,
|
|
526
|
+
active_model_getter=active_model_getter,
|
|
524
527
|
),
|
|
525
528
|
)
|
|
526
529
|
|