ltcai 0.3.0 → 0.3.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 +24 -0
- package/docs/CHANGELOG.md +82 -0
- package/knowledge_graph_api.py +10 -2
- package/latticeai/api/security_dashboard.py +580 -0
- package/latticeai/core/graph_curator.py +417 -0
- package/latticeai/core/model_compat.py +407 -0
- package/latticeai/core/model_resolution.py +227 -0
- package/package.json +1 -1
- package/server.py +254 -16
- package/static/account.html +2 -2
- package/static/admin.html +75 -1
- package/static/chat.html +2 -2
- package/static/graph.html +2 -2
- package/static/lattice-reference.css +82 -50
- package/static/scripts/account.js +10 -2
- package/static/scripts/admin.js +296 -0
- package/static/scripts/chat.js +82 -9
- package/static/scripts/graph.js +6 -2
- package/static/sw.js +1 -1
package/static/scripts/chat.js
CHANGED
|
@@ -11,6 +11,8 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
11
11
|
let currentUserNickname = "Guest";
|
|
12
12
|
let currentUserEmail = "";
|
|
13
13
|
let isAdmin = false;
|
|
14
|
+
let telegramHistorySyncEnabled = false;
|
|
15
|
+
let telegramHistorySyncInFlight = false;
|
|
14
16
|
|
|
15
17
|
// ── 멀티 LLM 파이프라인 상태 ──
|
|
16
18
|
let pipelineConfig = { planning: null, executing: null, reviewing: null };
|
|
@@ -517,6 +519,32 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
517
519
|
localStorage.setItem(scopedStorageKey('onboarding_complete'), 'true');
|
|
518
520
|
}
|
|
519
521
|
|
|
522
|
+
function consumeSetupAfterLoginFlag() {
|
|
523
|
+
try {
|
|
524
|
+
const params = new URLSearchParams(window.location.search);
|
|
525
|
+
const value = params.get('setup') === '1' || sessionStorage.getItem('ltcai_force_setup_after_login') === 'true';
|
|
526
|
+
sessionStorage.removeItem('ltcai_force_setup_after_login');
|
|
527
|
+
if (params.get('setup') === '1') {
|
|
528
|
+
const cleanUrl = `${window.location.pathname}${window.location.hash || ''}`;
|
|
529
|
+
window.history.replaceState({}, '', cleanUrl);
|
|
530
|
+
}
|
|
531
|
+
return value;
|
|
532
|
+
} catch (_) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function modelLoadedForChat() {
|
|
538
|
+
try {
|
|
539
|
+
const res = await apiFetch('/health');
|
|
540
|
+
if (!res.ok) return false;
|
|
541
|
+
const data = await res.json();
|
|
542
|
+
return Boolean(data.current_model || (data.loaded_models || []).length);
|
|
543
|
+
} catch (_) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
520
548
|
function workspaceLabel(kind) {
|
|
521
549
|
if (kind === 'company') return currentLang === 'ko' ? '회사 워크스페이스' : 'Company Workspace';
|
|
522
550
|
return currentLang === 'ko' ? '개인 워크스페이스' : 'Personal Workspace';
|
|
@@ -799,14 +827,20 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
799
827
|
let onboardingRecs = null;
|
|
800
828
|
let onboardingSelectedModel = null;
|
|
801
829
|
|
|
802
|
-
function startOnboardingIfNeeded(force = false) {
|
|
830
|
+
async function startOnboardingIfNeeded(force = false) {
|
|
803
831
|
updateWorkspaceModeUi();
|
|
804
|
-
|
|
832
|
+
const forceAfterLogin = consumeSetupAfterLoginFlag();
|
|
833
|
+
const hasLoadedModel = await modelLoadedForChat();
|
|
834
|
+
if (!force && !forceAfterLogin && onboardingComplete() && hasLoadedModel) {
|
|
805
835
|
document.getElementById('onboarding-overlay')?.classList.remove('open');
|
|
806
836
|
return;
|
|
807
837
|
}
|
|
808
838
|
document.getElementById('onboarding-overlay')?.classList.add('open');
|
|
809
|
-
|
|
839
|
+
if (forceAfterLogin || !hasLoadedModel) {
|
|
840
|
+
renderPcAnalysis();
|
|
841
|
+
} else {
|
|
842
|
+
renderOnboardingWorkspace();
|
|
843
|
+
}
|
|
810
844
|
}
|
|
811
845
|
|
|
812
846
|
function renderOnboardingShell(stepIndex, bodyHtml, actionsHtml = '') {
|
|
@@ -1260,7 +1294,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1260
1294
|
selectMode(mode);
|
|
1261
1295
|
setOnboardingComplete();
|
|
1262
1296
|
document.getElementById('onboarding-overlay')?.classList.remove('open');
|
|
1263
|
-
|
|
1297
|
+
showChat();
|
|
1264
1298
|
}
|
|
1265
1299
|
|
|
1266
1300
|
function switchAcctTab(tab) {
|
|
@@ -1836,7 +1870,17 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1836
1870
|
if (!finalData) throw new Error('모델 준비 응답이 비어 있습니다.');
|
|
1837
1871
|
closeModelPanel();
|
|
1838
1872
|
await loadModelStatus();
|
|
1839
|
-
|
|
1873
|
+
// 피드백 #1/#2: 사용자가 클릭한 modelId가 아니라 백엔드가 돌려준 current를 신뢰한다.
|
|
1874
|
+
const actualCurrent = finalData.current || (finalData.resolution && finalData.resolution.expected_current) || modelId;
|
|
1875
|
+
window.__latticeActiveModel = actualCurrent;
|
|
1876
|
+
let statusLine = `<b>${escapeHtml(compactModelName(actualCurrent))}</b> 로드 되었습니다.`;
|
|
1877
|
+
if (finalData.ready_to_chat === false) {
|
|
1878
|
+
const reason = (finalData.smoke_test && finalData.smoke_test.reason) || '채팅 호환성 검사 실패';
|
|
1879
|
+
statusLine += `<br><span class="sensitivity-preview">⚠️ 현재 채팅 호환성이 낮습니다 (${escapeHtml(reason)}). 다른 실행 엔진을 추천합니다.</span>`;
|
|
1880
|
+
} else if (finalData.compatibility_status === 'unknown') {
|
|
1881
|
+
statusLine += `<br><span class="sensitivity-preview">호환성 테스트를 완료하지 못했습니다. 채팅이 가능하지만 답변 품질이 일정하지 않을 수 있어요.</span>`;
|
|
1882
|
+
}
|
|
1883
|
+
addMessage('ai', statusLine);
|
|
1840
1884
|
} catch (e) {
|
|
1841
1885
|
document.getElementById('model-list').innerHTML = `
|
|
1842
1886
|
<div class="sensitivity-preview">${escapeHtml(e.message)}</div>
|
|
@@ -1849,6 +1893,19 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1849
1893
|
return prepareAndLoadModel(encodedId, engine);
|
|
1850
1894
|
}
|
|
1851
1895
|
|
|
1896
|
+
// 피드백 #1/#2: 사용자가 직접 모델을 선택했을 때도 같은 표준 흐름을 타도록 노출.
|
|
1897
|
+
async function selectModelByCard(card) {
|
|
1898
|
+
if (!card || !card.id) {
|
|
1899
|
+
throw new Error('모델 카드가 비어 있습니다.');
|
|
1900
|
+
}
|
|
1901
|
+
const encoded = encodeURIComponent(card.id);
|
|
1902
|
+
const engine = card.engine || (Array.isArray(card.engine_options) && card.engine_options[0]?.engine) || '';
|
|
1903
|
+
return prepareAndLoadModel(encoded, engine);
|
|
1904
|
+
}
|
|
1905
|
+
if (typeof window !== 'undefined') {
|
|
1906
|
+
window.selectModelByCard = selectModelByCard;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1852
1909
|
function fillVpcForm(config) {
|
|
1853
1910
|
if (!config) return;
|
|
1854
1911
|
document.getElementById('vpc-provider').value = config.provider || '';
|
|
@@ -1938,7 +1995,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1938
1995
|
if (!t) {
|
|
1939
1996
|
t = document.createElement('div');
|
|
1940
1997
|
t.id = 'ltcai-toast';
|
|
1941
|
-
t.style.cssText = 'position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background
|
|
1998
|
+
t.style.cssText = 'position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:rgba(255,255,255,0.96);color:#14162c;border:1px solid rgba(111,66,232,0.16);border-radius:10px;padding:10px 18px;font-size:13px;font-weight:600;z-index:9999;box-shadow:0 8px 24px rgba(88,72,150,0.16);pointer-events:none;transition:opacity .2s;';
|
|
1942
1999
|
document.body.appendChild(t);
|
|
1943
2000
|
}
|
|
1944
2001
|
t.textContent = msg;
|
|
@@ -3264,10 +3321,13 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
3264
3321
|
}
|
|
3265
3322
|
|
|
3266
3323
|
async function syncTelegramHistory() {
|
|
3324
|
+
if (!telegramHistorySyncEnabled || telegramHistorySyncInFlight) return;
|
|
3325
|
+
telegramHistorySyncInFlight = true;
|
|
3267
3326
|
try {
|
|
3268
3327
|
const res = await apiFetch('/history');
|
|
3269
3328
|
if (!res.ok) return;
|
|
3270
3329
|
const history = await res.json();
|
|
3330
|
+
let added = false;
|
|
3271
3331
|
for (const item of history) {
|
|
3272
3332
|
if (item.source !== 'telegram') continue;
|
|
3273
3333
|
const key = `${item.timestamp || ''}:${item.role}:${item.content}`;
|
|
@@ -3278,9 +3338,13 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
3278
3338
|
? 'Lattice AI'
|
|
3279
3339
|
: (item.user_nickname || 'Telegram');
|
|
3280
3340
|
addMessage(role, item.content || '', null, sender);
|
|
3341
|
+
added = true;
|
|
3281
3342
|
}
|
|
3282
|
-
loadHistory();
|
|
3343
|
+
if (added) loadHistory();
|
|
3283
3344
|
} catch (e) { }
|
|
3345
|
+
finally {
|
|
3346
|
+
telegramHistorySyncInFlight = false;
|
|
3347
|
+
}
|
|
3284
3348
|
}
|
|
3285
3349
|
|
|
3286
3350
|
async function sendToAgent(text, extraCtx = '') {
|
|
@@ -3866,10 +3930,19 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
3866
3930
|
const res = await apiFetch('/runtime_features');
|
|
3867
3931
|
if (res.ok) {
|
|
3868
3932
|
const f = await res.json();
|
|
3933
|
+
telegramHistorySyncEnabled = Boolean(f.telegram_enabled);
|
|
3869
3934
|
if (!f.graph_enabled) {
|
|
3870
3935
|
const btn = document.getElementById('data-graph-btn');
|
|
3871
3936
|
if (btn) btn.style.display = 'none';
|
|
3872
3937
|
}
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
} catch (_) {}
|
|
3941
|
+
try {
|
|
3942
|
+
const res = await apiFetch('/health');
|
|
3943
|
+
if (res.ok) {
|
|
3944
|
+
const data = await res.json();
|
|
3945
|
+
telegramHistorySyncEnabled = Boolean(data.features?.telegram_enabled);
|
|
3873
3946
|
}
|
|
3874
3947
|
} catch (_) {}
|
|
3875
3948
|
})();
|
|
@@ -3878,7 +3951,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
3878
3951
|
loadVpcStatus();
|
|
3879
3952
|
restoreCurrentConversation();
|
|
3880
3953
|
syncTelegramHistory();
|
|
3881
|
-
setInterval(syncTelegramHistory,
|
|
3954
|
+
setInterval(syncTelegramHistory, 15000);
|
|
3882
3955
|
|
|
3883
3956
|
// ── 내 컴퓨터 ──────────────────────────────────────────────────
|
|
3884
3957
|
let cuAgentRunning = false;
|
|
@@ -4370,7 +4443,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
4370
4443
|
function _showComplete() {
|
|
4371
4444
|
_subtitle('설정 완료!');
|
|
4372
4445
|
_footInfo('');
|
|
4373
|
-
_footBtns(`<button class="wbtn wbtn-primary" onclick="closeSetupWizard();loadModelStatus()">완료 ✓</button>`);
|
|
4446
|
+
_footBtns(`<button class="wbtn wbtn-primary" onclick="closeSetupWizard();loadModelStatus();showChat()">완료 ✓</button>`);
|
|
4374
4447
|
}
|
|
4375
4448
|
|
|
4376
4449
|
// ── MCP 관리 모달 ────────────────────────────────────────────────────────
|
package/static/scripts/graph.js
CHANGED
|
@@ -324,8 +324,12 @@ const API_BASE = window.location.protocol === 'file:' ? 'http://localhost:4825'
|
|
|
324
324
|
</div>
|
|
325
325
|
${rootRows ? `<div class="local-root-list">${rootRows}</div>` : ''}
|
|
326
326
|
<div class="local-option-row">
|
|
327
|
-
<
|
|
328
|
-
|
|
327
|
+
<button type="button" class="local-option-btn ${localState.includeOcr ? 'active' : ''}" onclick="setLocalOption('includeOcr', ${localState.includeOcr ? 'false' : 'true'})" aria-pressed="${localState.includeOcr ? 'true' : 'false'}">
|
|
328
|
+
<i class="ti ti-photo-scan"></i><span>${t('local_ocr')}</span>
|
|
329
|
+
</button>
|
|
330
|
+
<button type="button" class="local-option-btn ${localState.watchEnabled ? 'active' : ''}" onclick="setLocalOption('watchEnabled', ${localState.watchEnabled ? 'false' : 'true'})" aria-pressed="${localState.watchEnabled ? 'true' : 'false'}">
|
|
331
|
+
<i class="ti ti-refresh-dot"></i><span>${t('local_watch')}</span>
|
|
332
|
+
</button>
|
|
329
333
|
</div>
|
|
330
334
|
<div class="local-source-actions">
|
|
331
335
|
<button class="local-source-btn" ${localState.busy ? 'disabled' : ''} onclick="runLocalTree()" title="${t('local_tree')}"><i class="ti ti-folders"></i>${t('local_tree')}</button>
|
package/static/sw.js
CHANGED