@co-engram/viewer 0.1.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 +38 -0
- package/dist/brand-logos.d.ts +9 -0
- package/dist/brand-logos.d.ts.map +1 -0
- package/dist/brand-logos.js +10 -0
- package/dist/brand-logos.js.map +1 -0
- package/dist/html.d.ts +21 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +299 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/app.d.ts +11 -0
- package/dist/runtime/app.d.ts.map +1 -0
- package/dist/runtime/app.js +437 -0
- package/dist/runtime/app.js.map +1 -0
- package/dist/runtime/decay.d.ts +16 -0
- package/dist/runtime/decay.d.ts.map +1 -0
- package/dist/runtime/decay.js +108 -0
- package/dist/runtime/decay.js.map +1 -0
- package/dist/runtime/graph.d.ts +13 -0
- package/dist/runtime/graph.d.ts.map +1 -0
- package/dist/runtime/graph.js +313 -0
- package/dist/runtime/graph.js.map +1 -0
- package/dist/runtime/i18n.d.ts +16 -0
- package/dist/runtime/i18n.d.ts.map +1 -0
- package/dist/runtime/i18n.js +76 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/tabs.d.ts +8 -0
- package/dist/runtime/tabs.d.ts.map +1 -0
- package/dist/runtime/tabs.js +1783 -0
- package/dist/runtime/tabs.js.map +1 -0
- package/dist/server.d.ts +73 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +985 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.d.ts +13 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +1632 -0
- package/dist/styles.js.map +1 -0
- package/dist/vendor/dompurify-source.d.ts +11 -0
- package/dist/vendor/dompurify-source.d.ts.map +1 -0
- package/dist/vendor/dompurify-source.js +15 -0
- package/dist/vendor/dompurify-source.js.map +1 -0
- package/dist/vendor/marked-source.d.ts +11 -0
- package/dist/vendor/marked-source.d.ts.map +1 -0
- package/dist/vendor/marked-source.js +18 -0
- package/dist/vendor/marked-source.js.map +1 -0
- package/dist/vendor/vis-network-source.d.ts +11 -0
- package/dist/vendor/vis-network-source.d.ts.map +1 -0
- package/dist/vendor/vis-network-source.js +46 -0
- package/dist/vendor/vis-network-source.js.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @co-engram/viewer
|
|
2
|
+
|
|
3
|
+
**Host-agnostic HTTP viewer for Co-Engram team memory.**
|
|
4
|
+
|
|
5
|
+
Inline SPA that lets you browse engrams, synapses, skills, audit log, effectiveness metrics, and the trash bin. Bind to loopback only, optional bearer token auth, automatic `EADDRINUSE` retry.
|
|
6
|
+
|
|
7
|
+
Both `@co-engram/claude-code` and `@co-engram/openclaw` import this package to start a viewer — no host-specific coupling here.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { startViewerServer } from "@co-engram/viewer";
|
|
13
|
+
|
|
14
|
+
const runtime = await startViewerServer(ctx, {
|
|
15
|
+
port: 18799, // optional, defaults to 18799 with auto-retry
|
|
16
|
+
token: "optional", // optional bearer token
|
|
17
|
+
language: "zh", // 'en' | 'zh'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// later
|
|
21
|
+
await runtime.stop();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
- **`server.ts`** — HTTP server (11 endpoints under `/api/*` + SPA root)
|
|
27
|
+
- **`html.ts`** — SPA shell assembler (inlines vendor bundles, runtime modules)
|
|
28
|
+
- **`styles.ts`** — all CSS as a string literal
|
|
29
|
+
- **`runtime/{app,tabs,graph}.ts`** — client-side TS shipped as strings to the browser
|
|
30
|
+
- **`vendor/*-source.ts`** — auto-generated by `scripts/build-vendor.mjs`, inlining marked / DOMPurify / vis-network UMD bundles so the SPA works offline
|
|
31
|
+
|
|
32
|
+
## Build
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm build # runs build:vendor (inlines UMD bundles) + tsc
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run `pnpm build:vendor` after upgrading marked / DOMPurify / vis-network to refresh the inlined strings.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated from src/assets/*.svg. Do not edit directly.
|
|
3
|
+
*
|
|
4
|
+
* 由 scripts/build-assets.mjs 生成;改 SVG 后跑 `pnpm build` 重新生成。
|
|
5
|
+
*/
|
|
6
|
+
export declare const COENGRAMFAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" role=\"img\" aria-label=\"co-engram\"> <title>co-engram</title> <defs> <linearGradient id=\"favCurve\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#BEC7D2\"/> <stop offset=\"50%\" stop-color=\"#BCBAA5\"/> <stop offset=\"100%\" stop-color=\"#B8941D\"/> </linearGradient> <linearGradient id=\"favCore\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#BEC7D2\"/> <stop offset=\"50%\" stop-color=\"#BCBAA5\"/> <stop offset=\"100%\" stop-color=\"#B8941D\"/> </linearGradient> </defs> <path d=\"M 14 32 C 15 22, 26 22, 32 32 C 38 42, 49 42, 50 32 C 49 22, 38 22, 32 32 C 26 42, 15 42, 14 32 Z\" fill=\"none\" stroke=\"url(#favCurve)\" stroke-width=\"3.2\" stroke-linecap=\"round\"/> <circle cx=\"14\" cy=\"32\" r=\"5.5\" fill=\"#BEC7D2\"/> <circle cx=\"32\" cy=\"32\" r=\"5.5\" fill=\"url(#favCore)\"/> <circle cx=\"50\" cy=\"32\" r=\"5.5\" fill=\"#B8941D\"/> </svg>";
|
|
7
|
+
export declare const COENGRAMLOGODARK_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 400\" role=\"img\" aria-label=\"co-engram\"> <title>co-engram</title> <defs> <linearGradient id=\"coengramCurveDark\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#D8DFE6\"/> <stop offset=\"50%\" stop-color=\"#D6D4BC\"/> <stop offset=\"100%\" stop-color=\"#D4A838\"/> </linearGradient> <linearGradient id=\"coengramCoreDark\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#D8DFE6\"/> <stop offset=\"50%\" stop-color=\"#D6D4BC\"/> <stop offset=\"100%\" stop-color=\"#D4A838\"/> </linearGradient> </defs> <path d=\"M 85 200 C 95 135, 160 135, 200 200 C 240 265, 305 265, 315 200 C 305 135, 240 135, 200 200 C 160 265, 95 265, 85 200 Z\" fill=\"none\" stroke=\"url(#coengramCurveDark)\" stroke-width=\"7.5\" stroke-linecap=\"round\"/> <circle cx=\"85\" cy=\"200\" r=\"16\" fill=\"#D8DFE6\"/> <circle cx=\"200\" cy=\"200\" r=\"16\" fill=\"url(#coengramCoreDark)\"/> <circle cx=\"315\" cy=\"200\" r=\"16\" fill=\"#D4A838\"/> </svg>";
|
|
8
|
+
export declare const COENGRAMLOGO_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 400\" role=\"img\" aria-label=\"co-engram\"> <title>co-engram</title> <defs> <linearGradient id=\"coengramCurve\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#BEC7D2\"/> <stop offset=\"50%\" stop-color=\"#BCBAA5\"/> <stop offset=\"100%\" stop-color=\"#B8941D\"/> </linearGradient> <linearGradient id=\"coengramCore\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"> <stop offset=\"0%\" stop-color=\"#BEC7D2\"/> <stop offset=\"50%\" stop-color=\"#BCBAA5\"/> <stop offset=\"100%\" stop-color=\"#B8941D\"/> </linearGradient> </defs> <path d=\"M 85 200 C 95 135, 160 135, 200 200 C 240 265, 305 265, 315 200 C 305 135, 240 135, 200 200 C 160 265, 95 265, 85 200 Z\" fill=\"none\" stroke=\"url(#coengramCurve)\" stroke-width=\"7.5\" stroke-linecap=\"round\"/> <circle cx=\"85\" cy=\"200\" r=\"16\" fill=\"#BEC7D2\"/> <circle cx=\"200\" cy=\"200\" r=\"16\" fill=\"url(#coengramCore)\"/> <circle cx=\"315\" cy=\"200\" r=\"16\" fill=\"#B8941D\"/> </svg>";
|
|
9
|
+
//# sourceMappingURL=brand-logos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-logos.d.ts","sourceRoot":"","sources":["../src/brand-logos.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,eAAO,MAAM,mBAAmB,89BAAw4B,CAAA;AACx6B,eAAO,MAAM,oBAAoB,4hCAAs8B,CAAA;AACv+B,eAAO,MAAM,gBAAgB,4gCAAs7B,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Auto-generated from src/assets/*.svg. Do not edit directly.
|
|
4
|
+
*
|
|
5
|
+
* 由 scripts/build-assets.mjs 生成;改 SVG 后跑 `pnpm build` 重新生成。
|
|
6
|
+
*/
|
|
7
|
+
export const COENGRAMFAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="co-engram"> <title>co-engram</title> <defs> <linearGradient id="favCurve" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#BEC7D2"/> <stop offset="50%" stop-color="#BCBAA5"/> <stop offset="100%" stop-color="#B8941D"/> </linearGradient> <linearGradient id="favCore" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#BEC7D2"/> <stop offset="50%" stop-color="#BCBAA5"/> <stop offset="100%" stop-color="#B8941D"/> </linearGradient> </defs> <path d="M 14 32 C 15 22, 26 22, 32 32 C 38 42, 49 42, 50 32 C 49 22, 38 22, 32 32 C 26 42, 15 42, 14 32 Z" fill="none" stroke="url(#favCurve)" stroke-width="3.2" stroke-linecap="round"/> <circle cx="14" cy="32" r="5.5" fill="#BEC7D2"/> <circle cx="32" cy="32" r="5.5" fill="url(#favCore)"/> <circle cx="50" cy="32" r="5.5" fill="#B8941D"/> </svg>`;
|
|
8
|
+
export const COENGRAMLOGODARK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="co-engram"> <title>co-engram</title> <defs> <linearGradient id="coengramCurveDark" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#D8DFE6"/> <stop offset="50%" stop-color="#D6D4BC"/> <stop offset="100%" stop-color="#D4A838"/> </linearGradient> <linearGradient id="coengramCoreDark" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#D8DFE6"/> <stop offset="50%" stop-color="#D6D4BC"/> <stop offset="100%" stop-color="#D4A838"/> </linearGradient> </defs> <path d="M 85 200 C 95 135, 160 135, 200 200 C 240 265, 305 265, 315 200 C 305 135, 240 135, 200 200 C 160 265, 95 265, 85 200 Z" fill="none" stroke="url(#coengramCurveDark)" stroke-width="7.5" stroke-linecap="round"/> <circle cx="85" cy="200" r="16" fill="#D8DFE6"/> <circle cx="200" cy="200" r="16" fill="url(#coengramCoreDark)"/> <circle cx="315" cy="200" r="16" fill="#D4A838"/> </svg>`;
|
|
9
|
+
export const COENGRAMLOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="co-engram"> <title>co-engram</title> <defs> <linearGradient id="coengramCurve" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#BEC7D2"/> <stop offset="50%" stop-color="#BCBAA5"/> <stop offset="100%" stop-color="#B8941D"/> </linearGradient> <linearGradient id="coengramCore" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#BEC7D2"/> <stop offset="50%" stop-color="#BCBAA5"/> <stop offset="100%" stop-color="#B8941D"/> </linearGradient> </defs> <path d="M 85 200 C 95 135, 160 135, 200 200 C 240 265, 305 265, 315 200 C 305 135, 240 135, 200 200 C 160 265, 95 265, 85 200 Z" fill="none" stroke="url(#coengramCurve)" stroke-width="7.5" stroke-linecap="round"/> <circle cx="85" cy="200" r="16" fill="#BEC7D2"/> <circle cx="200" cy="200" r="16" fill="url(#coengramCore)"/> <circle cx="315" cy="200" r="16" fill="#B8941D"/> </svg>`;
|
|
10
|
+
//# sourceMappingURL=brand-logos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-logos.js","sourceRoot":"","sources":["../src/brand-logos.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;GAIG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,q4BAAq4B,CAAA;AACx6B,MAAM,CAAC,MAAM,oBAAoB,GAAG,m8BAAm8B,CAAA;AACv+B,MAAM,CAAC,MAAM,gBAAgB,GAAG,m7BAAm7B,CAAA"}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-Engram Viewer v2 SPA HTML
|
|
3
|
+
*
|
|
4
|
+
* 拼装骨架 HTML + styles.ts(CSS)+ vendor(vis-network 内联)+
|
|
5
|
+
* runtime/{app,graph,tabs}.ts(浏览器端 JS)。
|
|
6
|
+
*
|
|
7
|
+
* 各 tab 通过 CO_ENGRAM.on(name, fn) 注册渲染函数,在 showTab() 切换时调用。
|
|
8
|
+
* 整页不依赖任何外部 CDN(Alpine/htmx 已移除),完全离线可用。
|
|
9
|
+
*
|
|
10
|
+
* @module @co-engram/claude-code/viewer
|
|
11
|
+
*/
|
|
12
|
+
import { type Language } from "@co-engram/core";
|
|
13
|
+
export interface SpaHtmlOptions {
|
|
14
|
+
/** 是否要求 token 认证 */
|
|
15
|
+
readonly tokenRequired?: boolean;
|
|
16
|
+
/** UI 语言(默认 en) */
|
|
17
|
+
readonly language?: Language;
|
|
18
|
+
}
|
|
19
|
+
/** 渲染完整 SPA HTML */
|
|
20
|
+
export declare function renderSpaHtml(options?: SpaHtmlOptions): string;
|
|
21
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAgB7E,MAAM,WAAW,cAAc;IAC7B,oBAAoB;IACpB,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,mBAAmB;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAED,oBAAoB;AACpB,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAkSlE"}
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-Engram Viewer v2 SPA HTML
|
|
3
|
+
*
|
|
4
|
+
* 拼装骨架 HTML + styles.ts(CSS)+ vendor(vis-network 内联)+
|
|
5
|
+
* runtime/{app,graph,tabs}.ts(浏览器端 JS)。
|
|
6
|
+
*
|
|
7
|
+
* 各 tab 通过 CO_ENGRAM.on(name, fn) 注册渲染函数,在 showTab() 切换时调用。
|
|
8
|
+
* 整页不依赖任何外部 CDN(Alpine/htmx 已移除),完全离线可用。
|
|
9
|
+
*
|
|
10
|
+
* @module @co-engram/claude-code/viewer
|
|
11
|
+
*/
|
|
12
|
+
import { t, zh, en, DEFAULT_LANGUAGE } from "@co-engram/core";
|
|
13
|
+
import { VIEWER_CSS } from "./styles.js";
|
|
14
|
+
import { VIS_NETWORK_SOURCE } from "./vendor/vis-network-source.js";
|
|
15
|
+
import { MARKED_SOURCE } from "./vendor/marked-source.js";
|
|
16
|
+
import { DOMPURIFY_SOURCE } from "./vendor/dompurify-source.js";
|
|
17
|
+
import { I18N_RUNTIME } from "./runtime/i18n.js";
|
|
18
|
+
import { DECAY_RUNTIME } from "./runtime/decay.js";
|
|
19
|
+
import { APP_RUNTIME } from "./runtime/app.js";
|
|
20
|
+
import { GRAPH_RUNTIME } from "./runtime/graph.js";
|
|
21
|
+
import { TABS_RUNTIME } from "./runtime/tabs.js";
|
|
22
|
+
import { COENGRAMLOGO_SVG, COENGRAMLOGODARK_SVG, COENGRAMFAVICON_SVG, } from "./brand-logos.js";
|
|
23
|
+
/** 渲染完整 SPA HTML */
|
|
24
|
+
export function renderSpaHtml(options = {}) {
|
|
25
|
+
const language = options.language ?? DEFAULT_LANGUAGE;
|
|
26
|
+
const title = t(language, "viewer.title");
|
|
27
|
+
const slogan = t(language, "viewer.slogan");
|
|
28
|
+
const footer = t(language, "viewer.footer");
|
|
29
|
+
const searchPlaceholder = t(language, "viewer.search.placeholder");
|
|
30
|
+
const searchBtn = t(language, "viewer.search.button");
|
|
31
|
+
const searchClearBtn = t(language, "viewer.search.clear");
|
|
32
|
+
const searchClearTitle = t(language, "viewer.search.clear_title");
|
|
33
|
+
const authPrompt = options.tokenRequired
|
|
34
|
+
? t(language, "viewer.auth.prompt")
|
|
35
|
+
: "";
|
|
36
|
+
const authPlaceholder = t(language, "viewer.auth.placeholder");
|
|
37
|
+
const tabs = [
|
|
38
|
+
["stats", t(language, "viewer.tab.stats")],
|
|
39
|
+
["engrams", t(language, "viewer.tab.engrams")],
|
|
40
|
+
["graph", t(language, "viewer.tab.graph")],
|
|
41
|
+
["proposals", t(language, "viewer.tab.proposals")],
|
|
42
|
+
["merges", t(language, "viewer.tab.merges")],
|
|
43
|
+
["audit", t(language, "viewer.tab.audit")],
|
|
44
|
+
["trash", t(language, "viewer.tab.trash")],
|
|
45
|
+
["config", t(language, "viewer.tab.config")],
|
|
46
|
+
["help", t(language, "viewer.tab.help")],
|
|
47
|
+
];
|
|
48
|
+
const tabButtons = tabs
|
|
49
|
+
.map(([id, label]) => `<button data-tab="${id}" class="tab">${label}</button>`)
|
|
50
|
+
.join("\n ");
|
|
51
|
+
return `<!DOCTYPE html>
|
|
52
|
+
<html lang="${language}">
|
|
53
|
+
<head>
|
|
54
|
+
<meta charset="utf-8">
|
|
55
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
56
|
+
<title>${title}</title>
|
|
57
|
+
<meta name="color-scheme" content="light dark">
|
|
58
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;utf8,${encodeURIComponent(COENGRAMFAVICON_SVG)}">
|
|
59
|
+
<style>${VIEWER_CSS}</style>
|
|
60
|
+
<script>${VIS_NETWORK_SOURCE}</script>
|
|
61
|
+
<script>${MARKED_SOURCE}</script>
|
|
62
|
+
<script>${DOMPURIFY_SOURCE}</script>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<header class="app-header">
|
|
66
|
+
<div class="brand">
|
|
67
|
+
<span class="brand-logo brand-logo-light" aria-hidden="true">${COENGRAMLOGO_SVG}</span>
|
|
68
|
+
<span class="brand-logo brand-logo-dark" aria-hidden="true">${COENGRAMLOGODARK_SVG}</span>
|
|
69
|
+
<div class="brand-text">
|
|
70
|
+
<h1>${title}</h1>
|
|
71
|
+
<span class="brand-slogan">${slogan}</span>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<nav>
|
|
75
|
+
${tabButtons}
|
|
76
|
+
</nav>
|
|
77
|
+
${options.tokenRequired
|
|
78
|
+
? `<div class="auth-bar">${authPrompt} <input id="token-input" type="password" placeholder="${authPlaceholder}"/></div>`
|
|
79
|
+
: ""}
|
|
80
|
+
</header>
|
|
81
|
+
|
|
82
|
+
<main>
|
|
83
|
+
<!-- Stats -->
|
|
84
|
+
<section class="tab-panel" data-tab="stats">
|
|
85
|
+
<!-- Search bar 仅在 stats tab 显示 -->
|
|
86
|
+
<form id="search-form" class="search-bar">
|
|
87
|
+
<input id="search-input" type="text" name="q" placeholder="${searchPlaceholder}" autocomplete="off"/>
|
|
88
|
+
<button type="submit">${searchBtn}</button>
|
|
89
|
+
<button type="button" id="search-clear" title="${searchClearTitle}" onclick="CO_ENGRAM.clearSearch()">${searchClearBtn}</button>
|
|
90
|
+
</form>
|
|
91
|
+
<div id="search-results" class="panel" style="margin-bottom:1.5rem;display:none"></div>
|
|
92
|
+
<div id="stats-content"></div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<!-- Engrams -->
|
|
96
|
+
<section class="tab-panel" data-tab="engrams">
|
|
97
|
+
<div id="engrams-content"></div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<!-- Graph -->
|
|
101
|
+
<section class="tab-panel" data-tab="graph">
|
|
102
|
+
<div class="graph-container">
|
|
103
|
+
<div class="graph-toolbar">
|
|
104
|
+
<div class="toolbar-actions">
|
|
105
|
+
<button class="mini" onclick="CO_ENGRAM_GRAPH.fit()" title="适应视图:自动缩放并居中,让所有节点都可见">适应视图</button>
|
|
106
|
+
<button class="mini" onclick="CO_ENGRAM_GRAPH.togglePhysics()" title="物理引擎:开启时节点按弹簧/斥力模型自动布局(会消耗 CPU 直到稳定);关闭时冻结当前位置,适合大图稳定后浏览">物理引擎</button>
|
|
107
|
+
<button class="mini" onclick="CO_ENGRAM_GRAPH.reset()" title="重置过滤:恢复所有类型/族勾选,并重新适应视图">重置过滤</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div class="group-title">突触类型 · 按族分类</div>
|
|
111
|
+
${[
|
|
112
|
+
{
|
|
113
|
+
family: "structural",
|
|
114
|
+
familyColor: "#3b82f6",
|
|
115
|
+
label: "结构族",
|
|
116
|
+
desc: "描述知识间的组成/扩展关系",
|
|
117
|
+
kinds: [
|
|
118
|
+
[
|
|
119
|
+
"extends",
|
|
120
|
+
"扩展",
|
|
121
|
+
"#3b82f6",
|
|
122
|
+
"A 在 B 基础上扩展,继承 B 的语义并新增维度",
|
|
123
|
+
],
|
|
124
|
+
["part_of", "部分", "#60a5fa", "A 是 B 的组成部分(B has-a A)"],
|
|
125
|
+
[
|
|
126
|
+
"similar_to",
|
|
127
|
+
"相似",
|
|
128
|
+
"#1e40af",
|
|
129
|
+
"A 与 B 语义相近,可互换或互援",
|
|
130
|
+
],
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
family: "causal",
|
|
135
|
+
familyColor: "#f97316",
|
|
136
|
+
label: "因果族",
|
|
137
|
+
desc: "描述触发/依赖关系",
|
|
138
|
+
kinds: [
|
|
139
|
+
[
|
|
140
|
+
"depends_on",
|
|
141
|
+
"依赖",
|
|
142
|
+
"#f97316",
|
|
143
|
+
"A 的成立依赖 B(B 是 A 的前置条件)",
|
|
144
|
+
],
|
|
145
|
+
["causes", "导致", "#fb923c", "A 触发或产生 B(正向因果)"],
|
|
146
|
+
[
|
|
147
|
+
"follows",
|
|
148
|
+
"顺承",
|
|
149
|
+
"#c2410c",
|
|
150
|
+
"A 在时间/逻辑上跟随 B(无强因果)",
|
|
151
|
+
],
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
family: "evidential",
|
|
156
|
+
familyColor: "#10b981",
|
|
157
|
+
label: "证据族",
|
|
158
|
+
desc: "描述来源/冲突关系",
|
|
159
|
+
kinds: [
|
|
160
|
+
[
|
|
161
|
+
"derives_from",
|
|
162
|
+
"派生",
|
|
163
|
+
"#10b981",
|
|
164
|
+
"A 从 B 推导而来(B 是依据)",
|
|
165
|
+
],
|
|
166
|
+
["exemplifies", "例证", "#6ee7b7", "A 是 B 的具体实例/样本"],
|
|
167
|
+
[
|
|
168
|
+
"contradicts",
|
|
169
|
+
"矛盾",
|
|
170
|
+
"#ef4444",
|
|
171
|
+
"A 与 B 相互冲突,进入裁决流程",
|
|
172
|
+
],
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
family: "temporal",
|
|
177
|
+
familyColor: "#8b5cf6",
|
|
178
|
+
label: "时间族",
|
|
179
|
+
desc: "描述版本/演化关系",
|
|
180
|
+
kinds: [
|
|
181
|
+
["supersedes", "取代", "#8b5cf6", "A 取代过时的 B(版本更迭)"],
|
|
182
|
+
["consolidates", "整合", "#c4b5fd", "A 合并/精炼了 B 的内容"],
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
family: "modulatory",
|
|
187
|
+
familyColor: "#6b7280",
|
|
188
|
+
label: "调节族",
|
|
189
|
+
desc: "描述情境上下文关系",
|
|
190
|
+
kinds: [
|
|
191
|
+
[
|
|
192
|
+
"contextualizes",
|
|
193
|
+
"上下文",
|
|
194
|
+
"#6b7280",
|
|
195
|
+
"A 为 B 提供情境背景(非因果、非证据)",
|
|
196
|
+
],
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
.map((group) => `
|
|
201
|
+
<fieldset class="family-group">
|
|
202
|
+
<legend title="${group.desc}"><span class="family-dot" style="background:${group.familyColor}"></span>${group.label}</legend>
|
|
203
|
+
<div class="family-kinds">
|
|
204
|
+
${group.kinds
|
|
205
|
+
.map(([id, label, color, desc]) => `<label title="${desc}"><input type="checkbox" checked onchange="CO_ENGRAM_GRAPH.toggleSynapseKind('${id}', event.target.checked)"><span class="swatch" style="background:${color}"></span>${label}</label>`)
|
|
206
|
+
.join("")}
|
|
207
|
+
</div>
|
|
208
|
+
</fieldset>
|
|
209
|
+
`)
|
|
210
|
+
.join("")}
|
|
211
|
+
|
|
212
|
+
<div class="group-title">记忆印迹类型</div>
|
|
213
|
+
<div class="group kind-grid">
|
|
214
|
+
${[
|
|
215
|
+
["fact", "事实", "#10b981", "被确认成立、可独立验证的客观陈述"],
|
|
216
|
+
[
|
|
217
|
+
"observation",
|
|
218
|
+
"观察",
|
|
219
|
+
"#3b82f6",
|
|
220
|
+
"一次性感知到的事实,可能尚未沉淀为稳定结论",
|
|
221
|
+
],
|
|
222
|
+
[
|
|
223
|
+
"pattern",
|
|
224
|
+
"模式",
|
|
225
|
+
"#8b5cf6",
|
|
226
|
+
"从多次观察归纳出的规律,可预测未来行为",
|
|
227
|
+
],
|
|
228
|
+
["procedure", "流程", "#f97316", "步骤序列,执行后可复现某结果"],
|
|
229
|
+
[
|
|
230
|
+
"hypothesis",
|
|
231
|
+
"假设",
|
|
232
|
+
"#ef4444",
|
|
233
|
+
"待验证的猜测;在反例出现前可作工作假设",
|
|
234
|
+
],
|
|
235
|
+
]
|
|
236
|
+
.map(([k, label, color, desc]) => `<label title="${desc}"><input type="checkbox" checked onchange="CO_ENGRAM_GRAPH.toggleKind('${k}', event.target.checked)"><span class="swatch" style="background:${color}"></span>${label}</label>`)
|
|
237
|
+
.join("")}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
<div id="graph-canvas">
|
|
241
|
+
<div class="loading">加载图谱中...</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</section>
|
|
245
|
+
|
|
246
|
+
<!-- Proposals -->
|
|
247
|
+
<section class="tab-panel" data-tab="proposals">
|
|
248
|
+
<div id="proposals-content"></div>
|
|
249
|
+
</section>
|
|
250
|
+
|
|
251
|
+
<!-- Merges (P4.3) -->
|
|
252
|
+
<section class="tab-panel" data-tab="merges">
|
|
253
|
+
<div id="merges-content"></div>
|
|
254
|
+
</section>
|
|
255
|
+
|
|
256
|
+
<!-- Audit -->
|
|
257
|
+
<section class="tab-panel" data-tab="audit">
|
|
258
|
+
<div id="audit-content"></div>
|
|
259
|
+
</section>
|
|
260
|
+
|
|
261
|
+
<!-- Trash -->
|
|
262
|
+
<section class="tab-panel" data-tab="trash">
|
|
263
|
+
<div id="trash-content"></div>
|
|
264
|
+
</section>
|
|
265
|
+
|
|
266
|
+
<!-- Config -->
|
|
267
|
+
<section class="tab-panel" data-tab="config">
|
|
268
|
+
<div id="config-content"></div>
|
|
269
|
+
</section>
|
|
270
|
+
|
|
271
|
+
<!-- Help -->
|
|
272
|
+
<section class="tab-panel" data-tab="help">
|
|
273
|
+
<div id="help-content"></div>
|
|
274
|
+
</section>
|
|
275
|
+
</main>
|
|
276
|
+
|
|
277
|
+
<!-- Detail drawer (right side) -->
|
|
278
|
+
<aside id="detail-drawer" class="drawer">
|
|
279
|
+
<button class="drawer-close" aria-label="Close">✕</button>
|
|
280
|
+
<div class="drawer-body"></div>
|
|
281
|
+
</aside>
|
|
282
|
+
|
|
283
|
+
<footer class="app-footer">
|
|
284
|
+
<small>${footer}</small>
|
|
285
|
+
</footer>
|
|
286
|
+
|
|
287
|
+
<script>
|
|
288
|
+
window.CO_ENGRAM_I18N = ${JSON.stringify({ zh, en })};
|
|
289
|
+
window.CO_ENGRAM_LANG = ${JSON.stringify(language)};
|
|
290
|
+
${I18N_RUNTIME}
|
|
291
|
+
${DECAY_RUNTIME}
|
|
292
|
+
${APP_RUNTIME}
|
|
293
|
+
${GRAPH_RUNTIME}
|
|
294
|
+
${TABS_RUNTIME}
|
|
295
|
+
</script>
|
|
296
|
+
</body>
|
|
297
|
+
</html>`;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=html.js.map
|
package/dist/html.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewer v2 runtime — 通用前端工具:本地 state、auth、fetch、
|
|
3
|
+
* 颜色映射、时间格式化、tab 切换、drawer 管理。
|
|
4
|
+
*
|
|
5
|
+
* 这里 export 的字符串会被嵌入到 SPA HTML 的 <script> 标签内,
|
|
6
|
+
* 在浏览器端执行。完全离线,不依赖任何外部 CDN。
|
|
7
|
+
*
|
|
8
|
+
* @module @co-engram/claude-code/viewer/runtime/app
|
|
9
|
+
*/
|
|
10
|
+
export declare const APP_RUNTIME = "\n// ============================================================\n// Co-Engram Viewer v2 runtime(\u7EAF JS,\u65E0 Alpine/htmx \u4F9D\u8D56)\n// ============================================================\n\nconst CO_ENGRAM_STATE = {\n tab: 'stats',\n token: ''\n};\n\nconst CO_ENGRAM = (function() {\n 'use strict';\n\n // === \u989C\u8272 / \u7C7B\u578B \u6620\u5C04 ===\n const SYNAPSE_FAMILY = {\n extends: 'structural', part_of: 'structural', similar_to: 'structural',\n depends_on: 'causal', causes: 'causal', follows: 'causal',\n derives_from: 'evidential', contradicts: 'evidential', exemplifies: 'evidential',\n supersedes: 'temporal', consolidates: 'temporal',\n contextualizes: 'modulatory'\n };\n\n const FAMILY_COLOR = {\n structural: '#3b82f6',\n causal: '#f97316',\n evidential: '#10b981',\n temporal: '#8b5cf6',\n modulatory: '#6b7280'\n };\n const CONTRADICTS_COLOR = '#ef4444';\n\n const KIND_COLOR = {\n fact: '#10b981',\n observation: '#3b82f6',\n pattern: '#8b5cf6',\n procedure: '#f97316',\n hypothesis: '#ef4444'\n };\n\n // 12 \u79CD synapse kind \u5404\u81EA\u72EC\u7ACB\u7684\u989C\u8272(\u540C\u65CF\u4FDD\u6301\u8272\u8C03\u76F8\u8FD1,\u4F46\u660E\u5EA6\u4E0D\u540C\u4EE5\u4FBF\u533A\u5206)\n const SYNAPSE_KIND_COLOR = {\n // \u7ED3\u6784\u65CF \u00B7 \u84DD\u8272\u7CFB\n extends: '#3b82f6', // \u4E3B\u84DD\n part_of: '#60a5fa', // \u6D45\u84DD\n similar_to: '#1e40af', // \u6DF1\u84DD\n // \u56E0\u679C\u65CF \u00B7 \u6A59\u8272\u7CFB\n depends_on: '#f97316', // \u4E3B\u6A59\n causes: '#fb923c', // \u6D45\u6A59\n follows: '#c2410c', // \u6DF1\u6A59\n // \u8BC1\u636E\u65CF \u00B7 \u7EFF\u8272\u7CFB(contradicts \u72EC\u7ACB\u7EA2\u8272)\n derives_from: '#10b981', // \u4E3B\u7EFF\n exemplifies: '#6ee7b7', // \u6D45\u7EFF\n contradicts: '#ef4444', // \u7EA2(\u9AD8\u4F18\u5148\u7EA7)\n // \u65F6\u95F4\u65CF \u00B7 \u7D2B\u8272\u7CFB\n supersedes: '#8b5cf6', // \u4E3B\u7D2B\n consolidates: '#c4b5fd', // \u6D45\u7D2B\n // \u8C03\u8282\u65CF \u00B7 \u7070\u8272\u7CFB\n contextualizes: '#6b7280' // \u7070\n };\n\n // === \u672F\u8BED\u63D0\u793A(\u9F20\u6807\u60AC\u505C\u65F6\u663E\u793A) ===\n // key \u547D\u540D\u7EA6\u5B9A:{category}.{value},\u4E0E CO_ENGRAM_LABELS \u5BF9\u9F50\u3002\n const TOOLTIPS = {\n kind: {\n fact: '\u4E8B\u5B9E (fact):\u88AB\u786E\u8BA4\u6210\u7ACB\u3001\u53EF\u72EC\u7ACB\u9A8C\u8BC1\u7684\u5BA2\u89C2\u9648\u8FF0\u3002\u4F8B:\"\u9879\u76EE\u4F7F\u7528 PostgreSQL 14\"\u3002',\n observation: '\u89C2\u5BDF (observation):\u4E00\u6B21\u6027\u611F\u77E5\u5230\u7684\u4E8B\u5B9E,\u53EF\u80FD\u5C1A\u672A\u6C89\u6DC0\u4E3A\u7A33\u5B9A\u7ED3\u8BBA\u3002\u4F8B:\"\u4ECA\u5929 CI \u8DD1\u4E86 12 \u5206\u949F\"\u3002',\n pattern: '\u6A21\u5F0F (pattern):\u4ECE\u591A\u6B21\u89C2\u5BDF\u5F52\u7EB3\u51FA\u7684\u89C4\u5F8B,\u53EF\u9884\u6D4B\u672A\u6765\u884C\u4E3A\u3002\u4F8B:\"\u6BCF\u5468\u4E00\u65E9\u4E0A\u6784\u5EFA\u65F6\u95F4\u4F1A\u53D8\u957F\"\u3002',\n procedure: '\u6D41\u7A0B (procedure):\u6B65\u9AA4\u5E8F\u5217,\u6267\u884C\u540E\u53EF\u590D\u73B0\u67D0\u7ED3\u679C\u3002\u4F8B:\"\u53D1\u5E03\u524D\u9700\u8DD1 pnpm check\"\u3002',\n hypothesis: '\u5047\u8BBE (hypothesis):\u5F85\u9A8C\u8BC1\u7684\u731C\u6D4B;\u5728\u53CD\u4F8B\u51FA\u73B0\u524D\u53EF\u4F5C\u5DE5\u4F5C\u5047\u8BBE\u3002\u4F8B:\"\u6162\u67E5\u8BE2\u53EF\u80FD\u6E90\u4E8E\u7F3A\u5931\u7D22\u5F15\"\u3002'\n },\n status: {\n active: '\u6D3B\u8DC3 (active):\u8FD1\u671F\u88AB\u68C0\u7D22\u6216\u5F3A\u5316,\u5728\u53EC\u56DE\u6C60\u4E2D\u6743\u91CD\u9AD8\u3002',\n dormant: '\u4F11\u7720 (dormant):\u957F\u671F\u672A\u88AB\u68C0\u7D22,\u6743\u91CD\u5DF2\u8870\u51CF\u4F46\u672A\u9057\u5FD8\u3002',\n forgotten: '\u5DF2\u9057\u5FD8 (forgotten):\u7EF4\u62A4\u9636\u6BB5\u4E3B\u52A8\u9057\u5FD8,\u6587\u4EF6\u4ECD\u5728\u4F46\u9ED8\u8BA4\u4E0D\u53EC\u56DE\u3002',\n archived: '\u5DF2\u5F52\u6863 (archived):\u51B7\u5F52\u6863\u72B6\u6001,\u4EC5\u7528\u4E8E\u5386\u53F2\u56DE\u6EAF\u3002'\n },\n freshness: {\n fresh: '\u9C9C\u6D3B (fresh):ageDays \u2264 halfLife,\u6700\u8FD1\u88AB\u6709\u6548\u5F3A\u5316\u8FC7,\u5728\u53EC\u56DE\u6C60\u4E2D\u6743\u91CD\u6700\u9AD8\u3002',\n aging: '\u6E10\u8870 (aging):halfLife < ageDays \u2264 halfLife\u00D72,\u6743\u91CD\u6B63\u5728\u4E0B\u964D,\u5EFA\u8BAE\u5C3D\u5FEB\u5F3A\u5316\u3002',\n stale: '\u8FC7\u65F6 (stale):halfLife\u00D72 < ageDays \u2264 halfLife\u00D74,\u957F\u671F\u672A\u5F3A\u5316,\u5019\u9009\u9057\u5FD8\u5BF9\u8C61\u3002',\n forgotten: '\u9057\u5FD8 (forgotten):ageDays > halfLife\u00D74,\u9ED8\u8BA4\u79FB\u51FA\u53EC\u56DE\u6C60(\u6587\u4EF6\u4FDD\u7559,Git \u53EF\u8FFD\u6EAF)\u3002'\n },\n visibility: {\n public: '\u516C\u5F00 (public):\u6240\u6709\u4EBA/\u6240\u6709 agent \u53EF\u89C1\u3002',\n team: '\u56E2\u961F (team):\u4EC5\u540C\u56E2\u961F\u53EF\u89C1\u3002',\n private: '\u79C1\u6709 (private):\u4EC5\u521B\u5EFA\u8005\u53EF\u89C1\u3002',\n restricted: '\u53D7\u9650 (restricted):\u9700\u7279\u5B9A\u6743\u9650\u624D\u80FD\u67E5\u770B\u3002'\n },\n synapse: {\n extends: '\u6269\u5C55 (extends) \u00B7 \u7ED3\u6784\u65CF:A \u5728 B \u57FA\u7840\u4E0A\u6269\u5C55,\u7EE7\u627F B \u7684\u8BED\u4E49\u5E76\u65B0\u589E\u7EF4\u5EA6\u3002',\n part_of: '\u90E8\u5206 (part_of) \u00B7 \u7ED3\u6784\u65CF:A \u662F B \u7684\u7EC4\u6210\u90E8\u5206(B has-a A)\u3002',\n similar_to: '\u76F8\u4F3C (similar_to) \u00B7 \u7ED3\u6784\u65CF:A \u4E0E B \u8BED\u4E49\u76F8\u8FD1,\u53EF\u4E92\u6362\u6216\u4E92\u63F4\u3002',\n depends_on: '\u4F9D\u8D56 (depends_on) \u00B7 \u56E0\u679C\u65CF:A \u7684\u6210\u7ACB\u4F9D\u8D56 B(B \u662F A \u7684\u524D\u7F6E\u6761\u4EF6)\u3002',\n causes: '\u5BFC\u81F4 (causes) \u00B7 \u56E0\u679C\u65CF:A \u89E6\u53D1\u6216\u4EA7\u751F B(\u6B63\u5411\u56E0\u679C)\u3002',\n follows: '\u987A\u627F (follows) \u00B7 \u56E0\u679C\u65CF:A \u5728\u65F6\u95F4/\u903B\u8F91\u4E0A\u8DDF\u968F B(\u65E0\u5F3A\u56E0\u679C)\u3002',\n derives_from: '\u6D3E\u751F (derives_from) \u00B7 \u8BC1\u636E\u65CF:A \u4ECE B \u63A8\u5BFC\u800C\u6765(B \u662F\u4F9D\u636E)\u3002',\n contradicts: '\u77DB\u76FE (contradicts) \u00B7 \u8BC1\u636E\u65CF:A \u4E0E B \u76F8\u4E92\u51B2\u7A81,\u8FDB\u5165\u88C1\u51B3\u6D41\u7A0B\u3002',\n exemplifies: '\u4F8B\u8BC1 (exemplifies) \u00B7 \u8BC1\u636E\u65CF:A \u662F B \u7684\u5177\u4F53\u5B9E\u4F8B/\u6837\u672C\u3002',\n supersedes: '\u53D6\u4EE3 (supersedes) \u00B7 \u65F6\u95F4\u65CF:A \u53D6\u4EE3\u8FC7\u65F6\u7684 B(\u7248\u672C\u66F4\u8FED)\u3002',\n consolidates: '\u6574\u5408 (consolidates) \u00B7 \u65F6\u95F4\u65CF:A \u5408\u5E76/\u7CBE\u70BC\u4E86 B \u7684\u5185\u5BB9\u3002',\n contextualizes: '\u4E0A\u4E0B\u6587 (contextualizes) \u00B7 \u8C03\u8282\u65CF:A \u4E3A B \u63D0\u4F9B\u60C5\u5883\u80CC\u666F(\u975E\u56E0\u679C\u3001\u975E\u8BC1\u636E)\u3002'\n },\n family: {\n structural: '\u7ED3\u6784\u65CF (structural):\u63CF\u8FF0\u77E5\u8BC6\u95F4\u7684\u7EC4\u6210/\u6269\u5C55\u5173\u7CFB\u3002\u84DD\u8272\u3002',\n causal: '\u56E0\u679C\u65CF (causal):\u63CF\u8FF0\u89E6\u53D1/\u4F9D\u8D56\u5173\u7CFB\u3002\u6A59\u8272\u3002',\n evidential: '\u8BC1\u636E\u65CF (evidential):\u63CF\u8FF0\u6765\u6E90/\u51B2\u7A81\u5173\u7CFB\u3002\u7EFF\u8272(\u77DB\u76FE\u5355\u72EC\u6807\u7EA2)\u3002',\n temporal: '\u65F6\u95F4\u65CF (temporal):\u63CF\u8FF0\u7248\u672C/\u6F14\u5316\u5173\u7CFB\u3002\u7D2B\u8272\u3002',\n modulatory: '\u8C03\u8282\u65CF (modulatory):\u63CF\u8FF0\u60C5\u5883\u4E0A\u4E0B\u6587\u5173\u7CFB\u3002\u7070\u8272\u3002'\n },\n synapseDirection: {\n directional: '\u5355\u5411 (directional):A \u2192 B,\u5173\u7CFB\u4EC5\u4ECE\u6E90\u6307\u5411\u76EE\u6807\u3002',\n bidirectional: '\u53CC\u5411 (bidirectional):A \u2194 B,\u5173\u7CFB\u5BF9\u79F0\u9002\u7528\u3002'\n },\n resolution: {\n pending: '\u5F85\u5904\u7406 (pending):\u5DF2\u68C0\u6D4B\u5230\u77DB\u76FE,\u7B49\u5F85\u88C1\u51B3\u3002',\n auto_resolved: '\u5DF2\u81EA\u52A8\u88C1\u51B3 (auto_resolved):\u9636\u6BB5 1,LLM \u81EA\u52A8\u7ED9\u51FA\u88C1\u51B3\u3002',\n escalated: '\u5DF2\u5347\u7EA7 (escalated):\u9636\u6BB5 2,\u5347\u7EA7\u5230\u5F52\u5C5E\u4EBA\u88C1\u51B3\u3002',\n contested: '\u6709\u4E89\u8BAE (contested):\u9636\u6BB5 3,\u8D85\u65F6\u672A\u54CD\u5E94,\u9644\u8B66\u544A\u3002',\n resolved: '\u5DF2\u89E3\u51B3 (resolved):\u4EBA\u5DE5\u6216\u81EA\u52A8\u6700\u7EC8\u7ED3\u6848\u3002'\n },\n importance: '\u91CD\u8981\u6027 (importance):0-1 \u6570\u503C,\u8D8A\u9AD8\u5728\u53EC\u56DE\u6C60\u4E2D\u6743\u91CD\u8D8A\u5927\u3002\u7531\u521D\u59CB\u8BBE\u7F6E + \u5F3A\u5316\u4FE1\u53F7 + \u8870\u51CF\u7EFC\u5408\u5F97\u51FA\u3002',\n confidence: '\u7F6E\u4FE1\u5EA6 (confidence):0-1 \u6570\u503C,\u53CD\u6620\u8BE5\u8BB0\u5FC6\u6210\u7ACB\u7684\u53EF\u4FE1\u7A0B\u5EA6(\u4E0E\u91CD\u8981\u6027\u72EC\u7ACB)\u3002',\n retrievalCount: '\u68C0\u7D22\u6B21\u6570 (retrievalCount):\u8BE5\u8BB0\u5FC6\u88AB\u641C\u7D22/\u53EC\u56DE\u547D\u4E2D\u7684\u603B\u6B21\u6570\u3002',\n effectiveRetrievals: '\u6709\u6548\u68C0\u7D22 (effectiveRetrievals):\u547D\u4E2D\u540E\u88AB\u5B9E\u9645\u91C7\u7528(\u975E\u8FC7\u6EE4\u6389)\u7684\u6B21\u6570\u3002',\n failedUses: '\u5931\u8D25\u4F7F\u7528 (failedUses):\u547D\u4E2D\u540E\u88AB\u62A5\u544A\"\u65E0\u6548/\u8FC7\u65F6\"\u7684\u6B21\u6570\u3002\u5931\u8D25\u8FC7\u591A\u4F1A\u89E6\u53D1\u9057\u5FD8\u3002',\n reinforcementScore: '\u5F3A\u5316\u5206\u6570 (reinforcementScore):\u7D2F\u8BA1\u7684\u6B63\u5411\u5F3A\u5316\u4FE1\u53F7\u3002',\n emotionalValence: {\n positive: '\u79EF\u6781 (positive):\u8BE5\u8BB0\u5FC6\u7F16\u7801\u65F6\u5E26\u6709\u6B63\u5411\u60C5\u7EEA(\u6210\u529F/\u8D5E\u8D4F/\u89E3\u51B3)\u3002\u5F3A\u5316\u6743\u91CD\u7565\u9AD8\u3002',\n negative: '\u6D88\u6781 (negative):\u8BE5\u8BB0\u5FC6\u7F16\u7801\u65F6\u5E26\u6709\u8D1F\u5411\u60C5\u7EEA(\u5931\u8D25/\u8B66\u544A/\u53CD\u9A73)\u3002\u7528\u4E8E\u8B66\u793A\u672A\u6765\u51B3\u7B56\u3002',\n neutral: '\u4E2D\u6027 (neutral):\u7F16\u7801\u65F6\u65E0\u660E\u663E\u60C5\u7EEA\u503E\u5411,\u7EAF\u9648\u8FF0\u6027\u8BB0\u5FC6\u3002'\n },\n sourceType: {\n firsthand: '\u4E00\u624B (firsthand):\u4EB2\u5386/\u76F4\u63A5\u89C2\u6D4B,\u53EF\u4FE1\u5EA6\u6700\u9AD8\u3002',\n secondhand: '\u4E8C\u624B (secondhand):\u8F6C\u8FF0/\u6587\u6863/\u4ED6\u4EBA\u7ECF\u9A8C,\u9700\u4EA4\u53C9\u9A8C\u8BC1\u3002',\n inferred: '\u63A8\u65AD (inferred):\u4ECE\u5176\u4ED6\u8BB0\u5FC6\u5F52\u7EB3\u5F97\u51FA,\u65E0\u76F4\u63A5\u8BC1\u636E\u3002'\n },\n verification: {\n unverified: '\u672A\u9A8C\u8BC1 (unverified):\u65B0\u521B\u5EFA,\u5C1A\u672A\u901A\u8FC7\u5143\u8BA4\u77E5\u8BC4\u5206\u3002',\n plausible: '\u8C8C\u4F3C\u6210\u7ACB (plausible):overall \u2265 0.4,\u521D\u6B65\u901A\u8FC7\u4F46\u4ECD\u6709\u4E0D\u786E\u5B9A\u6027\u3002',\n probable: '\u8F83\u53EF\u80FD (probable):overall \u2265 0.6,\u7ECF\u591A\u6B21\u68C0\u7D22\u672A\u51FA\u73B0\u53CD\u4F8B\u3002',\n verified: '\u5DF2\u9A8C\u8BC1 (verified):overall \u2265 0.8 \u6216\u4EBA\u5DE5\u786E\u8BA4,\u53EF\u4F5C\u4E3A\u51B3\u7B56\u4F9D\u636E\u3002',\n refuted: '\u5DF2\u53CD\u9A73 (refuted):\u51FA\u73B0\u5F3A\u53CD\u4F8B\u6216\u5143\u8BA4\u77E5\u8BC4\u5206\u6781\u4F4E,\u4E0D\u5E94\u518D\u4F5C\u4F9D\u636E\u3002'\n },\n decayHalfLifeDays: '\u8870\u9000\u534A\u8870\u671F (decayHalfLifeDays):importance \u6BCF\u7ECF\u8FC7 N \u5929\u8870\u51CF\u4E00\u534A\u3002null \u8868\u793A\u6C38\u4E0D\u8870\u9000\u3002',\n lastEffectiveAt: '\u6700\u8FD1\u4E00\u6B21\u6709\u6548 (lastEffectiveAt):\u8BE5\u8BB0\u5FC6\u6700\u540E\u4E00\u6B21\u88AB\u5B9E\u9645\u91C7\u7EB3/\u5F3A\u5316\u6210\u529F\u7684\u65F6\u95F4\u6233\u3002',\n evidenceCount: '\u8BC1\u636E\u6570\u91CF (evidenceCount):\u652F\u6491\u8BE5\u8BB0\u5FC6\u7684\u72EC\u7ACB\u8BC1\u636E\u6761\u6570(\u7A81\u89E6 + \u5143\u6570\u636E)\u3002',\n encodingContext: '\u8BB0\u5FC6\u4EA7\u751F\u60C5\u5883 (encodingContext):\u8BB0\u5FC6\u521B\u5EFA\u65F6\u7684\u80CC\u666F\u63CF\u8FF0,\u7528\u4E8E\u60C5\u5883\u4F9D\u8D56\u56DE\u5FC6\u3002',\n perspective: '\u89C6\u89D2 (perspective):\u8BE5\u8BB0\u5FC6\u7684\u89C2\u5BDF\u89C6\u89D2\u6807\u8BC6(\u591A\u89C6\u89D2\u4FDD\u7559\u673A\u5236,spec \u00A75.3)\u3002',\n importanceVector: '\u591A\u7EF4\u91CD\u8981\u6027 (importanceVector):\u628A importance \u62C6\u89E3\u4E3A 5 \u4E2A\u72EC\u7ACB\u7EF4\u5EA6,\u4FBF\u4E8E\u7CBE\u7EC6\u5316\u8C03\u63A7\u3002',\n importanceDim: {\n personal: '\u4E2A\u4EBA\u7EF4\u5EA6 (personal):\u5BF9\u5F53\u524D\u7528\u6237\u7684\u5DE5\u4F5C\u5173\u8054\u5EA6\u3002',\n team: '\u56E2\u961F\u7EF4\u5EA6 (team):\u5BF9\u6574\u4E2A\u56E2\u961F\u7684\u534F\u4F5C\u4EF7\u503C\u3002',\n project: '\u9879\u76EE\u7EF4\u5EA6 (project):\u4E0E\u5F53\u524D\u9879\u76EE\u76EE\u6807\u7684\u5951\u5408\u5EA6\u3002',\n network: '\u7F51\u7EDC\u7EF4\u5EA6 (network):\u57FA\u4E8E\u7A81\u89E6\u8FDE\u63A5\u6570\u6D3E\u751F,\u53CD\u6620\u77E5\u8BC6\u56FE\u8C31\u4E2D\u5FC3\u6027\u3002',\n temporal: '\u65F6\u95F4\u7EF4\u5EA6 (temporal):\u57FA\u4E8E lastEffectiveAt + \u534A\u8870\u671F\u6D3E\u751F,\u8FD1\u671F\u5F3A\u5316\u7684\u5F97\u5206\u9AD8\u3002'\n }\n };\n\n function tip(key) {\n // \u652F\u6301 'kind.fact' / 'importance' \u4E24\u79CD key \u5F62\u5F0F\n const parts = key.split('.');\n let v = TOOLTIPS;\n for (const p of parts) {\n if (v && typeof v === 'object' && p in v) v = v[p];\n else return '';\n }\n if (typeof v !== 'string') return '';\n return ' title=\"' + v.replaceAll('\"', '"') + '\"';\n }\n\n function synapseFamily(kind) {\n return SYNAPSE_FAMILY[kind] || 'modulatory';\n }\n function familyColor(family) {\n return FAMILY_COLOR[family] || FAMILY_COLOR.modulatory;\n }\n function kindColor(kind) {\n return KIND_COLOR[kind] || '#6b7280';\n }\n function edgeColor(kind) {\n // \u4F18\u5148\u7528 12 \u79CD\u72EC\u7ACB\u989C\u8272,\u8BA9\u6BCF\u79CD kind \u89C6\u89C9\u4E0A\u53EF\u533A\u5206\n if (SYNAPSE_KIND_COLOR[kind]) return SYNAPSE_KIND_COLOR[kind];\n return kind === 'contradicts' ? CONTRADICTS_COLOR : familyColor(synapseFamily(kind));\n }\n\n // === \u65F6\u95F4\u683C\u5F0F\u5316 ===\n function relativeTime(iso) {\n if (!iso) return '';\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n const now = Date.now();\n const diffSec = Math.max(0, Math.floor((now - then) / 1000));\n if (diffSec < 60) return diffSec + 's ago';\n if (diffSec < 3600) return Math.floor(diffSec / 60) + 'm ago';\n if (diffSec < 86400) return Math.floor(diffSec / 3600) + 'h ago';\n if (diffSec < 86400 * 7) return Math.floor(diffSec / 86400) + 'd ago';\n return new Date(iso).toLocaleDateString();\n }\n\n function escapeHtml(s) {\n if (s == null) return '';\n return String(s)\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n }\n\n function importanceBar(value) {\n const v = Math.max(0, Math.min(1, Number(value) || 0));\n const pct = Math.round(v * 100);\n return '<span class=\"importance-bar\" title=\"importance=' + pct + '%\"><span style=\"width:' + pct + '%\"></span></span>';\n }\n\n // === Markdown \u6E32\u67D3(\u7528\u4E8E engram/synapse \u5185\u5BB9\u663E\u793A)===\n // marked.parse \u628A markdown \u8F6C HTML,DOMPurify.sanitize \u6D88\u6BD2\u540E\u8FD4\u56DE\u3002\n // \u9650\u5B9A ALLOWED_TAGS \u9632\u6B62 XSS(engram \u5185\u5BB9\u6765\u81EA LLM/\u7528\u6237,\u53EF\u80FD\u542B\u6076\u610F\u811A\u672C)\u3002\n // \u4E24\u4E2A vendor lib \u90FD\u901A\u8FC7 vendor/*.ts inline,\u5B8C\u5168\u79BB\u7EBF\u3002\n function renderMarkdown(md) {\n if (md == null) return '';\n var input = String(md);\n if (input.trim().length === 0) return '';\n if (typeof window.marked === 'undefined' || typeof window.DOMPurify === 'undefined') {\n // vendor \u672A\u52A0\u8F7D,\u964D\u7EA7\u4E3A escape \u540E\u7EAF\u6587\u672C\n return '<p>' + escapeHtml(input) + '</p>';\n }\n try {\n var html = window.marked.parse(input, { breaks: true, gfm: true });\n return window.DOMPurify.sanitize(html, {\n ALLOWED_TAGS: [\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'p', 'strong', 'em', 'del', 'code', 'pre',\n 'ul', 'ol', 'li', 'blockquote', 'a', 'hr', 'br',\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\n 'img', 'span'\n ],\n ALLOWED_ATTR: ['href', 'title', 'src', 'alt'],\n ALLOW_DATA_ATTR: false\n });\n } catch (e) {\n console.error('[co-engram] markdown render failed:', e);\n return '<p>' + escapeHtml(input) + '</p>';\n }\n }\n\n // === Auth / fetch wrapper ===\n function getToken() {\n return CO_ENGRAM_STATE.token || '';\n }\n function setToken(v) {\n CO_ENGRAM_STATE.token = v || '';\n try { localStorage.setItem('co-engram-viewer-token', CO_ENGRAM_STATE.token); } catch {}\n }\n function loadToken() {\n try { CO_ENGRAM_STATE.token = localStorage.getItem('co-engram-viewer-token') || ''; } catch {}\n }\n function authHeaders() {\n const t = getToken();\n return t ? { Authorization: 'Bearer ' + t } : {};\n }\n async function apiGet(url) {\n const r = await fetch(url, { headers: Object.assign({}, authHeaders(), { Accept: 'application/json' }) });\n if (!r.ok) throw new Error('GET ' + url + ' \u2192 ' + r.status);\n return r.json();\n }\n async function apiJson(url, method, body) {\n const r = await fetch(url, {\n method: method,\n headers: Object.assign({}, authHeaders(), { 'Content-Type': 'application/json' }),\n body: body == null ? undefined : JSON.stringify(body)\n });\n if (!r.ok) throw new Error(method + ' ' + url + ' \u2192 ' + r.status);\n return r.json().catch(function() { return {}; });\n }\n\n // === Tab \u5207\u6362 ===\n function showTab(name) {\n CO_ENGRAM_STATE.tab = name;\n document.querySelectorAll('.tab').forEach(function(b) {\n b.classList.toggle('active', b.dataset.tab === name);\n });\n document.querySelectorAll('section.tab-panel').forEach(function(s) {\n s.classList.toggle('active', s.dataset.tab === name);\n });\n CO_ENGRAM.onTabEnter(name);\n }\n\n // === Drawer (right side detail panel) ===\n function openDrawer(html) {\n const drawer = document.getElementById('detail-drawer');\n if (!drawer) return;\n drawer.querySelector('.drawer-body').innerHTML = html;\n drawer.classList.add('open');\n }\n function closeDrawer() {\n const drawer = document.getElementById('detail-drawer');\n if (drawer) drawer.classList.remove('open');\n }\n\n // === Audit action \u5206\u7C7B ===\n function auditActionClass(action) {\n if (action === 'contradicted') return 'audit-contradicted';\n if (action === 'retrieve_hit' || action === 'retrieve_effective' || action === 'retrieve_inconclusive') return 'audit-effective';\n if (action === 'propose' || action === 'accept' || action === 'dismiss') return 'audit-proposal';\n return 'audit-state';\n }\n\n // === \u6CE8\u518C\u8868 ===\n const TAB_HANDLERS = {};\n function on(name, fn) { TAB_HANDLERS[name] = fn; }\n function onTabEnter(name) {\n const h = TAB_HANDLERS[name];\n if (h) {\n try {\n const result = h();\n if (result && typeof result.catch === 'function') {\n result.catch(function(e) {\n console.error('[co-engram] tab handler async failed:', e);\n });\n }\n } catch (e) {\n console.error('[co-engram] tab handler failed:', e);\n }\n }\n }\n\n // \u6E05\u7A7A stats tab \u7684\u641C\u7D22\u7ED3\u679C,\u56DE\u5230\u9ED8\u8BA4\u7EDF\u8BA1\u89C6\u56FE\n function clearSearch() {\n var input = document.getElementById('search-input');\n if (input) input.value = '';\n var resultsEl = document.getElementById('search-results');\n if (resultsEl) {\n resultsEl.style.display = 'none';\n resultsEl.innerHTML = '';\n }\n }\n\n return {\n SYNAPSE_FAMILY: SYNAPSE_FAMILY, FAMILY_COLOR: FAMILY_COLOR,\n KIND_COLOR: KIND_COLOR, CONTRADICTS_COLOR: CONTRADICTS_COLOR,\n SYNAPSE_KIND_COLOR: SYNAPSE_KIND_COLOR,\n TOOLTIPS: TOOLTIPS,\n synapseFamily: synapseFamily, familyColor: familyColor,\n kindColor: kindColor, edgeColor: edgeColor,\n relativeTime: relativeTime, escapeHtml: escapeHtml, importanceBar: importanceBar,\n renderMarkdown: renderMarkdown,\n tip: tip,\n getToken: getToken, setToken: setToken, loadToken: loadToken,\n authHeaders: authHeaders, apiGet: apiGet, apiJson: apiJson,\n showTab: showTab, openDrawer: openDrawer, closeDrawer: closeDrawer,\n clearSearch: clearSearch,\n auditActionClass: auditActionClass,\n on: on, onTabEnter: onTabEnter\n };\n})();\n\nwindow.CO_ENGRAM = CO_ENGRAM;\n\ndocument.addEventListener('DOMContentLoaded', function() {\n // \u52A0\u8F7D\u5DF2\u4FDD\u5B58\u7684 token\n CO_ENGRAM.loadToken();\n // \u540C\u6B65\u5230 token-input(\u5982\u679C\u5B58\u5728)\n var tokenInput = document.getElementById('token-input');\n if (tokenInput) {\n tokenInput.value = CO_ENGRAM.getToken();\n tokenInput.addEventListener('input', function(ev) {\n CO_ENGRAM.setToken(ev.target.value);\n });\n }\n\n // tab \u70B9\u51FB\u5207\u6362\n document.querySelectorAll('.tab').forEach(function(btn) {\n btn.addEventListener('click', function() { CO_ENGRAM.showTab(btn.dataset.tab); });\n });\n // \u9ED8\u8BA4\u663E\u793A stats\n CO_ENGRAM.showTab('stats');\n\n // \u641C\u7D22\u680F:ID \u7ED1\u5B9A(\u4EC5\u5728 stats tab \u5185)\n var searchForm = document.getElementById('search-form');\n if (searchForm) {\n searchForm.addEventListener('submit', function(ev) {\n ev.preventDefault();\n var input = document.getElementById('search-input');\n var q = (input && input.value) ? input.value.trim() : '';\n if (!q) return;\n var resultsEl = document.getElementById('search-results');\n if (!resultsEl) return;\n resultsEl.style.display = 'block';\n resultsEl.innerHTML = '<div class=\"empty\">Searching...</div>';\n fetch('/api/search?q=' + encodeURIComponent(q), { headers: CO_ENGRAM.authHeaders() })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (!data.results || data.results.length === 0) {\n resultsEl.innerHTML = '<div class=\"empty\">\u65E0\u5339\u914D\u7ED3\u679C</div>';\n return;\n }\n var L = window.CO_ENGRAM_LABELS || {};\n var kindLabelMap = L.kind || {};\n resultsEl.innerHTML = '<div class=\"grid cols-3\">' + data.results.map(function(r) {\n var e = r.entry || r.engram || r;\n var id = CO_ENGRAM.escapeHtml(e.id || '');\n var title = CO_ENGRAM.escapeHtml(e.title || e.id || '');\n var kind = e.kind || '';\n var kindLabel = kindLabelMap[kind] || kind || '\u2014';\n var kindTip = CO_ENGRAM.tip ? CO_ENGRAM.tip('kind.' + kind) : '';\n return '<div class=\"card\">'\n + '<div class=\"card-title\" onclick=\"CO_ENGRAM_ENGRAMS.open(\\'' + id + '\\')\">' + title + '</div>'\n + '<div><span class=\"chip kind-' + kind + '\"' + kindTip + '>' + CO_ENGRAM.escapeHtml(kindLabel) + '</span></div>'\n + '</div>';\n }).join('') + '</div>';\n })\n .catch(function(err) {\n resultsEl.innerHTML = '<div class=\"empty\">Search failed: ' + CO_ENGRAM.escapeHtml(String(err.message || err)) + '</div>';\n });\n });\n }\n\n // drawer close\n var drawerClose = document.querySelector('#detail-drawer .drawer-close');\n if (drawerClose) drawerClose.addEventListener('click', function() { CO_ENGRAM.closeDrawer(); });\n // ESC \u5173 drawer\n document.addEventListener('keydown', function(e) {\n if (e.key === 'Escape') CO_ENGRAM.closeDrawer();\n });\n});\n";
|
|
11
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/runtime/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,WAAW,ivvBA0avB,CAAC"}
|