ltcai 11.2.0 → 11.5.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 +50 -53
- package/docs/CHANGELOG.md +87 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -2
- package/docs/PERMISSION_MODE.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +2 -2
- package/docs/v11.3.0_PLAN.md +202 -0
- package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +181 -0
- package/docs/v11.5.0_RUST_COMPLETE_PLAN.md +145 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/_kg_common/__init__.py +287 -0
- package/lattice_brain/graph/_kg_common/extraction.py +516 -0
- package/lattice_brain/graph/_kg_common/relations.py +161 -0
- package/lattice_brain/graph/_kg_common/text.py +479 -0
- package/lattice_brain/graph/discovery_index/__init__.py +35 -0
- package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
- package/lattice_brain/graph/discovery_index/extract.py +137 -0
- package/lattice_brain/graph/discovery_index/scan.py +411 -0
- package/lattice_brain/graph/discovery_index/upsert.py +495 -0
- package/lattice_brain/graph/projection/__init__.py +42 -0
- package/lattice_brain/graph/projection/curation.py +500 -0
- package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +15 -477
- package/lattice_brain/graph/retrieval/__init__.py +54 -0
- package/lattice_brain/graph/retrieval/context.py +197 -0
- package/lattice_brain/graph/retrieval/graph_view.py +319 -0
- package/lattice_brain/graph/retrieval/hybrid.py +488 -0
- package/lattice_brain/graph/retrieval/maintenance.py +121 -0
- package/lattice_brain/graph/retrieval/signals.py +95 -0
- package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
- package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
- package/lattice_brain/graph/retrieval_vector/search.py +560 -0
- package/lattice_brain/graph/retrieval_vector/status.py +374 -0
- package/lattice_brain/ingestion/__init__.py +130 -0
- package/lattice_brain/ingestion/_contract.py +90 -0
- package/lattice_brain/ingestion/constants.py +127 -0
- package/lattice_brain/ingestion/folder_scan.py +57 -0
- package/lattice_brain/ingestion/folders.py +258 -0
- package/lattice_brain/ingestion/hashing.py +26 -0
- package/lattice_brain/ingestion/jobs_api.py +107 -0
- package/lattice_brain/ingestion/models.py +80 -0
- package/lattice_brain/ingestion/pipeline.py +486 -0
- package/lattice_brain/ingestion/quality.py +209 -0
- package/lattice_brain/ingestion/routing.py +295 -0
- package/lattice_brain/multimodal/__init__.py +164 -0
- package/lattice_brain/multimodal/audio.py +77 -0
- package/lattice_brain/multimodal/common.py +118 -0
- package/lattice_brain/multimodal/images.py +498 -0
- package/lattice_brain/multimodal/ports.py +169 -0
- package/lattice_brain/multimodal/video.py +410 -0
- package/lattice_brain/portability/__init__.py +90 -0
- package/lattice_brain/portability/_contract.py +42 -0
- package/lattice_brain/portability/backups.py +338 -0
- package/lattice_brain/portability/bundles.py +136 -0
- package/lattice_brain/portability/constants.py +93 -0
- package/lattice_brain/portability/fsops.py +138 -0
- package/lattice_brain/portability/service.py +41 -0
- package/lattice_brain/{portability.py → portability/sharing.py} +44 -677
- package/lattice_brain/runtime/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/chronicle.py +63 -0
- package/latticeai/api/index_jobs.py +145 -0
- package/latticeai/core/agent/__init__.py +93 -0
- package/latticeai/core/agent/_contract.py +79 -0
- package/latticeai/core/agent/context.py +57 -0
- package/latticeai/core/agent/deps.py +125 -0
- package/latticeai/core/agent/execution.py +622 -0
- package/latticeai/core/agent/planning.py +145 -0
- package/latticeai/core/agent/recovery.py +157 -0
- package/latticeai/core/agent/runtime.py +210 -0
- package/latticeai/core/agent/verification.py +231 -0
- package/latticeai/core/embedding_providers/__init__.py +151 -0
- package/latticeai/core/embedding_providers/base.py +199 -0
- package/latticeai/core/embedding_providers/captions.py +162 -0
- package/latticeai/core/embedding_providers/profiles.py +126 -0
- package/latticeai/core/embedding_providers/text.py +350 -0
- package/latticeai/core/embedding_providers/vision.py +352 -0
- package/latticeai/core/file_generation/__init__.py +115 -0
- package/latticeai/core/file_generation/bundles.py +76 -0
- package/latticeai/core/file_generation/extraction.py +154 -0
- package/latticeai/core/file_generation/inference.py +235 -0
- package/latticeai/core/file_generation/orchestration.py +152 -0
- package/latticeai/core/file_generation/prompting.py +117 -0
- package/latticeai/core/file_generation/repair.py +114 -0
- package/latticeai/core/file_generation/sanitize.py +61 -0
- package/latticeai/core/file_generation/validation.py +201 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +14 -0
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/integrations/telegram_bot/__init__.py +123 -0
- package/latticeai/integrations/telegram_bot/__main__.py +17 -0
- package/latticeai/integrations/telegram_bot/config.py +86 -0
- package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
- package/latticeai/integrations/telegram_bot/flows.py +478 -0
- package/latticeai/integrations/telegram_bot/helpers.py +322 -0
- package/latticeai/integrations/telegram_bot/screens.py +394 -0
- package/latticeai/models/router/__init__.py +88 -0
- package/latticeai/models/router/_contract.py +66 -0
- package/latticeai/models/router/branding.py +56 -0
- package/latticeai/models/router/catalog.py +69 -0
- package/latticeai/models/router/documents.py +199 -0
- package/latticeai/models/router/errors.py +37 -0
- package/latticeai/models/router/generation.py +258 -0
- package/latticeai/models/router/loading.py +291 -0
- package/latticeai/models/router/local_models.py +85 -0
- package/latticeai/models/router/registry.py +147 -0
- package/latticeai/runtime/build_phases/__init__.py +82 -0
- package/latticeai/runtime/build_phases/features.py +421 -0
- package/latticeai/runtime/build_phases/foundation.py +555 -0
- package/latticeai/runtime/build_phases/web.py +492 -0
- package/latticeai/runtime/runtime_context.py +1 -0
- package/latticeai/services/architecture_readiness.py +48 -19
- package/latticeai/services/brain_intelligence/__init__.py +58 -0
- package/latticeai/services/brain_intelligence/_contract.py +71 -0
- package/latticeai/services/brain_intelligence/consistency.py +193 -0
- package/latticeai/services/brain_intelligence/constants.py +47 -0
- package/latticeai/services/brain_intelligence/digest.py +258 -0
- package/latticeai/services/brain_intelligence/health.py +331 -0
- package/latticeai/services/brain_intelligence/proposals.py +264 -0
- package/latticeai/services/brain_intelligence/sampling.py +84 -0
- package/latticeai/services/brain_intelligence/service.py +48 -0
- package/latticeai/services/chronicle.py +557 -0
- package/latticeai/services/memory_service/__init__.py +52 -0
- package/latticeai/services/memory_service/_contract.py +100 -0
- package/latticeai/services/memory_service/brief.py +431 -0
- package/latticeai/services/memory_service/constants.py +57 -0
- package/latticeai/services/memory_service/maintenance.py +138 -0
- package/latticeai/services/memory_service/manager.py +186 -0
- package/latticeai/services/memory_service/proof.py +136 -0
- package/latticeai/services/memory_service/recall.py +225 -0
- package/latticeai/services/memory_service/service.py +48 -0
- package/latticeai/services/memory_service/stores.py +110 -0
- package/latticeai/services/model_runtime/__init__.py +322 -0
- package/latticeai/services/model_runtime/cloud.py +87 -0
- package/latticeai/services/model_runtime/download.py +282 -0
- package/latticeai/services/model_runtime/engines.py +341 -0
- package/latticeai/services/model_runtime/loading.py +178 -0
- package/latticeai/services/model_runtime/service.py +129 -0
- package/latticeai/services/model_runtime/state.py +131 -0
- package/latticeai/services/model_runtime/status.py +255 -0
- package/latticeai/services/product_readiness.py +15 -7
- package/latticeai/setup/wizard/__init__.py +126 -0
- package/latticeai/setup/wizard/catalog.py +172 -0
- package/latticeai/setup/wizard/detect.py +323 -0
- package/latticeai/setup/wizard/install.py +348 -0
- package/latticeai/setup/wizard/paths.py +168 -0
- package/latticeai/setup/wizard/plans.py +74 -0
- package/latticeai/setup/wizard/recommend.py +320 -0
- package/package.json +6 -2
- package/scripts/bump_version.py +14 -0
- package/scripts/capture_release_evidence.mjs +33 -21
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_i18n_namespace_coverage.mjs +41 -4
- package/scripts/check_max_file_lines.mjs +102 -0
- package/scripts/check_release_evidence_bound.mjs +30 -15
- package/scripts/check_screenshot_pixel_delta.py +34 -4
- package/scripts/check_server_i18n.mjs +2 -0
- package/scripts/chunking_parity_corpus.py +449 -0
- package/scripts/generate_agent_parity_fixtures.py +752 -0
- package/scripts/generate_chunking_parity_fixtures.py +259 -0
- package/scripts/generate_rust_parity_fixtures.py +997 -0
- package/scripts/lib/mock_server_fingerprint.mjs +94 -0
- package/scripts/release_screen_claims.json +42 -2
- package/src-tauri/Cargo.lock +404 -3
- package/src-tauri/Cargo.toml +13 -1
- package/src-tauri/src/backend.rs +460 -0
- package/src-tauri/src/folder.rs +33 -0
- package/src-tauri/src/main.rs +109 -399
- package/src-tauri/src/topology.rs +356 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +41 -37
- package/static/app/assets/Act-CWnxSCgN.js +1 -0
- package/static/app/assets/AdminConsole-BEQYU6kF.js +1 -0
- package/static/app/assets/{Brain-tuhI4sOC.js → Brain-DWu1BhFg.js} +2 -2
- package/static/app/assets/BrainHome-95Hilr9R.js +2 -0
- package/static/app/assets/BrainSignals-QdeqCpAF.js +1 -0
- package/static/app/assets/Capture-BHpCxnzb.js +1 -0
- package/static/app/assets/Chronicle-B4xYKoed.js +1 -0
- package/static/app/assets/CommandPalette-BVXnttSz.js +1 -0
- package/static/app/assets/Library-DgYcHome.js +1 -0
- package/static/app/assets/{LivingBrain-DBwhto14.js → LivingBrain-CrJLDbf7.js} +1 -1
- package/static/app/assets/ProductFlow-DFlScKoJ.js +1 -0
- package/static/app/assets/ReviewCard-Cy5f48Pj.js +3 -0
- package/static/app/assets/System-NF8IfhTa.js +1 -0
- package/static/app/assets/arrow-left-DwkSYrjR.js +1 -0
- package/static/app/assets/{bot-Cia42c2h.js → bot-CucuhLhm.js} +1 -1
- package/static/app/assets/brain-BBnSryW_.js +1 -0
- package/static/app/assets/{button-2j2Ijzgq.js → button-C2GUj2Ai.js} +1 -1
- package/static/app/assets/circle-check-CxOVPwYq.js +1 -0
- package/static/app/assets/{circle-pause-BEFeWpVW.js → circle-pause-CbkWzBmG.js} +1 -1
- package/static/app/assets/{circle-play-ujXMcHxl.js → circle-play-7lEaqHdJ.js} +1 -1
- package/static/app/assets/{cpu-k4awryFq.js → cpu-DAlCXlIy.js} +1 -1
- package/static/app/assets/{download-DFbLJ_ig.js → download-RNhuuJwh.js} +1 -1
- package/static/app/assets/{folder-open-7y_b6xkM.js → folder-open-CLW4odzM.js} +1 -1
- package/static/app/assets/{hard-drive-Bidh02Kr.js → hard-drive-NKEiDIAJ.js} +1 -1
- package/static/app/assets/{index-DwDl9-8Y.css → index-BLPb5lmE.css} +1 -1
- package/static/app/assets/index-DMurvUuR.js +10 -0
- package/static/app/assets/input-D2UhPC1X.js +1 -0
- package/static/app/assets/link-2-6amKbP_P.js +1 -0
- package/static/app/assets/{permissionCopy-Bpb83Hx9.js → permissionCopy-Cu9TZtdR.js} +1 -1
- package/static/app/assets/primitives-gPsccucr.js +1 -0
- package/static/app/assets/search-Cj_TKk_2.js +1 -0
- package/static/app/assets/{share-2-BH1M-WNi.js → share-2-Bau7KkPq.js} +1 -1
- package/static/app/assets/{shield-alert-BlKdBXcG.js → shield-alert-BufNYypi.js} +1 -1
- package/static/app/assets/{textarea-CCWbUfFB.js → textarea-BQnVWhYs.js} +1 -1
- package/static/app/assets/{useFocusTrap-YdHQ7pJ1.js → useFocusTrap-B3_w60si.js} +1 -1
- package/static/app/assets/useMutation-BHhCflT6.js +1 -0
- package/static/app/assets/{useQuery-CXQiwbVT.js → useQuery-rBWfI-5t.js} +1 -1
- package/static/app/assets/utils-V_5-wxr5.js +4 -0
- package/static/app/assets/workspace-K1zjYUHj.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/graph/_kg_common.py +0 -1331
- package/lattice_brain/graph/discovery_index.py +0 -1141
- package/lattice_brain/graph/retrieval.py +0 -1120
- package/lattice_brain/graph/retrieval_vector.py +0 -1293
- package/lattice_brain/ingestion.py +0 -1525
- package/lattice_brain/multimodal.py +0 -1258
- package/latticeai/core/agent.py +0 -1465
- package/latticeai/core/embedding_providers.py +0 -1196
- package/latticeai/core/file_generation.py +0 -1047
- package/latticeai/integrations/telegram_bot.py +0 -1390
- package/latticeai/models/router.py +0 -1007
- package/latticeai/runtime/build_phases.py +0 -1450
- package/latticeai/services/brain_intelligence.py +0 -1083
- package/latticeai/services/memory_service.py +0 -1177
- package/latticeai/services/model_runtime.py +0 -1281
- package/latticeai/setup/wizard.py +0 -1310
- package/static/app/assets/Act-AWf0SAKp.js +0 -1
- package/static/app/assets/AdminConsole-D0u8Tiyj.js +0 -1
- package/static/app/assets/BrainHome-Ts7G_Ila.js +0 -2
- package/static/app/assets/BrainSignals-jMYgQ2Ar.js +0 -1
- package/static/app/assets/Capture-CqOSzyPr.js +0 -1
- package/static/app/assets/CommandPalette-DC0Bzh-I.js +0 -1
- package/static/app/assets/Library-CX-bbhmK.js +0 -1
- package/static/app/assets/ProductFlow-BHA2cfKI.js +0 -1
- package/static/app/assets/ReviewCard-BUhCKRNM.js +0 -3
- package/static/app/assets/System-Bu2t5hn1.js +0 -1
- package/static/app/assets/arrow-left-Dzwa5zRb.js +0 -1
- package/static/app/assets/brain-DJMoqrwx.js +0 -1
- package/static/app/assets/index-BpYkzcVm.js +0 -10
- package/static/app/assets/input-DSlJJxRs.js +0 -1
- package/static/app/assets/primitives-BCx6TvfG.js +0 -1
- package/static/app/assets/search-Cgy8cCFJ.js +0 -1
- package/static/app/assets/utils-zqPZJxdx.js +0 -4
- package/static/app/assets/workspace-DXTihhfU.js +0 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""One local file → its rows and its graph nodes.
|
|
2
|
+
|
|
3
|
+
The computer/drive/folder hierarchy, the ``local_file_index`` row, and the
|
|
4
|
+
file node with its concepts and edges. ``_local_scoped_slug`` lives here
|
|
5
|
+
because both hierarchy and node upsert call it. Moved verbatim out of
|
|
6
|
+
``discovery_index.py`` (v11.3.0 decomposition).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
# ruff: noqa: F403,F405
|
|
14
|
+
from .._kg_common import * # noqa: F403,F401
|
|
15
|
+
|
|
16
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
17
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
18
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .._kg_contract import KnowledgeGraphCore as _Core
|
|
21
|
+
else:
|
|
22
|
+
_Core = object
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _local_scoped_slug(prefix: str, value: str, workspace_id: Optional[str]) -> str:
|
|
26
|
+
"""Preserve legacy IDs while isolating newly workspace-scoped nodes."""
|
|
27
|
+
slug = _slug(value)
|
|
28
|
+
if not workspace_id:
|
|
29
|
+
return f"{prefix}:{slug}"
|
|
30
|
+
scope = _sha256_text(str(workspace_id))[:12]
|
|
31
|
+
return f"{prefix}:{scope}:{slug}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _LocalUpsertMixin(_Core):
|
|
35
|
+
"""Hierarchy + file node/index upserts. Composed into the public mixin."""
|
|
36
|
+
|
|
37
|
+
def _ensure_local_hierarchy(
|
|
38
|
+
self,
|
|
39
|
+
conn: sqlite3.Connection,
|
|
40
|
+
*,
|
|
41
|
+
source_id: str,
|
|
42
|
+
root: Path,
|
|
43
|
+
file_path: Path,
|
|
44
|
+
os_type: str,
|
|
45
|
+
drive_id: str,
|
|
46
|
+
user_email: Optional[str] = None,
|
|
47
|
+
workspace_id: Optional[str] = None,
|
|
48
|
+
) -> str:
|
|
49
|
+
computer_label = platform.node() or "내 컴퓨터"
|
|
50
|
+
computer_id = _local_scoped_slug("computer", computer_label, workspace_id)
|
|
51
|
+
drive_identity = f"{workspace_id}|{os_type}:{drive_id}" if workspace_id else f"{os_type}:{drive_id}"
|
|
52
|
+
drive_node_id = f"drive:{_sha256_text(drive_identity)[:24]}"
|
|
53
|
+
root_folder_id = f"folder:{_sha256_text(f'{source_id}:root')[:24]}"
|
|
54
|
+
self._upsert_node(
|
|
55
|
+
conn,
|
|
56
|
+
computer_id,
|
|
57
|
+
"Computer",
|
|
58
|
+
computer_label,
|
|
59
|
+
metadata={"os_type": os_type, "workspace_id": workspace_id},
|
|
60
|
+
owner=user_email,
|
|
61
|
+
workspace_id=workspace_id,
|
|
62
|
+
)
|
|
63
|
+
self._upsert_node(
|
|
64
|
+
conn,
|
|
65
|
+
drive_node_id,
|
|
66
|
+
"Drive",
|
|
67
|
+
drive_id,
|
|
68
|
+
metadata={"os_type": os_type, "drive_id": drive_id, "workspace_id": workspace_id},
|
|
69
|
+
owner=user_email,
|
|
70
|
+
workspace_id=workspace_id,
|
|
71
|
+
)
|
|
72
|
+
stale_parents = conn.execute(
|
|
73
|
+
"""
|
|
74
|
+
SELECT e.from_node
|
|
75
|
+
FROM edges e
|
|
76
|
+
JOIN nodes n ON n.id=e.from_node
|
|
77
|
+
WHERE e.to_node=? AND n.type='Drive' AND e.from_node<>?
|
|
78
|
+
""",
|
|
79
|
+
(root_folder_id, drive_node_id),
|
|
80
|
+
).fetchall()
|
|
81
|
+
for row in stale_parents:
|
|
82
|
+
conn.execute(
|
|
83
|
+
"DELETE FROM edges WHERE from_node=? AND to_node=?",
|
|
84
|
+
(row["from_node"], root_folder_id),
|
|
85
|
+
)
|
|
86
|
+
conn.execute(
|
|
87
|
+
"DELETE FROM edges_v2 WHERE source=? AND target=?",
|
|
88
|
+
(row["from_node"], root_folder_id),
|
|
89
|
+
)
|
|
90
|
+
self._upsert_edge(
|
|
91
|
+
conn,
|
|
92
|
+
computer_id,
|
|
93
|
+
drive_node_id,
|
|
94
|
+
"포함함",
|
|
95
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
96
|
+
)
|
|
97
|
+
self._upsert_node(
|
|
98
|
+
conn,
|
|
99
|
+
root_folder_id,
|
|
100
|
+
"Folder",
|
|
101
|
+
root.name or str(root),
|
|
102
|
+
summary=str(root),
|
|
103
|
+
metadata={"source_id": source_id, "path": str(root), "root": True, "workspace_id": workspace_id},
|
|
104
|
+
owner=user_email,
|
|
105
|
+
workspace_id=workspace_id,
|
|
106
|
+
)
|
|
107
|
+
self._upsert_edge(
|
|
108
|
+
conn,
|
|
109
|
+
drive_node_id,
|
|
110
|
+
root_folder_id,
|
|
111
|
+
"포함함",
|
|
112
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
relative_parent = file_path.parent.relative_to(root)
|
|
117
|
+
except ValueError:
|
|
118
|
+
relative_parent = Path()
|
|
119
|
+
parent_id = root_folder_id
|
|
120
|
+
current_path = root
|
|
121
|
+
for part in relative_parent.parts:
|
|
122
|
+
current_path = current_path / part
|
|
123
|
+
folder_id = (
|
|
124
|
+
f"folder:{_sha256_text(f'{source_id}:{current_path.as_posix()}')[:24]}"
|
|
125
|
+
)
|
|
126
|
+
self._upsert_node(
|
|
127
|
+
conn,
|
|
128
|
+
folder_id,
|
|
129
|
+
"Folder",
|
|
130
|
+
part,
|
|
131
|
+
summary=str(current_path),
|
|
132
|
+
metadata={
|
|
133
|
+
"source_id": source_id,
|
|
134
|
+
"path": str(current_path),
|
|
135
|
+
"root": False,
|
|
136
|
+
"workspace_id": workspace_id,
|
|
137
|
+
},
|
|
138
|
+
owner=user_email,
|
|
139
|
+
workspace_id=workspace_id,
|
|
140
|
+
)
|
|
141
|
+
self._upsert_edge(
|
|
142
|
+
conn,
|
|
143
|
+
parent_id,
|
|
144
|
+
folder_id,
|
|
145
|
+
"포함함",
|
|
146
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
147
|
+
)
|
|
148
|
+
parent_id = folder_id
|
|
149
|
+
return parent_id
|
|
150
|
+
|
|
151
|
+
def _upsert_local_file_index(
|
|
152
|
+
self,
|
|
153
|
+
conn: sqlite3.Connection,
|
|
154
|
+
*,
|
|
155
|
+
source_id: str,
|
|
156
|
+
root: Path,
|
|
157
|
+
file_path: Path,
|
|
158
|
+
stat: Optional[os.stat_result],
|
|
159
|
+
os_type: str,
|
|
160
|
+
drive_id: str,
|
|
161
|
+
status: str,
|
|
162
|
+
parser_type: str,
|
|
163
|
+
sha256: Optional[str] = None,
|
|
164
|
+
graph_node_id: Optional[str] = None,
|
|
165
|
+
error_message: Optional[str] = None,
|
|
166
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
167
|
+
) -> str:
|
|
168
|
+
try:
|
|
169
|
+
relative_path = file_path.relative_to(root).as_posix()
|
|
170
|
+
except ValueError:
|
|
171
|
+
relative_path = file_path.name
|
|
172
|
+
index_id = f"local-index:{_sha256_text(f'{source_id}:{relative_path}')[:24]}"
|
|
173
|
+
now = _now()
|
|
174
|
+
size = stat.st_size if stat else None
|
|
175
|
+
modified_at = _safe_iso_from_stat_mtime(stat.st_mtime) if stat else ""
|
|
176
|
+
conn.execute(
|
|
177
|
+
"""
|
|
178
|
+
INSERT INTO local_file_index(
|
|
179
|
+
id, source_id, os_type, drive_id, root_path, file_path, relative_path,
|
|
180
|
+
file_name, extension, size_bytes, modified_at, sha256, last_scanned_at,
|
|
181
|
+
last_indexed_at, parser_type, status, error_message, graph_node_id,
|
|
182
|
+
deleted, metadata_json
|
|
183
|
+
)
|
|
184
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
185
|
+
ON CONFLICT(source_id, relative_path) DO UPDATE SET
|
|
186
|
+
os_type=excluded.os_type,
|
|
187
|
+
drive_id=excluded.drive_id,
|
|
188
|
+
root_path=excluded.root_path,
|
|
189
|
+
file_path=excluded.file_path,
|
|
190
|
+
file_name=excluded.file_name,
|
|
191
|
+
extension=excluded.extension,
|
|
192
|
+
size_bytes=excluded.size_bytes,
|
|
193
|
+
modified_at=excluded.modified_at,
|
|
194
|
+
sha256=excluded.sha256,
|
|
195
|
+
last_scanned_at=excluded.last_scanned_at,
|
|
196
|
+
last_indexed_at=excluded.last_indexed_at,
|
|
197
|
+
parser_type=excluded.parser_type,
|
|
198
|
+
status=excluded.status,
|
|
199
|
+
error_message=excluded.error_message,
|
|
200
|
+
graph_node_id=excluded.graph_node_id,
|
|
201
|
+
deleted=excluded.deleted,
|
|
202
|
+
metadata_json=excluded.metadata_json
|
|
203
|
+
""",
|
|
204
|
+
(
|
|
205
|
+
index_id,
|
|
206
|
+
source_id,
|
|
207
|
+
os_type,
|
|
208
|
+
drive_id,
|
|
209
|
+
str(root),
|
|
210
|
+
str(file_path),
|
|
211
|
+
relative_path,
|
|
212
|
+
file_path.name,
|
|
213
|
+
file_path.suffix.lower(),
|
|
214
|
+
size,
|
|
215
|
+
modified_at,
|
|
216
|
+
sha256,
|
|
217
|
+
now,
|
|
218
|
+
now if status == "indexed" else None,
|
|
219
|
+
parser_type,
|
|
220
|
+
status,
|
|
221
|
+
error_message,
|
|
222
|
+
graph_node_id,
|
|
223
|
+
0 if status != "deleted" else 1,
|
|
224
|
+
_json(metadata),
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
return index_id
|
|
228
|
+
|
|
229
|
+
def _upsert_local_file_node(
|
|
230
|
+
self,
|
|
231
|
+
conn: sqlite3.Connection,
|
|
232
|
+
*,
|
|
233
|
+
source_id: str,
|
|
234
|
+
root: Path,
|
|
235
|
+
file_path: Path,
|
|
236
|
+
stat: os.stat_result,
|
|
237
|
+
os_type: str,
|
|
238
|
+
drive_id: str,
|
|
239
|
+
sha256: str,
|
|
240
|
+
category: str,
|
|
241
|
+
parser_type: str,
|
|
242
|
+
text: str,
|
|
243
|
+
parser_meta: Dict[str, Any],
|
|
244
|
+
user_email: Optional[str] = None,
|
|
245
|
+
workspace_id: Optional[str] = None,
|
|
246
|
+
) -> str:
|
|
247
|
+
text = _clean_text(text)
|
|
248
|
+
if not text:
|
|
249
|
+
raise ValueError("텍스트 추출 결과가 비어 있습니다.")
|
|
250
|
+
try:
|
|
251
|
+
relative_path = file_path.relative_to(root).as_posix()
|
|
252
|
+
except ValueError:
|
|
253
|
+
relative_path = file_path.name
|
|
254
|
+
file_node_id = f"local-file:{_sha256_text(f'{source_id}:{relative_path}')[:24]}"
|
|
255
|
+
parent_folder_id = self._ensure_local_hierarchy(
|
|
256
|
+
conn,
|
|
257
|
+
source_id=source_id,
|
|
258
|
+
root=root,
|
|
259
|
+
file_path=file_path,
|
|
260
|
+
os_type=os_type,
|
|
261
|
+
drive_id=drive_id,
|
|
262
|
+
user_email=user_email,
|
|
263
|
+
workspace_id=workspace_id,
|
|
264
|
+
)
|
|
265
|
+
linked_rows = conn.execute(
|
|
266
|
+
"""
|
|
267
|
+
SELECT e.to_node AS id, n.type, n.metadata_json
|
|
268
|
+
FROM edges e
|
|
269
|
+
JOIN nodes n ON n.id=e.to_node
|
|
270
|
+
WHERE e.from_node=?
|
|
271
|
+
""",
|
|
272
|
+
(file_node_id,),
|
|
273
|
+
).fetchall()
|
|
274
|
+
child_ids = []
|
|
275
|
+
auto_candidate_ids = set()
|
|
276
|
+
for row in linked_rows:
|
|
277
|
+
linked_metadata = _safe_loads(row["metadata_json"])
|
|
278
|
+
if row["type"] in {"Chunk", "ImageText", "Section"} or linked_metadata.get("source_node") == file_node_id:
|
|
279
|
+
child_ids.append(row["id"])
|
|
280
|
+
elif linked_metadata.get("auto_extracted") and linked_metadata.get("source") == "local_folder":
|
|
281
|
+
auto_candidate_ids.add(row["id"])
|
|
282
|
+
conn.execute("DELETE FROM chunks WHERE source_node=?", (file_node_id,))
|
|
283
|
+
if child_ids:
|
|
284
|
+
placeholders = ",".join("?" * len(child_ids))
|
|
285
|
+
conn.execute(f"DELETE FROM nodes WHERE id IN ({placeholders})", child_ids)
|
|
286
|
+
self._v2_delete_nodes(conn, child_ids)
|
|
287
|
+
conn.execute("DELETE FROM edges WHERE from_node=?", (file_node_id,))
|
|
288
|
+
self._v2_delete_edges_from(conn, file_node_id)
|
|
289
|
+
removable_auto_ids = set()
|
|
290
|
+
for node_id in auto_candidate_ids:
|
|
291
|
+
remaining_edges = conn.execute(
|
|
292
|
+
"SELECT from_node, to_node FROM edges WHERE from_node=? OR to_node=?",
|
|
293
|
+
(node_id, node_id),
|
|
294
|
+
).fetchall()
|
|
295
|
+
if all(
|
|
296
|
+
row["from_node"] in auto_candidate_ids
|
|
297
|
+
and row["to_node"] in auto_candidate_ids
|
|
298
|
+
for row in remaining_edges
|
|
299
|
+
):
|
|
300
|
+
removable_auto_ids.add(node_id)
|
|
301
|
+
if removable_auto_ids:
|
|
302
|
+
placeholders = ",".join("?" * len(removable_auto_ids))
|
|
303
|
+
params = list(removable_auto_ids)
|
|
304
|
+
conn.execute(
|
|
305
|
+
f"DELETE FROM edges WHERE from_node IN ({placeholders}) OR to_node IN ({placeholders})",
|
|
306
|
+
params * 2,
|
|
307
|
+
)
|
|
308
|
+
conn.execute(f"DELETE FROM nodes WHERE id IN ({placeholders})", params)
|
|
309
|
+
self._v2_delete_nodes(conn, params)
|
|
310
|
+
|
|
311
|
+
metadata = {
|
|
312
|
+
"source": "local_folder",
|
|
313
|
+
"source_id": source_id,
|
|
314
|
+
"root_path": str(root),
|
|
315
|
+
"file_path": str(file_path),
|
|
316
|
+
"relative_path": relative_path,
|
|
317
|
+
"filename": file_path.name,
|
|
318
|
+
"ext": file_path.suffix.lower(),
|
|
319
|
+
"category": category,
|
|
320
|
+
"parser_type": parser_type,
|
|
321
|
+
"bytes": stat.st_size,
|
|
322
|
+
"modified_at": _safe_iso_from_stat_mtime(stat.st_mtime),
|
|
323
|
+
"sha256": sha256,
|
|
324
|
+
"parser": parser_meta,
|
|
325
|
+
"workspace_id": workspace_id,
|
|
326
|
+
}
|
|
327
|
+
self._upsert_node(
|
|
328
|
+
conn,
|
|
329
|
+
file_node_id,
|
|
330
|
+
_node_type_for_category(category),
|
|
331
|
+
file_path.name,
|
|
332
|
+
summary=text[:700],
|
|
333
|
+
metadata=metadata,
|
|
334
|
+
raw=metadata,
|
|
335
|
+
owner=user_email,
|
|
336
|
+
workspace_id=workspace_id,
|
|
337
|
+
)
|
|
338
|
+
self._upsert_edge(
|
|
339
|
+
conn,
|
|
340
|
+
parent_folder_id,
|
|
341
|
+
file_node_id,
|
|
342
|
+
"포함함",
|
|
343
|
+
weight=1.0,
|
|
344
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
345
|
+
)
|
|
346
|
+
self._cleanup_local_graph_orphans(conn, source_id)
|
|
347
|
+
|
|
348
|
+
target_for_concepts = text
|
|
349
|
+
if category == "image" and text:
|
|
350
|
+
image_text_id = f"imagetext:{_sha256_text(f'{file_node_id}:ocr')[:24]}"
|
|
351
|
+
self._upsert_node(
|
|
352
|
+
conn,
|
|
353
|
+
image_text_id,
|
|
354
|
+
"ImageText",
|
|
355
|
+
f"{file_path.name} OCR",
|
|
356
|
+
summary=_clean_text(text)[:700],
|
|
357
|
+
metadata={
|
|
358
|
+
"source_node": file_node_id,
|
|
359
|
+
"source_id": source_id,
|
|
360
|
+
"chars": len(text),
|
|
361
|
+
"workspace_id": workspace_id,
|
|
362
|
+
},
|
|
363
|
+
owner=user_email,
|
|
364
|
+
workspace_id=workspace_id,
|
|
365
|
+
)
|
|
366
|
+
self._upsert_edge(
|
|
367
|
+
conn,
|
|
368
|
+
file_node_id,
|
|
369
|
+
image_text_id,
|
|
370
|
+
"포함함",
|
|
371
|
+
weight=0.8,
|
|
372
|
+
metadata={"source": "ocr", "workspace_id": workspace_id},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Typed chunking by file extension (markdown/code/plain); plain files
|
|
376
|
+
# keep legacy _chunks boundaries and therefore identical chunk ids.
|
|
377
|
+
chunk_strategy = chunk_strategy_for(file_path.name)
|
|
378
|
+
for index, piece in enumerate(typed_chunks(text, strategy=chunk_strategy)):
|
|
379
|
+
chunk = piece["text"]
|
|
380
|
+
chunk_fields = typed_chunk_meta_fields(piece)
|
|
381
|
+
chunk_id = f"chunk:{_sha256_text(f'{file_node_id}:{index}:{chunk}')[:24]}"
|
|
382
|
+
self._upsert_node(
|
|
383
|
+
conn,
|
|
384
|
+
chunk_id,
|
|
385
|
+
"Chunk",
|
|
386
|
+
f"{file_path.name} chunk {index + 1}",
|
|
387
|
+
summary=chunk[:500],
|
|
388
|
+
metadata={
|
|
389
|
+
"index": index,
|
|
390
|
+
"source_node": file_node_id,
|
|
391
|
+
"source_id": source_id,
|
|
392
|
+
"workspace_id": workspace_id,
|
|
393
|
+
**chunk_fields,
|
|
394
|
+
},
|
|
395
|
+
owner=user_email,
|
|
396
|
+
workspace_id=workspace_id,
|
|
397
|
+
)
|
|
398
|
+
self._upsert_chunk(
|
|
399
|
+
conn,
|
|
400
|
+
chunk_id=chunk_id,
|
|
401
|
+
source_node=file_node_id,
|
|
402
|
+
text=chunk,
|
|
403
|
+
metadata={
|
|
404
|
+
"index": index,
|
|
405
|
+
"source_node": file_node_id,
|
|
406
|
+
"source_id": source_id,
|
|
407
|
+
"workspace_id": workspace_id,
|
|
408
|
+
**chunk_fields,
|
|
409
|
+
},
|
|
410
|
+
)
|
|
411
|
+
self._upsert_edge(
|
|
412
|
+
conn,
|
|
413
|
+
file_node_id,
|
|
414
|
+
chunk_id,
|
|
415
|
+
"포함함",
|
|
416
|
+
weight=0.7,
|
|
417
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
concepts = _extract_concepts(target_for_concepts, limit=18)
|
|
421
|
+
concept_ids: Dict[str, str] = {}
|
|
422
|
+
for concept in concepts:
|
|
423
|
+
node_t = _classify_node_type(concept, target_for_concepts)
|
|
424
|
+
concept_id = _local_scoped_slug(node_t.lower(), concept, workspace_id)
|
|
425
|
+
concept_ids[concept.lower()] = concept_id
|
|
426
|
+
self._upsert_node(
|
|
427
|
+
conn,
|
|
428
|
+
concept_id,
|
|
429
|
+
node_t,
|
|
430
|
+
concept,
|
|
431
|
+
metadata={
|
|
432
|
+
"auto_extracted": True,
|
|
433
|
+
"source": "local_folder",
|
|
434
|
+
"source_id": source_id,
|
|
435
|
+
"workspace_id": workspace_id,
|
|
436
|
+
},
|
|
437
|
+
owner=user_email,
|
|
438
|
+
workspace_id=workspace_id,
|
|
439
|
+
)
|
|
440
|
+
self._upsert_edge(
|
|
441
|
+
conn,
|
|
442
|
+
file_node_id,
|
|
443
|
+
concept_id,
|
|
444
|
+
"언급함",
|
|
445
|
+
weight=0.75,
|
|
446
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
for triple in _extract_triples(target_for_concepts, concepts, limit=20):
|
|
450
|
+
subj_id = concept_ids.get(triple["subject"].lower())
|
|
451
|
+
obj_id = concept_ids.get(triple["object"].lower())
|
|
452
|
+
if subj_id and obj_id and subj_id != obj_id:
|
|
453
|
+
self._upsert_edge(
|
|
454
|
+
conn,
|
|
455
|
+
subj_id,
|
|
456
|
+
obj_id,
|
|
457
|
+
triple["relation"],
|
|
458
|
+
weight=0.9,
|
|
459
|
+
metadata={
|
|
460
|
+
"context": triple.get("context", "")[:240],
|
|
461
|
+
"source_id": source_id,
|
|
462
|
+
"workspace_id": workspace_id,
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
for item in _semantic_items(target_for_concepts):
|
|
467
|
+
sem_type = item["type"]
|
|
468
|
+
sem_title = item["title"]
|
|
469
|
+
sem_id = f"{sem_type.lower()}:{_sha256_text(f'{file_node_id}:{sem_type}:{sem_title}')[:24]}"
|
|
470
|
+
self._upsert_node(
|
|
471
|
+
conn,
|
|
472
|
+
sem_id,
|
|
473
|
+
sem_type,
|
|
474
|
+
sem_title,
|
|
475
|
+
summary=item["summary"],
|
|
476
|
+
metadata={
|
|
477
|
+
"auto_extracted": True,
|
|
478
|
+
"source_node": file_node_id,
|
|
479
|
+
"filename": file_path.name,
|
|
480
|
+
"workspace_id": workspace_id,
|
|
481
|
+
},
|
|
482
|
+
raw=item,
|
|
483
|
+
owner=user_email,
|
|
484
|
+
workspace_id=workspace_id,
|
|
485
|
+
)
|
|
486
|
+
self._upsert_edge(
|
|
487
|
+
conn,
|
|
488
|
+
file_node_id,
|
|
489
|
+
sem_id,
|
|
490
|
+
"포함함",
|
|
491
|
+
weight=0.9,
|
|
492
|
+
metadata={"source": "local_scan", "workspace_id": workspace_id},
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return file_node_id
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Legacy ↔ v2 projection, the keyword index, and curation over the result.
|
|
2
|
+
|
|
3
|
+
Split into two cohesive submodules in v11.3.0 (no behaviour change):
|
|
4
|
+
``v2_schema`` (the derived projection, its migration, and the FTS index) and
|
|
5
|
+
``curation`` (topic promotion, the review queue, noise reduction).
|
|
6
|
+
:class:`KnowledgeGraphProjectionMixin` composes both under its original name, so
|
|
7
|
+
``KnowledgeGraphStore``'s MRO and every method resolution are what they were,
|
|
8
|
+
and every name the single module exposed is re-exported here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# The single module had no ``__all__``: its surface was "every module global",
|
|
12
|
+
# star-imported ``_kg_common`` vocabulary included. That is reproduced verbatim
|
|
13
|
+
# below, so the waiver the star import needs applies to this file too.
|
|
14
|
+
# ruff: noqa: F403,F405
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .._kg_common import * # noqa: F401
|
|
18
|
+
|
|
19
|
+
# Module-level names of the single file, re-exported with the redundant-alias
|
|
20
|
+
# form so each is marked deliberate rather than a leftover import.
|
|
21
|
+
#
|
|
22
|
+
# Stubbing note: rebinding one of these *here* changes only this module's name.
|
|
23
|
+
# ``curate`` reads ``_promotion_review_default`` as ``curation``'s own global,
|
|
24
|
+
# so a test standing in for it patches ``…graph.projection.curation``.
|
|
25
|
+
from .curation import _LAST_NOISE_CURATE_KEY as _LAST_NOISE_CURATE_KEY
|
|
26
|
+
from .curation import _PENDING_PROMOTIONS_CAP as _PENDING_PROMOTIONS_CAP
|
|
27
|
+
from .curation import _PENDING_PROMOTIONS_KEY as _PENDING_PROMOTIONS_KEY
|
|
28
|
+
from .curation import _PROMOTION_REVIEW_ENV as _PROMOTION_REVIEW_ENV
|
|
29
|
+
from .curation import KnowledgeGraphCurationMixin
|
|
30
|
+
from .curation import _promotion_review_default as _promotion_review_default
|
|
31
|
+
from .v2_schema import KnowledgeGraphV2SchemaMixin
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class KnowledgeGraphProjectionMixin(
|
|
35
|
+
KnowledgeGraphV2SchemaMixin, KnowledgeGraphCurationMixin
|
|
36
|
+
):
|
|
37
|
+
"""Every projection and curation method, under the name the store mixes in.
|
|
38
|
+
|
|
39
|
+
Composition only: the two mixins own disjoint halves of what used to be one
|
|
40
|
+
class body, and this subclass adds nothing. ``KnowledgeGraphStore`` keeps
|
|
41
|
+
listing exactly this name among its bases.
|
|
42
|
+
"""
|