ltcai 11.2.0 → 11.4.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 +46 -53
- package/docs/CHANGELOG.md +61 -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 +176 -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/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 +9 -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 +407 -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 +1 -0
- package/scripts/generate_rust_parity_fixtures.py +562 -0
- package/scripts/lib/mock_server_fingerprint.mjs +94 -0
- package/scripts/release_screen_claims.json +31 -2
- package/src-tauri/Cargo.lock +361 -3
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/src/backend.rs +349 -0
- package/src-tauri/src/folder.rs +33 -0
- package/src-tauri/src/main.rs +97 -399
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +41 -37
- package/static/app/assets/Act-yYpYnn0v.js +1 -0
- package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
- package/static/app/assets/{Brain-tuhI4sOC.js → Brain-C1HBN0Wf.js} +2 -2
- package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
- package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
- package/static/app/assets/Capture-CFIRsFNE.js +1 -0
- package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
- package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
- package/static/app/assets/Library-DwO3yZST.js +1 -0
- package/static/app/assets/{LivingBrain-DBwhto14.js → LivingBrain-Jn1GK0-S.js} +1 -1
- package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
- package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
- package/static/app/assets/System-DW8F-2xL.js +1 -0
- package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
- package/static/app/assets/{bot-Cia42c2h.js → bot-IM_E_Y12.js} +1 -1
- package/static/app/assets/brain-Ci1CkWjM.js +1 -0
- package/static/app/assets/{button-2j2Ijzgq.js → button-COwyqfHM.js} +1 -1
- package/static/app/assets/circle-check-DfInj-qD.js +1 -0
- package/static/app/assets/{circle-pause-BEFeWpVW.js → circle-pause-DEM4A1Y5.js} +1 -1
- package/static/app/assets/{circle-play-ujXMcHxl.js → circle-play-C9djDuLd.js} +1 -1
- package/static/app/assets/{cpu-k4awryFq.js → cpu-DFdo1gw-.js} +1 -1
- package/static/app/assets/{download-DFbLJ_ig.js → download-SnJL6oqk.js} +1 -1
- package/static/app/assets/{folder-open-7y_b6xkM.js → folder-open-CqZeDkjE.js} +1 -1
- package/static/app/assets/{hard-drive-Bidh02Kr.js → hard-drive-j1jJXYYf.js} +1 -1
- package/static/app/assets/{index-DwDl9-8Y.css → index-BLPb5lmE.css} +1 -1
- package/static/app/assets/index-_u5iUHDr.js +10 -0
- package/static/app/assets/input-B0lPdRQZ.js +1 -0
- package/static/app/assets/link-2-CoFbooHS.js +1 -0
- package/static/app/assets/{permissionCopy-Bpb83Hx9.js → permissionCopy-BsyLxtao.js} +1 -1
- package/static/app/assets/primitives-DEbN-d6p.js +1 -0
- package/static/app/assets/search-BybIWPNd.js +1 -0
- package/static/app/assets/{share-2-BH1M-WNi.js → share-2-CVtZ_ewX.js} +1 -1
- package/static/app/assets/{shield-alert-BlKdBXcG.js → shield-alert-CBi2GNWM.js} +1 -1
- package/static/app/assets/{textarea-CCWbUfFB.js → textarea-DNMpB5ih.js} +1 -1
- package/static/app/assets/{useFocusTrap-YdHQ7pJ1.js → useFocusTrap-C83t3GXF.js} +1 -1
- package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
- package/static/app/assets/{useQuery-CXQiwbVT.js → useQuery-Dcp1OChy.js} +1 -1
- package/static/app/assets/utils-BlZr7Pd4.js +4 -0
- package/static/app/assets/workspace-jJY4RuAV.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,311 @@
|
|
|
1
|
+
"""Routing and lifecycle: the poll loop and the two things it can route to.
|
|
2
|
+
|
|
3
|
+
Slash commands (:func:`handle_command`), inline-button callbacks
|
|
4
|
+
(:func:`handle_callback_query`), and the long-poll loop (:func:`run_bot`) that
|
|
5
|
+
feeds both. This is the only layer that decides *what* happens for an incoming
|
|
6
|
+
update; every branch delegates to a screen or a flow and holds no logic of its
|
|
7
|
+
own beyond the routing table.
|
|
8
|
+
|
|
9
|
+
Fail-closed startup, unchanged: no bot token, no allowlist, or no server
|
|
10
|
+
capability means the loop never starts. Every allowed update is dispatched onto
|
|
11
|
+
its own task with an exception logger attached, so one failing handler cannot
|
|
12
|
+
stop the poll.
|
|
13
|
+
|
|
14
|
+
Stubbing note: ``get_updates``, ``TOKEN``, ``download_as_base64``,
|
|
15
|
+
``process_ai_request``, ``handle_command``, ``handle_callback_query``, and every
|
|
16
|
+
screen/flow handler are read as *this* module's globals, so a test standing in
|
|
17
|
+
for any of them patches this module.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from latticeai.core.logging_safety import safe_log_text
|
|
25
|
+
|
|
26
|
+
from .config import TOKEN, _get_server_session, logger
|
|
27
|
+
from .flows import (
|
|
28
|
+
_is_approval_pause,
|
|
29
|
+
ask_ai,
|
|
30
|
+
handle_plan_callback,
|
|
31
|
+
handle_proposal_callback,
|
|
32
|
+
process_ai_request,
|
|
33
|
+
send_plan_for_approval,
|
|
34
|
+
show_review_center,
|
|
35
|
+
)
|
|
36
|
+
from .helpers import (
|
|
37
|
+
allowed_chat_ids,
|
|
38
|
+
answer_callback,
|
|
39
|
+
download_as_base64,
|
|
40
|
+
get_updates,
|
|
41
|
+
is_chat_allowed,
|
|
42
|
+
register_chat_id,
|
|
43
|
+
send_chat_action,
|
|
44
|
+
send_message,
|
|
45
|
+
)
|
|
46
|
+
from .screens import (
|
|
47
|
+
clear_server_history,
|
|
48
|
+
do_unload_model,
|
|
49
|
+
process_document_file,
|
|
50
|
+
send_mcp_tools,
|
|
51
|
+
send_web_link,
|
|
52
|
+
show_graph_stats,
|
|
53
|
+
show_history_summary,
|
|
54
|
+
show_menu,
|
|
55
|
+
show_model_info,
|
|
56
|
+
show_status,
|
|
57
|
+
take_screenshot,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# ── Command dispatch ──────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
HELP_TEXT = """\
|
|
63
|
+
🧠 Lattice AI 원격 제어 명령어
|
|
64
|
+
|
|
65
|
+
/menu — 메인 메뉴 (인라인 키보드)
|
|
66
|
+
/status — 서버 상태 및 메모리
|
|
67
|
+
/model — 현재 모델 + 언로드 버튼
|
|
68
|
+
/unload — 모든 모델 언로드 (RAM 해제)
|
|
69
|
+
/graph — Knowledge Graph 통계
|
|
70
|
+
/ss 또는 /screenshot — 현재 화면 캡처
|
|
71
|
+
/history [n] — 최근 대화 n건 (기본 5)
|
|
72
|
+
/clear [n] — 기록 정리 (마지막 n건 유지)
|
|
73
|
+
/web — 웹 UI 링크
|
|
74
|
+
/mcp — MCP 도구 목록
|
|
75
|
+
/review — 변경 제안 검토함 (승인/거절)
|
|
76
|
+
/help — 이 도움말
|
|
77
|
+
|
|
78
|
+
/agent <작업> — 멀티 LLM 에이전트 (계획 확인 후 실행)
|
|
79
|
+
/agent <작업> --exec <모델> --review <모델> — 실행/검토 LLM 지정
|
|
80
|
+
|
|
81
|
+
일반 텍스트 → AI에게 질문
|
|
82
|
+
사진 전송 → AI 이미지 분석
|
|
83
|
+
문서 전송(PDF, DOCX, XLSX, PPTX, TXT, CSV) → Knowledge Graph 수집
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
async def handle_command(client, chat_id, command: str, args: str):
|
|
87
|
+
cmd = command.lower().lstrip("/").split("@")[0]
|
|
88
|
+
|
|
89
|
+
if cmd == "start":
|
|
90
|
+
await send_message(client, chat_id, "🧠 Lattice AI 원격 제어 준비 완료!")
|
|
91
|
+
await show_menu(client, chat_id)
|
|
92
|
+
elif cmd == "menu":
|
|
93
|
+
await show_menu(client, chat_id)
|
|
94
|
+
elif cmd == "status":
|
|
95
|
+
await show_status(client, chat_id)
|
|
96
|
+
elif cmd == "model":
|
|
97
|
+
await show_model_info(client, chat_id)
|
|
98
|
+
elif cmd == "unload":
|
|
99
|
+
await do_unload_model(client, chat_id)
|
|
100
|
+
elif cmd == "graph":
|
|
101
|
+
await show_graph_stats(client, chat_id)
|
|
102
|
+
elif cmd in {"ss", "screenshot"}:
|
|
103
|
+
await take_screenshot(client, chat_id)
|
|
104
|
+
elif cmd == "history":
|
|
105
|
+
n = int(args.strip()) if args.strip().isdigit() else 5
|
|
106
|
+
await show_history_summary(client, chat_id, n)
|
|
107
|
+
elif cmd in {"clear", "clear_history", "forget"}:
|
|
108
|
+
keep = int(args.strip()) if args.strip().isdigit() else 0
|
|
109
|
+
await clear_server_history(client, chat_id, keep)
|
|
110
|
+
elif cmd == "web":
|
|
111
|
+
await send_web_link(client, chat_id)
|
|
112
|
+
elif cmd == "mcp":
|
|
113
|
+
await send_mcp_tools(client, chat_id)
|
|
114
|
+
elif cmd in {"review", "proposals"}:
|
|
115
|
+
await show_review_center(client, chat_id)
|
|
116
|
+
elif cmd in {"help", "h"}:
|
|
117
|
+
await send_message(client, chat_id, HELP_TEXT)
|
|
118
|
+
elif cmd == "agent":
|
|
119
|
+
if not args:
|
|
120
|
+
await send_message(client, chat_id, "사용법: /agent <작업 내용>\n예: /agent 쇼핑몰 메인 페이지 HTML 만들어줘\n\n특정 AI 지정:\n/agent <작업> --exec openai/gpt-4o --review together:Qwen/Qwen3-VL-32B-Instruct")
|
|
121
|
+
return
|
|
122
|
+
# Parse optional --exec / --review flags
|
|
123
|
+
exec_model = reviewing_model = None
|
|
124
|
+
task_text = args
|
|
125
|
+
import re as _re
|
|
126
|
+
em = _re.search(r'--exec\s+(\S+)', args)
|
|
127
|
+
rm = _re.search(r'--review\s+(\S+)', args)
|
|
128
|
+
if em:
|
|
129
|
+
exec_model = em.group(1)
|
|
130
|
+
task_text = task_text.replace(em.group(0), "").strip()
|
|
131
|
+
if rm:
|
|
132
|
+
reviewing_model = rm.group(1)
|
|
133
|
+
task_text = task_text.replace(rm.group(0), "").strip()
|
|
134
|
+
await send_chat_action(client, chat_id, "typing")
|
|
135
|
+
data = await ask_ai(client, task_text, agent_mode=True,
|
|
136
|
+
executing_model=exec_model, reviewing_model=reviewing_model)
|
|
137
|
+
if isinstance(data, dict) and _is_approval_pause(data):
|
|
138
|
+
await send_plan_for_approval(client, chat_id, data)
|
|
139
|
+
else:
|
|
140
|
+
ans = data.get("response", str(data)) if isinstance(data, dict) else str(data)
|
|
141
|
+
await send_message(client, chat_id, ans)
|
|
142
|
+
else:
|
|
143
|
+
await send_message(client, chat_id, f"알 수 없는 명령어: /{cmd}\n/help 로 명령어 목록을 확인하세요.")
|
|
144
|
+
|
|
145
|
+
# ── Callback query handler ────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
async def handle_callback_query(client, callback_query):
|
|
148
|
+
cq_id = callback_query.get("id", "")
|
|
149
|
+
chat_id = (
|
|
150
|
+
(callback_query.get("message") or {}).get("chat") or {}
|
|
151
|
+
).get("id")
|
|
152
|
+
data = callback_query.get("data", "")
|
|
153
|
+
|
|
154
|
+
if not is_chat_allowed(chat_id):
|
|
155
|
+
logger.warning("허용되지 않은 텔레그램 callback 차단: %s", safe_log_text(chat_id))
|
|
156
|
+
await answer_callback(client, cq_id, "허용되지 않은 채팅입니다.")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
await answer_callback(client, cq_id)
|
|
160
|
+
|
|
161
|
+
if data == "cmd:status":
|
|
162
|
+
await show_status(client, chat_id)
|
|
163
|
+
elif data == "cmd:model":
|
|
164
|
+
await show_model_info(client, chat_id)
|
|
165
|
+
elif data == "cmd:graph":
|
|
166
|
+
await show_graph_stats(client, chat_id)
|
|
167
|
+
elif data == "cmd:screenshot":
|
|
168
|
+
await take_screenshot(client, chat_id)
|
|
169
|
+
elif data == "cmd:history":
|
|
170
|
+
await show_history_summary(client, chat_id, 5)
|
|
171
|
+
elif data == "cmd:clear":
|
|
172
|
+
await clear_server_history(client, chat_id, 0)
|
|
173
|
+
elif data == "cmd:web":
|
|
174
|
+
await send_web_link(client, chat_id)
|
|
175
|
+
elif data == "cmd:mcp":
|
|
176
|
+
await send_mcp_tools(client, chat_id)
|
|
177
|
+
elif data == "cmd:review":
|
|
178
|
+
await show_review_center(client, chat_id)
|
|
179
|
+
elif data == "cmd:menu":
|
|
180
|
+
await show_menu(client, chat_id)
|
|
181
|
+
elif data.startswith("model:unload:"):
|
|
182
|
+
model_id = data[len("model:unload:"):]
|
|
183
|
+
await do_unload_model(client, chat_id, model_id)
|
|
184
|
+
elif data.startswith("plan:"):
|
|
185
|
+
task = asyncio.create_task(handle_plan_callback(client, chat_id, data))
|
|
186
|
+
task.add_done_callback(_log_task_exception)
|
|
187
|
+
elif data.startswith("proposal:"):
|
|
188
|
+
task = asyncio.create_task(handle_proposal_callback(client, chat_id, data))
|
|
189
|
+
task.add_done_callback(_log_task_exception)
|
|
190
|
+
|
|
191
|
+
# ── Main loop ─────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
async def run_bot():
|
|
194
|
+
if not TOKEN:
|
|
195
|
+
logger.warning("LATTICEAI_TELEGRAM_BOT_TOKEN이 설정되지 않아 텔레그램 봇을 시작하지 않습니다.")
|
|
196
|
+
return
|
|
197
|
+
if not allowed_chat_ids():
|
|
198
|
+
logger.error(
|
|
199
|
+
"LATTICEAI_TELEGRAM_ALLOWED_CHAT_IDS가 없어 텔레그램 봇을 시작하지 않습니다."
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
if not _get_server_session():
|
|
203
|
+
logger.error(
|
|
204
|
+
"LATTICEAI_SERVER_SESSION_TOKEN이 없어 텔레그램 봇을 시작하지 않습니다."
|
|
205
|
+
)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
logger.info("🚀 비동기 텔레그램 봇 모드 시작!")
|
|
209
|
+
last_update_id = None
|
|
210
|
+
retry_delay = 1
|
|
211
|
+
|
|
212
|
+
async with httpx.AsyncClient() as client:
|
|
213
|
+
while True:
|
|
214
|
+
try:
|
|
215
|
+
updates = await get_updates(client, last_update_id)
|
|
216
|
+
retry_delay = 1
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error("get_updates 실패: %s", safe_log_text(e))
|
|
219
|
+
await asyncio.sleep(min(retry_delay, 30))
|
|
220
|
+
retry_delay = min(retry_delay * 2, 30)
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
if not (updates and updates.get("ok")):
|
|
224
|
+
await asyncio.sleep(0.5)
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
for update in updates.get("result", []):
|
|
228
|
+
try:
|
|
229
|
+
last_update_id = update.get("update_id") + 1
|
|
230
|
+
|
|
231
|
+
# ── Callback query (inline button press) ──────────────────
|
|
232
|
+
if "callback_query" in update:
|
|
233
|
+
task = asyncio.create_task(handle_callback_query(client, update["callback_query"]))
|
|
234
|
+
task.add_done_callback(_log_task_exception)
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
if "message" not in update:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
msg = update["message"]
|
|
241
|
+
chat_id = msg["chat"]["id"]
|
|
242
|
+
if not is_chat_allowed(chat_id):
|
|
243
|
+
logger.warning(
|
|
244
|
+
"허용되지 않은 텔레그램 메시지 차단: %s",
|
|
245
|
+
safe_log_text(chat_id),
|
|
246
|
+
)
|
|
247
|
+
continue
|
|
248
|
+
register_chat_id(chat_id)
|
|
249
|
+
text = msg.get("text", "")
|
|
250
|
+
caption = msg.get("caption", "")
|
|
251
|
+
|
|
252
|
+
# ── Photo → vision AI ─────────────────────────────────────
|
|
253
|
+
if "photo" in msg:
|
|
254
|
+
file_id = msg["photo"][-1]["file_id"]
|
|
255
|
+
await send_message(client, chat_id, "📸 사진을 받았습니다. 분석을 시작합니다...")
|
|
256
|
+
image_data = await download_as_base64(client, file_id)
|
|
257
|
+
prompt = caption or text or "이 이미지를 분석해줘."
|
|
258
|
+
task = asyncio.create_task(process_ai_request(client, chat_id, prompt, image_data))
|
|
259
|
+
task.add_done_callback(_log_task_exception)
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
# ── Document ──────────────────────────────────────────────
|
|
263
|
+
if "document" in msg:
|
|
264
|
+
doc = msg["document"]
|
|
265
|
+
mime = doc.get("mime_type", "")
|
|
266
|
+
filename = doc.get("file_name", "file")
|
|
267
|
+
if mime.startswith("image/"):
|
|
268
|
+
image_data = await download_as_base64(client, doc["file_id"])
|
|
269
|
+
prompt = caption or text or "이 이미지를 분석해줘."
|
|
270
|
+
task = asyncio.create_task(process_ai_request(client, chat_id, prompt, image_data))
|
|
271
|
+
else:
|
|
272
|
+
await send_message(client, chat_id, f"📄 {filename} 을 Knowledge Graph에 수집합니다...")
|
|
273
|
+
task = asyncio.create_task(
|
|
274
|
+
process_document_file(client, chat_id, doc["file_id"], filename, caption)
|
|
275
|
+
)
|
|
276
|
+
task.add_done_callback(_log_task_exception)
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
# ── Voice / audio ─────────────────────────────────────────
|
|
280
|
+
if "voice" in msg or "audio" in msg:
|
|
281
|
+
await send_message(
|
|
282
|
+
client, chat_id,
|
|
283
|
+
"🎤 음성 메시지를 받았습니다. 현재 음성 인식(Whisper)이 설정되어 있지 않습니다.\n"
|
|
284
|
+
"텍스트로 질문을 보내주세요."
|
|
285
|
+
)
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
if not text:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
# ── Commands ──────────────────────────────────────────────
|
|
292
|
+
if text.startswith("/"):
|
|
293
|
+
parts = text.split(None, 1)
|
|
294
|
+
command = parts[0]
|
|
295
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
296
|
+
task = asyncio.create_task(handle_command(client, chat_id, command, args))
|
|
297
|
+
task.add_done_callback(_log_task_exception)
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# ── Plain text → AI ───────────────────────────────────────
|
|
301
|
+
task = asyncio.create_task(process_ai_request(client, chat_id, text))
|
|
302
|
+
task.add_done_callback(_log_task_exception)
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.error("업데이트 처리 중 예외: %s", safe_log_text(e))
|
|
306
|
+
|
|
307
|
+
await asyncio.sleep(0.5)
|
|
308
|
+
|
|
309
|
+
def _log_task_exception(task):
|
|
310
|
+
if not task.cancelled() and task.exception():
|
|
311
|
+
logger.error("백그라운드 태스크 예외: %s", safe_log_text(task.exception()))
|