@takagaki/cortex-decisions-viewer 0.4.24 → 0.4.25
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/dist/render.js +180 -17
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -17,6 +17,8 @@ function renderSite(site) {
|
|
|
17
17
|
// ※ 投入フォーム設定 site.intake は build.ts が --intake-url/--intake-token/--project-key から付与する。
|
|
18
18
|
const dataJson = JSON.stringify(site).replace(/</g, "\\u003c");
|
|
19
19
|
const safeTitle = escapeHtml(site.title);
|
|
20
|
+
// 接続マップ(操作タブ最上部のSVG図)。ビルド時に生成し<template>で埋め込む。fleet情報が無ければ出さない
|
|
21
|
+
const connMap = renderConnectionMap(site);
|
|
20
22
|
const githubBtn = site.repoBaseUrl
|
|
21
23
|
? `<a class="gh-btn" href="${escapeHtml(site.repoBaseUrl)}" target="_blank" rel="noopener" title="コンテキストリポジトリをGitHubで開く">${GITHUB_ICON}</a>`
|
|
22
24
|
: "";
|
|
@@ -45,7 +47,7 @@ function renderSite(site) {
|
|
|
45
47
|
<footer class="site-footer">
|
|
46
48
|
<span id="footer-meta"></span>
|
|
47
49
|
</footer>
|
|
48
|
-
<script type="application/json" id="site-data">${dataJson}</script>
|
|
50
|
+
${connMap ? `<template id="conn-map">${connMap}</template>\n` : ""}<script type="application/json" id="site-data">${dataJson}</script>
|
|
49
51
|
<script>${CLIENT_JS}</script>
|
|
50
52
|
</body>
|
|
51
53
|
</html>`;
|
|
@@ -58,6 +60,106 @@ function escapeHtml(s) {
|
|
|
58
60
|
.replace(/>/g, ">")
|
|
59
61
|
.replace(/"/g, """);
|
|
60
62
|
}
|
|
63
|
+
/** 接続マップの能力定義(表示順・絵文字は詳細カードの流儀を踏襲) */
|
|
64
|
+
const CM_CAPS = [
|
|
65
|
+
{ cap: "課題管理", icon: "📋", pipe: /sync-backlog/ },
|
|
66
|
+
{ cap: "会議", icon: "🎙", pipe: /ingest-minutes/ },
|
|
67
|
+
{ cap: "共有資料", icon: "📁", pipe: /sync-materials/ },
|
|
68
|
+
{ cap: "デザイン", icon: "🎨", pipe: /sync-designs|update-design-notes/ },
|
|
69
|
+
{ cap: "チャット", icon: "💬", pipe: null },
|
|
70
|
+
{ cap: "開発", icon: "🐙", pipe: null },
|
|
71
|
+
];
|
|
72
|
+
/** ツールid→表示名(接続マップのノードラベル用) */
|
|
73
|
+
const CM_TOOL_LABEL = {
|
|
74
|
+
backlog: "Backlog",
|
|
75
|
+
jira: "Jira",
|
|
76
|
+
"google-meet": "Google Meet",
|
|
77
|
+
teams: "Teams",
|
|
78
|
+
"google-drive": "Google Drive",
|
|
79
|
+
box: "Box",
|
|
80
|
+
local: "ローカル",
|
|
81
|
+
figma: "Figma",
|
|
82
|
+
slack: "Slack",
|
|
83
|
+
github: "GitHub",
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* 接続マップ(データソースとの接続を視覚化したSVG)をビルド時に生成する。
|
|
87
|
+
* 上段に6アダプター(能力)ノード、下中央にリポジトリノード、間をベジェの破線で結ぶ。
|
|
88
|
+
* 状態: 健全=色付き+流れるアニメーション / 問題あり=黄+⚠️ / 未使用=グレー。
|
|
89
|
+
* fleet-status由来の情報が何も無ければ null(マップを出さない・後方互換)。
|
|
90
|
+
*/
|
|
91
|
+
function renderConnectionMap(site) {
|
|
92
|
+
const internals = site.internalSources ?? [];
|
|
93
|
+
const externals = site.externalSources ?? [];
|
|
94
|
+
const pipes = site.pipelines ?? [];
|
|
95
|
+
const tools = site.tools ?? {};
|
|
96
|
+
if (!internals.length && !externals.length && !pipes.length && !Object.keys(tools).length) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const slackExts = externals.filter((s) => s.type === "slack");
|
|
100
|
+
const ghExts = externals.filter((s) => s.type.startsWith("github"));
|
|
101
|
+
const nodes = [];
|
|
102
|
+
const links = [];
|
|
103
|
+
const W = 960;
|
|
104
|
+
const REPO_X = W / 2;
|
|
105
|
+
CM_CAPS.forEach((c, i) => {
|
|
106
|
+
const cx = 80 + i * 160;
|
|
107
|
+
const toolVal = tools[c.cap];
|
|
108
|
+
const internal = internals.find((s) => s.kind === c.cap);
|
|
109
|
+
const isLive = c.cap === "チャット" || c.cap === "開発";
|
|
110
|
+
const liveExts = c.cap === "チャット" ? slackExts : c.cap === "開発" ? ghExts : [];
|
|
111
|
+
// 使用中か: tools宣言がnone以外 / internalSourceが存在(enabled:false除く) / ライブ参照の実体がある
|
|
112
|
+
const used = (toolVal != null && toolVal !== "none") ||
|
|
113
|
+
(internal != null && internal.enabled !== false) ||
|
|
114
|
+
(isLive && liveExts.length > 0);
|
|
115
|
+
const off = toolVal === "none" || internal?.enabled === false || !used;
|
|
116
|
+
// 問題あり: ライブ参照はgate≠okが1件以上、同期ソースは対応パイプラインの直近runがfailure
|
|
117
|
+
const warn = !off &&
|
|
118
|
+
(isLive
|
|
119
|
+
? liveExts.some((s) => s.gate !== "ok")
|
|
120
|
+
: pipes.some((p) => c.pipe != null && c.pipe.test(p.id) && p.applicable !== false && p.lastConclusion === "failure"));
|
|
121
|
+
const state = off ? "cm-off" : warn ? "cm-warn" : "cm-ok";
|
|
122
|
+
const toolName = toolVal && toolVal !== "none"
|
|
123
|
+
? CM_TOOL_LABEL[toolVal] ?? toolVal
|
|
124
|
+
: internal
|
|
125
|
+
? CM_TOOL_LABEL[internal.tool] ?? internal.tool
|
|
126
|
+
: isLive && liveExts.length
|
|
127
|
+
? CM_TOOL_LABEL[c.cap === "チャット" ? "slack" : "github"]
|
|
128
|
+
: c.cap;
|
|
129
|
+
const label = `${c.icon} ${off && (toolVal === "none" || !toolName) ? c.cap : toolName}`;
|
|
130
|
+
// ノード下の補足: 未使用 / ライブ参照 / 最終同期(時刻はクライアントでローカルタイム整形するため data-ts で渡す)
|
|
131
|
+
let sub = "";
|
|
132
|
+
if (off) {
|
|
133
|
+
sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle">未使用</text>`;
|
|
134
|
+
}
|
|
135
|
+
else if (isLive) {
|
|
136
|
+
sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle">ライブ参照</text>`;
|
|
137
|
+
}
|
|
138
|
+
else if (internal?.lastSync) {
|
|
139
|
+
sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle" data-ts="${escapeHtml(internal.lastSync)}"></text>`;
|
|
140
|
+
}
|
|
141
|
+
const badge = warn ? `<text class="cm-badge" x="${cx + 62}" y="26" text-anchor="middle">⚠️</text>` : "";
|
|
142
|
+
// クリックでページ内の該当詳細カード(アンカー)へスクロールする
|
|
143
|
+
const anchor = `ds-${c.cap}`;
|
|
144
|
+
nodes.push(`<g class="cm-node ${state}" data-target="${anchor}" tabindex="0" role="link" aria-label="${escapeHtml(c.cap)}の詳細へ">` +
|
|
145
|
+
`<rect x="${cx - 74}" y="16" width="148" height="54" rx="10"></rect>` +
|
|
146
|
+
`<text class="cm-label" x="${cx}" y="40" text-anchor="middle">${escapeHtml(label)}</text>` +
|
|
147
|
+
sub +
|
|
148
|
+
badge +
|
|
149
|
+
`</g>`);
|
|
150
|
+
links.push(`<path class="cm-link ${state}" d="M ${cx} 72 C ${cx} 150, ${REPO_X} 160, ${REPO_X} 238"></path>`);
|
|
151
|
+
});
|
|
152
|
+
const repoTitle = site.title.length > 24 ? site.title.slice(0, 23) + "…" : site.title;
|
|
153
|
+
const repo = `<g class="cm-repo">` +
|
|
154
|
+
`<rect x="${REPO_X - 170}" y="240" width="340" height="52" rx="12"></rect>` +
|
|
155
|
+
`<text class="cm-repo-label" x="${REPO_X}" y="272" text-anchor="middle">📦 コンテキストリポジトリ(${escapeHtml(repoTitle)})</text>` +
|
|
156
|
+
`</g>`;
|
|
157
|
+
return (`<svg class="cm-svg" viewBox="0 0 ${W} 312" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="データソースとの接続マップ">` +
|
|
158
|
+
links.join("") +
|
|
159
|
+
nodes.join("") +
|
|
160
|
+
repo +
|
|
161
|
+
`</svg>`);
|
|
162
|
+
}
|
|
61
163
|
const CSS = `
|
|
62
164
|
:root {
|
|
63
165
|
--bg: #f6f7f9;
|
|
@@ -360,6 +462,30 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
360
462
|
.ds-group > .ds-list { margin-top: 8px; padding-left: 12px; border-left: 2px solid var(--border); }
|
|
361
463
|
.badge.ds-sum-ok { background: #e9f7ef; color: #1f7a45; }
|
|
362
464
|
.badge.ds-sum-warn { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
|
|
465
|
+
/* ---- 接続マップ(操作タブ最上部のSVG図) ---- */
|
|
466
|
+
.conn-map { overflow-x: auto; margin: 4px 0 20px; }
|
|
467
|
+
.conn-map .cm-svg { display: block; min-width: 760px; width: 100%; height: auto; font-family: inherit; }
|
|
468
|
+
.cm-node { cursor: pointer; outline: none; }
|
|
469
|
+
.cm-node rect { fill: var(--surface); stroke: var(--border); stroke-width: 1.5; }
|
|
470
|
+
.cm-node:hover rect, .cm-node:focus rect { stroke: var(--accent); }
|
|
471
|
+
.cm-node.cm-ok rect { stroke: var(--accent); }
|
|
472
|
+
.cm-node.cm-warn rect { fill: #fffbeb; stroke: #fcd34d; }
|
|
473
|
+
.cm-node.cm-off rect { fill: #f8f9fa; stroke: #d1d5db; stroke-dasharray: 4 3; }
|
|
474
|
+
.cm-label { font-size: 13.5px; font-weight: 600; fill: var(--text); }
|
|
475
|
+
.cm-node.cm-warn .cm-label { fill: #92400e; }
|
|
476
|
+
.cm-node.cm-off .cm-label { fill: #9ca3af; }
|
|
477
|
+
.cm-node.cm-off text { filter: grayscale(1); opacity: .8; }
|
|
478
|
+
.cm-sync { font-size: 10.5px; fill: var(--muted); font-variant-numeric: tabular-nums; }
|
|
479
|
+
.cm-node.cm-off .cm-sync { fill: #9ca3af; }
|
|
480
|
+
.cm-badge { font-size: 13px; }
|
|
481
|
+
.cm-link { fill: none; stroke-width: 1.6; stroke-dasharray: 6 6; stroke: #94a3b8; }
|
|
482
|
+
.cm-link.cm-ok { stroke: var(--accent); animation: cm-flow 1.1s linear infinite; }
|
|
483
|
+
.cm-link.cm-warn { stroke: #d97706; }
|
|
484
|
+
.cm-link.cm-off { stroke: #d1d5db; opacity: .6; }
|
|
485
|
+
@keyframes cm-flow { to { stroke-dashoffset: -24; } }
|
|
486
|
+
@media (prefers-reduced-motion: reduce) { .cm-link.cm-ok { animation: none; } }
|
|
487
|
+
.cm-repo rect { fill: var(--accent-weak); stroke: var(--accent); stroke-width: 1.5; }
|
|
488
|
+
.cm-repo-label { font-size: 14px; font-weight: 700; fill: #1e3a8a; }
|
|
363
489
|
/* 未使用(非活性)行: アダプターとして存在するが、この案件では設定されていないもの */
|
|
364
490
|
.ds-card.ds-inactive, .ds-pipe.ds-inactive { background: #f8f9fa; border-style: dashed; box-shadow: none; }
|
|
365
491
|
.ds-inactive .ds-icon { filter: grayscale(1); opacity: .55; }
|
|
@@ -516,8 +642,11 @@ const CLIENT_JS = `
|
|
|
516
642
|
if (SITE.graph && SITE.graph.nodes.length > 0) {
|
|
517
643
|
TABS.push({ key: "graph", label: "ナレッジグラフ" });
|
|
518
644
|
}
|
|
519
|
-
// Cortex
|
|
520
|
-
|
|
645
|
+
// Cortex自体を操作するアクションと、接続・自動化の情報の受け皿(取り込み・接続マップ・データソース)。
|
|
646
|
+
// 投入フォーム設定 or fleet-status由来の接続情報のどちらかがあれば出す。
|
|
647
|
+
var HAS_FLEET = !!((SITE.internalSources || []).length || (SITE.externalSources || []).length ||
|
|
648
|
+
(SITE.pipelines || []).length || SITE.tools);
|
|
649
|
+
if (SITE.intake || HAS_FLEET) {
|
|
521
650
|
TABS.push({ key: "ops", label: "操作" });
|
|
522
651
|
}
|
|
523
652
|
|
|
@@ -700,7 +829,6 @@ const CLIENT_JS = `
|
|
|
700
829
|
var homeBody = el("div", { class: "body" });
|
|
701
830
|
app.appendChild(homeBody);
|
|
702
831
|
loadBody(h, homeBody);
|
|
703
|
-
renderDataSources();
|
|
704
832
|
}
|
|
705
833
|
|
|
706
834
|
// ---------- データソースと自動化(fleet-status.json 由来。無ければ非表示) ----------
|
|
@@ -775,7 +903,8 @@ const CLIENT_JS = `
|
|
|
775
903
|
if (inactive) {
|
|
776
904
|
col.appendChild(el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }));
|
|
777
905
|
}
|
|
778
|
-
|
|
906
|
+
// id は接続マップのノードclickの飛び先(アンカー)
|
|
907
|
+
var card = el("div", { class: "ds-card" + (inactive ? " ds-inactive" : ""), id: "ds-" + (s.kind || "") }, [
|
|
779
908
|
el("div", { class: "ds-card-main" }, [
|
|
780
909
|
el("span", { class: "ds-icon", text: DS_KIND_ICON[s.kind] || "📁" }),
|
|
781
910
|
el("span", { class: "ds-type", text: s.kind || "" }),
|
|
@@ -804,9 +933,15 @@ const CLIENT_JS = `
|
|
|
804
933
|
var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
|
|
805
934
|
var wrap = el("div", { class: "ds-list" });
|
|
806
935
|
// チャット未使用: Slackグループの位置(先頭)に「存在するが未設定」のグレー行を出す
|
|
807
|
-
if (chatOff) wrap.appendChild(inactiveSourceRow("💬", "チャット"));
|
|
936
|
+
if (chatOff) wrap.appendChild(inactiveSourceRow("💬", "チャット", "ds-チャット"));
|
|
937
|
+
var chatAnchored = chatOff;
|
|
938
|
+
var devAnchored = devOff;
|
|
808
939
|
groups.forEach(function (g) {
|
|
809
940
|
var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
|
|
941
|
+
// 接続マップのノードclickの飛び先(アンカー)。チャット=Slack群の先頭・開発=GitHub群の先頭
|
|
942
|
+
var anchorId = null;
|
|
943
|
+
if (g.type === "slack" && !chatAnchored) { anchorId = "ds-チャット"; chatAnchored = true; }
|
|
944
|
+
if (g.type.indexOf("github") === 0 && !devAnchored) { anchorId = "ds-開発"; devAnchored = true; }
|
|
810
945
|
var cards = el("div", { class: "ds-list" });
|
|
811
946
|
g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
|
|
812
947
|
if (g.items.length >= 4) {
|
|
@@ -819,13 +954,16 @@ const CLIENT_JS = `
|
|
|
819
954
|
? el("span", { class: "badge ds-sum-ok", text: "接続OK" })
|
|
820
955
|
: el("span", { class: "badge ds-sum-warn", text: "⚠️ " + bad + "件に問題" }),
|
|
821
956
|
]);
|
|
822
|
-
|
|
957
|
+
var det = el("details", { class: "ds-group" }, [sum, cards]);
|
|
958
|
+
if (anchorId) det.id = anchorId;
|
|
959
|
+
wrap.appendChild(det);
|
|
823
960
|
} else {
|
|
961
|
+
if (anchorId && cards.firstChild) cards.firstChild.id = anchorId;
|
|
824
962
|
while (cards.firstChild) wrap.appendChild(cards.firstChild);
|
|
825
963
|
}
|
|
826
964
|
});
|
|
827
965
|
// 開発未使用: GitHubグループの位置(末尾)にグレー行を出す
|
|
828
|
-
if (devOff) wrap.appendChild(inactiveSourceRow("🐙", "GitHub"));
|
|
966
|
+
if (devOff) wrap.appendChild(inactiveSourceRow("🐙", "GitHub", "ds-開発"));
|
|
829
967
|
sec.appendChild(wrap);
|
|
830
968
|
}
|
|
831
969
|
|
|
@@ -859,8 +997,8 @@ const CLIENT_JS = `
|
|
|
859
997
|
app.appendChild(sec);
|
|
860
998
|
}
|
|
861
999
|
// 未使用能力のプレースホルダ行(アダプターとして存在するが、この案件では設定されていないもの)
|
|
862
|
-
function inactiveSourceRow(icon, typeLabel) {
|
|
863
|
-
return el("div", { class: "ds-card ds-inactive" }, [
|
|
1000
|
+
function inactiveSourceRow(icon, typeLabel, anchorId) {
|
|
1001
|
+
return el("div", { class: "ds-card ds-inactive", id: anchorId }, [
|
|
864
1002
|
el("div", { class: "ds-card-main" }, [
|
|
865
1003
|
el("span", { class: "ds-icon", text: icon }),
|
|
866
1004
|
el("span", { class: "ds-type", text: typeLabel }),
|
|
@@ -887,14 +1025,39 @@ const CLIENT_JS = `
|
|
|
887
1025
|
]);
|
|
888
1026
|
}
|
|
889
1027
|
|
|
890
|
-
// ----------
|
|
1028
|
+
// ---------- 操作タブ(接続マップ・データソースと自動化・取り込み。今後この受け皿に機能を足す) ----------
|
|
891
1029
|
function renderOps() {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1030
|
+
renderConnMap();
|
|
1031
|
+
renderDataSources();
|
|
1032
|
+
if (SITE.intake) {
|
|
1033
|
+
app.appendChild(el("p", { class: "ops-intro", text: "Cortex にコンテキストを取り込む操作です。ファイルを選ぶかテキストを貼って送信すると、自動で取り込まれます(数分で反映)。" }));
|
|
1034
|
+
app.appendChild(el("div", { class: "section-label", text: "取り込み" }));
|
|
1035
|
+
var grid = el("div", { class: "ops-grid" });
|
|
1036
|
+
grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
|
|
1037
|
+
grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
|
|
1038
|
+
app.appendChild(grid);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
|
|
1043
|
+
function renderConnMap() {
|
|
1044
|
+
var tpl = document.getElementById("conn-map");
|
|
1045
|
+
if (!tpl) return; // fleet情報が無い(旧fleet-status等)→ マップなし
|
|
1046
|
+
var wrap = el("div", { class: "conn-map" });
|
|
1047
|
+
wrap.appendChild(tpl.content.cloneNode(true));
|
|
1048
|
+
// 最終同期の時刻はブラウザのローカルタイムで整形(詳細カードの表示と揃える)
|
|
1049
|
+
wrap.querySelectorAll("[data-ts]").forEach(function (t) {
|
|
1050
|
+
var s = dsFmtTime(t.getAttribute("data-ts"));
|
|
1051
|
+
if (s) t.textContent = "最終同期 " + s;
|
|
1052
|
+
});
|
|
1053
|
+
// ノードclickで、下の該当詳細カード(アンカー)へスクロール
|
|
1054
|
+
wrap.addEventListener("click", function (ev) {
|
|
1055
|
+
var g = ev.target && ev.target.closest ? ev.target.closest("[data-target]") : null;
|
|
1056
|
+
if (!g) return;
|
|
1057
|
+
var target = document.getElementById(g.getAttribute("data-target"));
|
|
1058
|
+
if (target && target.scrollIntoView) target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1059
|
+
});
|
|
1060
|
+
app.appendChild(wrap);
|
|
898
1061
|
}
|
|
899
1062
|
function opsCard(icon, title, desc, kind, btnLabel) {
|
|
900
1063
|
var btn = el("button", { class: "ops-card-btn", text: "+ " + btnLabel });
|
package/package.json
CHANGED