@unson/brainbase-mcp 0.41.10 → 0.41.13
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 +1 -1
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/judgment-history.js +76 -3
- package/dist/local-web-host.js +19 -2
- package/package.json +6 -2
- package/ui/area-nav.css +79 -0
- package/ui/area-nav.js +249 -0
- package/ui/foundation-overview.css +9 -0
- package/ui/foundation-overview.js +75 -5
- package/ui/graph-ontology-view.css +88 -0
- package/ui/graph-ontology-view.js +248 -0
- package/ui/graph-own-share.js +167 -0
- package/ui/graph-registry-view.css +272 -50
- package/ui/graph-registry-view.js +539 -456
- package/ui/graph-view-shared.css +32 -0
- package/ui/graph-view-shared.js +191 -12
- package/ui/judgment-history.css +155 -2
- package/ui/judgment-history.js +649 -241
- package/ui/local-web-shell.css +1 -13
- package/ui/local-web-shell.js +80 -40
- package/ui/meeting-minutes.css +87 -1
- package/ui/meeting-minutes.js +275 -30
- package/ui/outcome-knowledge.js +6 -2
- package/ui/people-and-companies-view.css +166 -15
- package/ui/people-and-companies-view.js +307 -93
- package/ui/world/world-canvas-notes.js +3 -2
- package/ui/world/world-view.css +19 -0
- package/ui/world/world-view.js +52 -10
- package/ui/world/world-work-rail.js +114 -8
package/ui/judgment-history.js
CHANGED
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
workspaceDialog,
|
|
15
15
|
workspaceFilterPopover,
|
|
16
16
|
workspaceErrorCard,
|
|
17
|
-
|
|
18
|
-
workspaceDismissNotification,
|
|
17
|
+
workspaceCombobox,
|
|
19
18
|
workspaceDefinition,
|
|
20
19
|
workspaceDetailEmpty,
|
|
21
20
|
workspaceLedger,
|
|
@@ -32,18 +31,63 @@ const PERIODS = Object.freeze({ week: 'week', past30days: 'past30days', all: 'al
|
|
|
32
31
|
const PERIOD_LABELS = Object.freeze({ week: '今週', past30days: '過去30日', all: '全期間' });
|
|
33
32
|
const SECTION_KEYS = Object.freeze(['needs_human', 'blocked', 'continued', 'other']);
|
|
34
33
|
const NORMAL_ENTRYPOINTS = Object.freeze(['codex', 'claude_code', 'mana', 'company_os', 'unknown']);
|
|
34
|
+
// Where the judgment was made. "company_os" is the organization server's own
|
|
35
|
+
// runs, so it is named after what the person sees rather than the module.
|
|
35
36
|
const NORMAL_ENTRYPOINT_LABELS = Object.freeze({
|
|
36
37
|
codex: 'Codex',
|
|
37
38
|
claude_code: 'Claude Code',
|
|
38
39
|
mana: 'Mana',
|
|
39
|
-
company_os: '
|
|
40
|
-
unknown: '
|
|
40
|
+
company_os: '組織のBrainbase',
|
|
41
|
+
unknown: '記録元が不明',
|
|
42
|
+
});
|
|
43
|
+
// The filter names the organization source a little more fully than a row.
|
|
44
|
+
const NORMAL_ENTRYPOINT_FILTER_LABELS = Object.freeze({
|
|
45
|
+
company_os: '組織のBrainbase(自動の判断)',
|
|
41
46
|
});
|
|
42
47
|
const FEEDBACK_KINDS = Object.freeze(['feedback', 'result', 'correction']);
|
|
43
48
|
const FEEDBACK_KIND_LABELS = Object.freeze({
|
|
44
|
-
feedback: '
|
|
45
|
-
result: '
|
|
46
|
-
correction: '
|
|
49
|
+
feedback: 'メモ',
|
|
50
|
+
result: '良かった',
|
|
51
|
+
correction: '直すところ',
|
|
52
|
+
});
|
|
53
|
+
// The one question a person answers about a judgment. Each answer maps to
|
|
54
|
+
// the existing append-only event kinds; only answers that say the result was
|
|
55
|
+
// looked at carry an explicit outcome.
|
|
56
|
+
const FEEDBACK_ANSWERS = Object.freeze([
|
|
57
|
+
{ value: 'good', kind: 'result', outcomeStatus: 'confirmed', label: '良かった', placeholder: '気づいたことがあれば書いてください(任意)', fallbackSummary: '良かった' },
|
|
58
|
+
{ value: 'fix', kind: 'correction', outcomeStatus: 'confirmed', label: '直すところがある', placeholder: 'どこを、どう直すかを書いてください', fallbackSummary: null },
|
|
59
|
+
{ value: 'unsure', kind: 'feedback', outcomeStatus: null, label: 'まだ分からない', placeholder: '分かったら確かめたいことを書いてください(任意)', fallbackSummary: 'まだ結果を確かめていない' },
|
|
60
|
+
]);
|
|
61
|
+
const REFERENCE_KIND_LABELS = Object.freeze({
|
|
62
|
+
philosophy: '哲学',
|
|
63
|
+
objective: '目的',
|
|
64
|
+
model: '世界モデル',
|
|
65
|
+
problem_snapshot: '問題の記録',
|
|
66
|
+
evidence: '根拠の記録',
|
|
67
|
+
});
|
|
68
|
+
// Graph ids carry their type as a fixed prefix. This is read only when the
|
|
69
|
+
// record left `kind` empty; an unknown prefix stays "その他".
|
|
70
|
+
const REFERENCE_ID_PREFIXES = Object.freeze([
|
|
71
|
+
['fnd_philosophy_', 'philosophy'],
|
|
72
|
+
['fnd_unson_philosophy_', 'philosophy'],
|
|
73
|
+
['phi_', 'philosophy'],
|
|
74
|
+
['fnd_objective_', 'objective'],
|
|
75
|
+
['fnd_unson_objective_', 'objective'],
|
|
76
|
+
['obj_', 'objective'],
|
|
77
|
+
['fnd_model_', 'model'],
|
|
78
|
+
['fnd_unson_model_', 'model'],
|
|
79
|
+
]);
|
|
80
|
+
const MISSING_FIELD_LABELS = Object.freeze({
|
|
81
|
+
entrypoint: '記録元',
|
|
82
|
+
project_code: 'プロジェクト',
|
|
83
|
+
'judgment.status': '判断の状態',
|
|
84
|
+
'judgment.summary': '判断の内容',
|
|
85
|
+
'judgment.reason': '判断の理由',
|
|
86
|
+
'judgment.selected_references': '使った参照',
|
|
87
|
+
'judgment.alternatives': '比べた案',
|
|
88
|
+
'execution.status': '実行したか',
|
|
89
|
+
'execution.result_summary': '実行の結果',
|
|
90
|
+
'execution.outcome_status': '結果を確かめたか',
|
|
47
91
|
});
|
|
48
92
|
|
|
49
93
|
function isRecord(value) {
|
|
@@ -121,6 +165,11 @@ function findFirst(node, predicate) {
|
|
|
121
165
|
return null;
|
|
122
166
|
}
|
|
123
167
|
|
|
168
|
+
// The page search box; the filter dropdowns carry their own text inputs.
|
|
169
|
+
function isSearchInput(node) {
|
|
170
|
+
return node?.tagName === 'INPUT' && attributeValue(node, 'type') === 'search';
|
|
171
|
+
}
|
|
172
|
+
|
|
124
173
|
function replaceNode(nextNode, previousNode) {
|
|
125
174
|
const parent = nextNode?.parentNode;
|
|
126
175
|
if (!parent || !previousNode) return false;
|
|
@@ -594,7 +643,18 @@ function normalRecordSearchText(record) {
|
|
|
594
643
|
].filter(Boolean).join(' ').toLocaleLowerCase('ja-JP');
|
|
595
644
|
}
|
|
596
645
|
|
|
597
|
-
|
|
646
|
+
/**
|
|
647
|
+
* A result counts as looked at when the record says so, or when a person
|
|
648
|
+
* appended an answer that explicitly confirmed it. Feedback events are
|
|
649
|
+
* append-only and never rewrite `execution.outcome_status`, so both are read.
|
|
650
|
+
*/
|
|
651
|
+
function outcomeConfirmed(record) {
|
|
652
|
+
if (text(record?.execution?.outcome_status).toLowerCase() === 'confirmed') return true;
|
|
653
|
+
const events = Array.isArray(record?.feedbackEvents) ? record.feedbackEvents : [];
|
|
654
|
+
return events.some((event) => text(event?.content?.outcome_status).toLowerCase() === 'confirmed');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function aggregateNormalJudgmentHistory(home,{ now = new Date(), period = PERIODS.past30days, query = '' } = {}) {
|
|
598
658
|
const empty = {
|
|
599
659
|
mode: 'normal',
|
|
600
660
|
rows: [],
|
|
@@ -645,6 +705,7 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
645
705
|
missingFields: record.missingFields,
|
|
646
706
|
alternatives: record.alternatives,
|
|
647
707
|
references: record.selectedReferences,
|
|
708
|
+
feedbackEvents: record.feedbackEvents,
|
|
648
709
|
searchText,
|
|
649
710
|
});
|
|
650
711
|
}
|
|
@@ -672,7 +733,7 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
672
733
|
const referenceCount = metricsUnconfirmed || referencesUnconfirmed ? null : references.size;
|
|
673
734
|
const confirmedOutcomes = metricsUnconfirmed
|
|
674
735
|
? null
|
|
675
|
-
: rows.filter(
|
|
736
|
+
: rows.filter(outcomeConfirmed).length;
|
|
676
737
|
return {
|
|
677
738
|
...empty,
|
|
678
739
|
rows,
|
|
@@ -817,82 +878,298 @@ function referenceLabel(ref) {
|
|
|
817
878
|
return label;
|
|
818
879
|
}
|
|
819
880
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
881
|
+
/** The kind of a reference in words, from its recorded kind, usage or id prefix. */
|
|
882
|
+
function referenceKindLabel(ref) {
|
|
883
|
+
for (const key of [text(ref?.kind).toLowerCase(), text(ref?.usage).toLowerCase(), text(ref?.layer).toLowerCase()]) {
|
|
884
|
+
if (REFERENCE_KIND_LABELS[key]) return REFERENCE_KIND_LABELS[key];
|
|
885
|
+
}
|
|
886
|
+
const id = text(ref?.ref ?? ref?.id);
|
|
887
|
+
const prefix = REFERENCE_ID_PREFIXES.find(([candidate]) => id.startsWith(candidate));
|
|
888
|
+
return prefix ? REFERENCE_KIND_LABELS[prefix[1]] : 'その他の参照';
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/** "哲学 2・目的 1" for references that only have an id. */
|
|
892
|
+
function referenceKindCounts(references) {
|
|
893
|
+
const counts = new Map();
|
|
825
894
|
for (const ref of references) {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
895
|
+
const kind = referenceKindLabel(ref);
|
|
896
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
897
|
+
}
|
|
898
|
+
return [...counts].map(([kind, count]) => `${kind} ${count}`).join('・');
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function makeReferenceSummary(doc, references, labels = null) {
|
|
902
|
+
if (references === null || references === undefined) return makeWorkspaceElement(doc, 'span', { className: 'bb-jh-unrecorded', text: '参照は記録されていません' });
|
|
903
|
+
// Personal KG records are summarized by the owner badge only, never by name.
|
|
904
|
+
const shown = references.filter((ref) => judgmentReferenceKind(ref) !== PERSONAL_KNOWLEDGE_KIND);
|
|
905
|
+
if (!shown.length) {
|
|
906
|
+
return makeWorkspaceElement(doc, 'span', {
|
|
907
|
+
className: 'bb-jh-unrecorded',
|
|
908
|
+
text: references.length ? '判断の枠組みは記録されていません' : '参照はありません',
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
const labeled = [];
|
|
912
|
+
const unlabeled = [];
|
|
913
|
+
for (const ref of shown) {
|
|
914
|
+
const name = referenceDisplayName(ref, labels);
|
|
915
|
+
if (name.known) labeled.push(name.text);
|
|
916
|
+
else unlabeled.push(ref);
|
|
829
917
|
}
|
|
830
918
|
const wrap = makeWorkspaceElement(doc, 'span', { className: 'bb-jh-reference-summary' });
|
|
831
919
|
if (labeled.length) {
|
|
832
920
|
wrap.append(makeWorkspaceElement(doc, 'strong', { text: labeled.slice(0, 2).join('、') }));
|
|
833
921
|
if (labeled.length > 2) wrap.append(makeWorkspaceElement(doc, 'small', { text: `ほか${labeled.length - 2}件` }));
|
|
834
922
|
}
|
|
835
|
-
if (unlabeled
|
|
923
|
+
if (unlabeled.length) {
|
|
924
|
+
wrap.append(makeWorkspaceElement(doc, labeled.length ? 'small' : 'strong', { text: referenceKindCounts(unlabeled) }));
|
|
925
|
+
}
|
|
836
926
|
return wrap;
|
|
837
927
|
}
|
|
838
928
|
|
|
929
|
+
// Only a state that asks the person for something is shown in the list.
|
|
930
|
+
// "resolved" and an unrecorded state are kept in the record details.
|
|
839
931
|
function judgmentStatusLabel(status) {
|
|
932
|
+
const key = text(status);
|
|
933
|
+
if (key === 'needs_clarification' || key === 'needs_classification' || key === 'needs_policy_resolution') return 'あなたの確認待ち';
|
|
934
|
+
return '';
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function judgmentStatusDetail(status) {
|
|
840
938
|
const labels = {
|
|
841
|
-
resolved: '
|
|
842
|
-
needs_clarification: '
|
|
843
|
-
needs_classification: '
|
|
844
|
-
needs_policy_resolution: '
|
|
845
|
-
unknown: '状態は未記録',
|
|
939
|
+
resolved: '進め方が決まった',
|
|
940
|
+
needs_clarification: 'あなたの確認待ち',
|
|
941
|
+
needs_classification: 'あなたの確認待ち',
|
|
942
|
+
needs_policy_resolution: 'あなたの確認待ち',
|
|
846
943
|
};
|
|
847
|
-
|
|
848
|
-
if (!key) return '状態は未記録';
|
|
849
|
-
return labels[key] ?? '状態は未記録';
|
|
944
|
+
return labels[text(status)] ?? null;
|
|
850
945
|
}
|
|
851
946
|
|
|
852
947
|
function makeDecisionSummary(doc, row) {
|
|
853
948
|
if (row.mode === 'normal') {
|
|
854
949
|
const summary = text(row.judgment?.summary);
|
|
855
950
|
const wrap = makeWorkspaceElement(doc, 'span', { className: 'bb-jh-decision is-normal' });
|
|
856
|
-
wrap.append(makeWorkspaceElement(doc, 'strong', { text: summary || '
|
|
951
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: summary || '判断の内容は記録されていません' }));
|
|
952
|
+
wrap.append(makeOwnerJudgmentBadge(doc, row.references));
|
|
857
953
|
return wrap;
|
|
858
954
|
}
|
|
859
955
|
const summary = text(row.proof?.decision?.summary);
|
|
860
956
|
const wrap = makeWorkspaceElement(doc, 'span', { className: `bb-jh-decision${row.delegated ? ' is-delegated' : ''}` });
|
|
861
|
-
wrap.append(makeWorkspaceElement(doc, 'strong', { text: summary || (row.proof?.interruption?.resolution === 'continued_without_human' ? '
|
|
957
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: summary || (row.proof?.interruption?.resolution === 'continued_without_human' ? '判断の内容は記録されていません' : '本人に確認') }));
|
|
862
958
|
if (row.delegated) wrap.append(makeWorkspaceElement(doc, 'small', { text: 'Brainbaseが代わりに判断' }));
|
|
863
959
|
else if (row.proof?.interruption?.resolution !== 'continued_without_human') wrap.append(makeWorkspaceElement(doc, 'small', { text: '本人に戻した判断' }));
|
|
864
960
|
return wrap;
|
|
865
961
|
}
|
|
866
962
|
|
|
963
|
+
// docs/specs/judgment-history-frame-basis.md: the shared kind/usage values.
|
|
964
|
+
const FRAME_KINDS = Object.freeze(['philosophy', 'objective', 'model']);
|
|
965
|
+
const FRAME_KIND_LABELS = Object.freeze({ philosophy: '哲学', objective: '目的', model: '世界モデル' });
|
|
966
|
+
const FRAME_ROLE_LABELS = Object.freeze({
|
|
967
|
+
constraint: '守る制約として使った',
|
|
968
|
+
criterion: '照らした基準として使った',
|
|
969
|
+
prediction: '置いた予測として使った',
|
|
970
|
+
});
|
|
971
|
+
const FRAME_ROLE_DETAIL_LABELS = Object.freeze({
|
|
972
|
+
constraint: { satisfied: '守れた', violated: '反した', tension: 'ぶつかった' },
|
|
973
|
+
criterion: { satisfied: '満たした', violated: '満たさない', tension: '一部ぶつかる' },
|
|
974
|
+
prediction: { held: 'データで当たった', failed: 'データで外れた', unchecked: 'まだデータで確かめていない' },
|
|
975
|
+
});
|
|
976
|
+
const PERSONAL_KNOWLEDGE_KIND = 'personal_knowledge';
|
|
977
|
+
|
|
978
|
+
/** Kind of a selected reference: the recorded kind, or the Graph id prefix for older records. */
|
|
979
|
+
export function judgmentReferenceKind(reference) {
|
|
980
|
+
const kind = text(reference?.kind);
|
|
981
|
+
if (kind) return kind;
|
|
982
|
+
const ref = text(reference?.ref ?? reference?.id);
|
|
983
|
+
return REFERENCE_ID_PREFIXES.find(([prefix]) => ref.startsWith(prefix))?.[1] ?? null;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/** Parse the shared usage value; values from other writers stay unparsed. */
|
|
987
|
+
export function parseJudgmentFrameUsage(usage) {
|
|
988
|
+
const [role, detail = null, ...rest] = text(usage).split(':');
|
|
989
|
+
if (rest.length || !FRAME_ROLE_LABELS[role]) return null;
|
|
990
|
+
if (detail !== null && !FRAME_ROLE_DETAIL_LABELS[role][detail]) return null;
|
|
991
|
+
if (role === 'constraint' && detail === null) return null;
|
|
992
|
+
return { role, detail };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Group one judgment's references into the frame it used and whether the
|
|
997
|
+
* owner's Personal KG entered it. "Not used" is claimed only for a writer
|
|
998
|
+
* that records usage; anything else stays "unrecorded" (P9).
|
|
999
|
+
*/
|
|
1000
|
+
export function describeJudgmentBasis(references) {
|
|
1001
|
+
const groups = { philosophy: [], objective: [], model: [] };
|
|
1002
|
+
const others = [];
|
|
1003
|
+
let personal = 0;
|
|
1004
|
+
let usageRecorded = false;
|
|
1005
|
+
for (const reference of Array.isArray(references) ? references : []) {
|
|
1006
|
+
const kind = judgmentReferenceKind(reference);
|
|
1007
|
+
if (kind === PERSONAL_KNOWLEDGE_KIND) {
|
|
1008
|
+
personal += 1;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
const usage = parseJudgmentFrameUsage(reference?.usage);
|
|
1012
|
+
if (FRAME_KINDS.includes(kind)) {
|
|
1013
|
+
if (usage) usageRecorded = true;
|
|
1014
|
+
groups[kind].push({ reference, usage });
|
|
1015
|
+
} else {
|
|
1016
|
+
others.push(reference);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
const owner = personal > 0
|
|
1020
|
+
? { state: 'applied', count: personal }
|
|
1021
|
+
: usageRecorded ? { state: 'not_applied', count: 0 } : { state: 'unrecorded', count: 0 };
|
|
1022
|
+
return { groups, others, owner, recorded: Array.isArray(references) };
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const OWNER_JUDGMENT_BADGES = Object.freeze({
|
|
1026
|
+
applied: '本人の考えあり',
|
|
1027
|
+
not_applied: '本人の考えなし',
|
|
1028
|
+
unrecorded: '本人の考え:記録なし',
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
// "recorded" is the normal case and "unknown" means nobody checked; neither
|
|
1032
|
+
// needs the person's attention, so only the two blocking states are shown.
|
|
867
1033
|
const REFERENCE_AVAILABILITY_LABELS = Object.freeze({
|
|
868
|
-
recorded: '今の権限で開けます',
|
|
869
1034
|
permission_denied: '開く権限がありません',
|
|
870
|
-
unavailable: '
|
|
871
|
-
|
|
1035
|
+
unavailable: '本文はもう残っていません',
|
|
1036
|
+
});
|
|
1037
|
+
const REFERENCE_AVAILABILITY_DETAIL = Object.freeze({
|
|
1038
|
+
recorded: '開ける',
|
|
1039
|
+
permission_denied: '開く権限がない',
|
|
1040
|
+
unavailable: '本文がない',
|
|
1041
|
+
unknown: '開けるか確かめていない',
|
|
872
1042
|
});
|
|
873
1043
|
|
|
874
1044
|
function appendReferenceList(doc, references) {
|
|
875
1045
|
if (references === null || references === undefined) return makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '参照は記録されていません。' });
|
|
876
1046
|
if (!references.length) return makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '参照はありません。' });
|
|
1047
|
+
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-references' });
|
|
877
1048
|
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-reference-list' });
|
|
1049
|
+
const bare = [];
|
|
878
1050
|
for (const ref of references) {
|
|
879
|
-
const item = makeWorkspaceElement(doc, 'li');
|
|
880
1051
|
const label = referenceLabel(ref);
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
if (
|
|
884
|
-
|
|
1052
|
+
const application = ref.application && ref.application !== ref.label && ref.application !== label ? ref.application : '';
|
|
1053
|
+
const availability = REFERENCE_AVAILABILITY_LABELS[text(ref.availability)];
|
|
1054
|
+
if (!label && !application && !ref.why && !ref.usage && !availability) {
|
|
1055
|
+
bare.push(ref);
|
|
1056
|
+
continue;
|
|
885
1057
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
item.append(makeWorkspaceElement(doc, '
|
|
889
|
-
if (
|
|
890
|
-
if (
|
|
891
|
-
|
|
1058
|
+
const item = makeWorkspaceElement(doc, 'li');
|
|
1059
|
+
item.append(makeWorkspaceElement(doc, 'strong', { text: label || referenceKindLabel(ref) }));
|
|
1060
|
+
if (label) item.append(makeWorkspaceElement(doc, 'small', { text: referenceKindLabel(ref) }));
|
|
1061
|
+
if (application) item.append(makeWorkspaceElement(doc, 'span', { text: application }));
|
|
1062
|
+
if (availability) item.append(makeWorkspaceElement(doc, 'span', { className: 'bb-jh-reference-availability', text: availability }));
|
|
1063
|
+
if (ref.why) item.append(makeWorkspaceElement(doc, 'span', { text: `選んだ理由: ${ref.why}` }));
|
|
1064
|
+
if (ref.usage && !REFERENCE_KIND_LABELS[text(ref.usage)]) item.append(makeWorkspaceElement(doc, 'span', { text: `使い方: ${ref.usage}` }));
|
|
1065
|
+
const alternateLabels = (ref.labels ?? []).filter((value) => value && value !== ref.label);
|
|
892
1066
|
if (alternateLabels.length) item.append(makeWorkspaceElement(doc, 'span', { text: `別名: ${alternateLabels.join('、')}` }));
|
|
893
1067
|
list.append(item);
|
|
894
1068
|
}
|
|
895
|
-
|
|
1069
|
+
if (list.children?.length) wrap.append(list);
|
|
1070
|
+
if (bare.length) {
|
|
1071
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-reference-kinds', text: referenceKindCounts(bare) }));
|
|
1072
|
+
wrap.append(makeWorkspaceElement(doc, 'p', {
|
|
1073
|
+
className: 'bb-jh-reference-note',
|
|
1074
|
+
text: 'この記録には参照の名前が残っていません。どの記録かは「記録の詳細」のIDで確かめられます。',
|
|
1075
|
+
}));
|
|
1076
|
+
}
|
|
1077
|
+
return wrap;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** Name for a frame reference, resolved by the host under the viewer's current permission. */
|
|
1081
|
+
function referenceDisplayName(reference, labels) {
|
|
1082
|
+
const recorded = referenceLabel(reference);
|
|
1083
|
+
if (recorded) return { text: recorded, known: true };
|
|
1084
|
+
const entry = labels?.get?.(text(reference?.ref ?? reference?.id));
|
|
1085
|
+
if (entry?.status === 'found' && text(entry.label)) return { text: text(entry.label), known: true };
|
|
1086
|
+
if (entry?.status === 'loading') return { text: '名前を確認中', known: false };
|
|
1087
|
+
return { text: '名前を確かめられません', known: false };
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function frameUsageText(usage) {
|
|
1091
|
+
if (!usage) return '使い方の記録なし';
|
|
1092
|
+
const detail = usage.detail ? FRAME_ROLE_DETAIL_LABELS[usage.role][usage.detail] : '';
|
|
1093
|
+
return detail ? `${FRAME_ROLE_LABELS[usage.role]}・${detail}` : FRAME_ROLE_LABELS[usage.role];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function referenceWhy(doc, reference) {
|
|
1097
|
+
const why = text(reference?.why);
|
|
1098
|
+
return why ? makeWorkspaceElement(doc, 'span', { className: 'bb-jh-basis-why', text: `選んだ理由:${why}` }) : null;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function appendJudgmentBasis(doc, references, labels) {
|
|
1102
|
+
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-basis' });
|
|
1103
|
+
if (references === null || references === undefined) {
|
|
1104
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '判断の枠組み(哲学・目的・世界モデル)の記録はありません。使っていないとは限りません。' }));
|
|
1105
|
+
return wrap;
|
|
1106
|
+
}
|
|
1107
|
+
const basis = describeJudgmentBasis(references);
|
|
1108
|
+
for (const kind of FRAME_KINDS) {
|
|
1109
|
+
const section = makeWorkspaceElement(doc, 'section', { className: `bb-jh-basis-group is-${kind}` });
|
|
1110
|
+
section.append(makeWorkspaceElement(doc, 'h4', { text: FRAME_KIND_LABELS[kind] }));
|
|
1111
|
+
const entries = basis.groups[kind];
|
|
1112
|
+
if (!entries.length) {
|
|
1113
|
+
section.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: 'この判断では記録されていません。' }));
|
|
1114
|
+
} else {
|
|
1115
|
+
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-basis-list' });
|
|
1116
|
+
for (const { reference, usage } of entries) {
|
|
1117
|
+
const item = makeWorkspaceElement(doc, 'li');
|
|
1118
|
+
const name = referenceDisplayName(reference, labels);
|
|
1119
|
+
item.append(makeWorkspaceElement(doc, 'strong', { className: name.known ? '' : 'bb-jh-unrecorded', text: name.text }));
|
|
1120
|
+
item.append(makeWorkspaceElement(doc, 'span', {
|
|
1121
|
+
className: `bb-jh-basis-usage${usage ? ` is-${usage.role}${usage.detail ? ` is-${usage.detail}` : ''}` : ' is-unrecorded'}`,
|
|
1122
|
+
text: frameUsageText(usage),
|
|
1123
|
+
}));
|
|
1124
|
+
const why = referenceWhy(doc, reference);
|
|
1125
|
+
if (why) item.append(why);
|
|
1126
|
+
list.append(item);
|
|
1127
|
+
}
|
|
1128
|
+
section.append(list);
|
|
1129
|
+
}
|
|
1130
|
+
wrap.append(section);
|
|
1131
|
+
}
|
|
1132
|
+
if (basis.others.length) {
|
|
1133
|
+
const section = makeWorkspaceElement(doc, 'section', { className: 'bb-jh-basis-group is-other' });
|
|
1134
|
+
section.append(makeWorkspaceElement(doc, 'h4', { text: 'そのほかの参照' }));
|
|
1135
|
+
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-basis-list' });
|
|
1136
|
+
for (const reference of basis.others) {
|
|
1137
|
+
const item = makeWorkspaceElement(doc, 'li');
|
|
1138
|
+
const name = referenceDisplayName(reference, labels);
|
|
1139
|
+
item.append(makeWorkspaceElement(doc, 'strong', { className: name.known ? '' : 'bb-jh-unrecorded', text: name.text }));
|
|
1140
|
+
item.append(makeWorkspaceElement(doc, 'small', { text: referenceKindLabel(reference) }));
|
|
1141
|
+
const why = referenceWhy(doc, reference);
|
|
1142
|
+
if (why) item.append(why);
|
|
1143
|
+
list.append(item);
|
|
1144
|
+
}
|
|
1145
|
+
section.append(list);
|
|
1146
|
+
wrap.append(section);
|
|
1147
|
+
}
|
|
1148
|
+
return wrap;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function appendOwnerJudgment(doc, references) {
|
|
1152
|
+
const { owner } = describeJudgmentBasis(references);
|
|
1153
|
+
const wrap = makeWorkspaceElement(doc, 'div', { className: `bb-jh-owner is-${owner.state}` });
|
|
1154
|
+
if (owner.state === 'applied') {
|
|
1155
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: `あなたの判断の材料(個人KG)を${owner.count}件使いました。` }));
|
|
1156
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { text: 'この判断には、あなたの考えが入っています。個人KGの中身はここには出しません。' }));
|
|
1157
|
+
} else if (owner.state === 'not_applied') {
|
|
1158
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: 'あなたの判断の材料(個人KG)は使っていません。' }));
|
|
1159
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { text: 'Graphの哲学・目的・世界モデルと、AIの判断で進めました。' }));
|
|
1160
|
+
} else {
|
|
1161
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: '個人KGを使ったかどうかは記録されていません。' }));
|
|
1162
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: 'この記録の書き手は使い方を残していません。使っていないとは限りません。' }));
|
|
1163
|
+
}
|
|
1164
|
+
return wrap;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function makeOwnerJudgmentBadge(doc, references) {
|
|
1168
|
+
const { owner } = describeJudgmentBasis(references);
|
|
1169
|
+
return makeWorkspaceElement(doc, 'span', {
|
|
1170
|
+
className: `bb-jh-owner-badge is-${owner.state}`,
|
|
1171
|
+
text: OWNER_JUDGMENT_BADGES[owner.state],
|
|
1172
|
+
});
|
|
896
1173
|
}
|
|
897
1174
|
|
|
898
1175
|
function evidenceIdentifier(entry) {
|
|
@@ -927,16 +1204,16 @@ function appendNormalAlternatives(doc, alternatives) {
|
|
|
927
1204
|
if (alternatives === null || alternatives === undefined) {
|
|
928
1205
|
return makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '案は記録されていません。' });
|
|
929
1206
|
}
|
|
930
|
-
if (!alternatives.length) return makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '
|
|
1207
|
+
if (!alternatives.length) return makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '比べた案はありません。' });
|
|
931
1208
|
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-alternative-list' });
|
|
932
1209
|
for (const alternative of alternatives) {
|
|
933
1210
|
const item = makeWorkspaceElement(doc, 'li');
|
|
934
|
-
item.append(makeWorkspaceElement(doc, 'strong', { text: alternative.label || '
|
|
1211
|
+
item.append(makeWorkspaceElement(doc, 'strong', { text: alternative.label || '案の名前は記録されていません' }));
|
|
935
1212
|
if (alternative.evaluation) item.append(makeWorkspaceElement(doc, 'span', { text: `評価: ${alternative.evaluation}` }));
|
|
936
|
-
if (alternative.adoption) item.append(makeWorkspaceElement(doc, 'span', { text:
|
|
937
|
-
else if (alternative.adopted === true) item.append(makeWorkspaceElement(doc, 'span', { text: '
|
|
938
|
-
else if (alternative.adopted === false) item.append(makeWorkspaceElement(doc, 'span', { text: '
|
|
939
|
-
else if (Object.prototype.hasOwnProperty.call(alternative, 'adopted')) item.append(makeWorkspaceElement(doc, 'span', { text: '
|
|
1213
|
+
if (alternative.adoption) item.append(makeWorkspaceElement(doc, 'span', { text: alternative.adoption }));
|
|
1214
|
+
else if (alternative.adopted === true) item.append(makeWorkspaceElement(doc, 'span', { text: '採用した案' }));
|
|
1215
|
+
else if (alternative.adopted === false) item.append(makeWorkspaceElement(doc, 'span', { text: '採用しなかった案' }));
|
|
1216
|
+
else if (Object.prototype.hasOwnProperty.call(alternative, 'adopted')) item.append(makeWorkspaceElement(doc, 'span', { text: '採用したかは記録されていません' }));
|
|
940
1217
|
list.append(item);
|
|
941
1218
|
}
|
|
942
1219
|
return list;
|
|
@@ -944,26 +1221,26 @@ function appendNormalAlternatives(doc, alternatives) {
|
|
|
944
1221
|
|
|
945
1222
|
function normalExecutionLabel(execution) {
|
|
946
1223
|
const status = text(execution?.status).toLowerCase();
|
|
947
|
-
const labels = { pending: '
|
|
948
|
-
return labels[status] ?? '
|
|
1224
|
+
const labels = { pending: 'まだ実行していません', completed: '実行しました', failed: '実行に失敗しました', held: '保留しています', cancelled: '取り消しました' };
|
|
1225
|
+
return labels[status] ?? '実行したかは記録されていません';
|
|
949
1226
|
}
|
|
950
1227
|
|
|
951
|
-
function normalOutcomeLabel(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1228
|
+
function normalOutcomeLabel(record) {
|
|
1229
|
+
return outcomeConfirmed(record)
|
|
1230
|
+
? '結果を確かめました'
|
|
1231
|
+
: 'まだ確かめていません。下の「この判断は良かったか」で記録できます。';
|
|
955
1232
|
}
|
|
956
1233
|
|
|
957
|
-
function appendNormalExecution(doc,
|
|
1234
|
+
function appendNormalExecution(doc, record) {
|
|
958
1235
|
return workspaceDefinition(doc, [
|
|
959
|
-
['実行', [normalExecutionLabel(execution), text(execution?.result_summary)]],
|
|
960
|
-
['結果', normalOutcomeLabel(
|
|
1236
|
+
['実行', [normalExecutionLabel(record.execution), text(record.execution?.result_summary)]],
|
|
1237
|
+
['結果', normalOutcomeLabel(record)],
|
|
961
1238
|
]);
|
|
962
1239
|
}
|
|
963
1240
|
|
|
964
|
-
function
|
|
965
|
-
if (!Array.isArray(missingFields)
|
|
966
|
-
return
|
|
1241
|
+
function missingFieldLabels(missingFields) {
|
|
1242
|
+
if (!Array.isArray(missingFields)) return [];
|
|
1243
|
+
return [...new Set(missingFields.map((field) => MISSING_FIELD_LABELS[field] ?? field))];
|
|
967
1244
|
}
|
|
968
1245
|
|
|
969
1246
|
function appendTextParts(doc, values) {
|
|
@@ -975,10 +1252,8 @@ function appendTextParts(doc, values) {
|
|
|
975
1252
|
}
|
|
976
1253
|
|
|
977
1254
|
function normalFeedbackContentSummary(content) {
|
|
978
|
-
if (!isRecord(content)) return '
|
|
979
|
-
return text(content.summary ?? content.result_summary ?? content.message ?? content.reason)
|
|
980
|
-
|| canonicalJson(content)
|
|
981
|
-
|| '内容は未記録';
|
|
1255
|
+
if (!isRecord(content)) return '内容は記録されていません';
|
|
1256
|
+
return text(content.summary ?? content.result_summary ?? content.message ?? content.reason) || '内容は記録されていません';
|
|
982
1257
|
}
|
|
983
1258
|
|
|
984
1259
|
function normalReferencePayload(record, event, applicabilityReason = '', eventRef = null) {
|
|
@@ -1001,130 +1276,169 @@ function normalReferencePayloadText(record, event, applicabilityReason = '', eve
|
|
|
1001
1276
|
|
|
1002
1277
|
function appendNormalReferenceExport(doc, record, event, { onCopyReference } = {}) {
|
|
1003
1278
|
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-reference-export' });
|
|
1004
|
-
|
|
1005
|
-
? `${
|
|
1006
|
-
|
|
1007
|
-
wrap.append(makeWorkspaceElement(doc, 'code', { text: refLabel }));
|
|
1008
|
-
if (event) wrap.append(makeWorkspaceElement(doc, 'p', { text: `種別: ${FEEDBACK_KIND_LABELS[event.kind] ?? event.kind}・${normalFeedbackContentSummary(event.content)}` }));
|
|
1279
|
+
wrap.append(makeWorkspaceElement(doc, 'p', {
|
|
1280
|
+
text: event ? `${FEEDBACK_KIND_LABELS[event.kind] ?? event.kind}:${normalFeedbackContentSummary(event.content)}` : 'この判断そのもの',
|
|
1281
|
+
}));
|
|
1009
1282
|
const reason = makeWorkspaceElement(doc, 'textarea', {
|
|
1010
1283
|
className: 'bb-jh-applicability-reason',
|
|
1011
1284
|
attrs: {
|
|
1012
1285
|
rows: 2,
|
|
1013
|
-
'aria-label':
|
|
1014
|
-
placeholder: '
|
|
1286
|
+
'aria-label': `引き継ぐ理由${event ? `(${event.eventId})` : ''}`,
|
|
1287
|
+
placeholder: '次の判断でこれを使う理由(任意)',
|
|
1015
1288
|
},
|
|
1016
1289
|
});
|
|
1017
1290
|
reason.value = '';
|
|
1018
|
-
wrap.append(makeWorkspaceElement(doc, 'label', { text: '
|
|
1291
|
+
wrap.append(makeWorkspaceElement(doc, 'label', { text: '引き継ぐ理由' }), reason);
|
|
1019
1292
|
const preview = makeWorkspaceElement(doc, 'pre', {
|
|
1020
1293
|
className: 'bb-jh-reference-payload',
|
|
1021
1294
|
text: normalReferencePayloadText(record, event),
|
|
1022
1295
|
});
|
|
1023
|
-
const status = makeWorkspaceElement(doc, 'span', { className: 'bb-jh-copy-status', text: '
|
|
1296
|
+
const status = makeWorkspaceElement(doc, 'span', { className: 'bb-jh-copy-status', text: '' });
|
|
1024
1297
|
wrap.append(preview, workspaceButton(doc, {
|
|
1025
|
-
text: '
|
|
1298
|
+
text: 'AIへ渡す参照をコピー',
|
|
1026
1299
|
variant: 'quiet',
|
|
1027
1300
|
attrs: { 'data-action': 'copy-judgment-reference' },
|
|
1028
1301
|
onClick: () => {
|
|
1029
1302
|
if (typeof onCopyReference === 'function') onCopyReference({ record, event, reason, preview, status });
|
|
1030
|
-
else status.textContent = '
|
|
1303
|
+
else status.textContent = '上の内容を選んでコピーできます。';
|
|
1031
1304
|
},
|
|
1032
1305
|
}), status);
|
|
1033
1306
|
return wrap;
|
|
1034
1307
|
}
|
|
1035
1308
|
|
|
1036
|
-
function appendNormalFeedbackEvents(doc, record
|
|
1309
|
+
function appendNormalFeedbackEvents(doc, record) {
|
|
1037
1310
|
const events = Array.isArray(record.feedbackEvents) ? record.feedbackEvents : [];
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
wrap.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: '追記された結果・訂正・フィードバックはありません。' }));
|
|
1041
|
-
wrap.append(appendNormalReferenceExport(doc, record, null, { onCopyReference }));
|
|
1042
|
-
return wrap;
|
|
1043
|
-
}
|
|
1311
|
+
if (!events.length) return null;
|
|
1312
|
+
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-feedback-events' });
|
|
1044
1313
|
for (const event of events) {
|
|
1045
|
-
const item = makeWorkspaceElement(doc, '
|
|
1314
|
+
const item = makeWorkspaceElement(doc, 'li', { className: `bb-jh-feedback-event is-${event.kind}` });
|
|
1046
1315
|
item.append(makeWorkspaceElement(doc, 'strong', { text: FEEDBACK_KIND_LABELS[event.kind] ?? event.kind }));
|
|
1047
|
-
item.append(makeWorkspaceElement(doc, 'code', { text: `record_id=${event.recordId} event_id=${event.eventId}` }));
|
|
1048
1316
|
item.append(makeWorkspaceElement(doc, 'p', { text: normalFeedbackContentSummary(event.content) }));
|
|
1049
|
-
|
|
1050
|
-
wrap.append(item);
|
|
1317
|
+
list.append(item);
|
|
1051
1318
|
}
|
|
1052
|
-
return
|
|
1319
|
+
return list;
|
|
1053
1320
|
}
|
|
1054
1321
|
|
|
1055
1322
|
function feedbackStatusText(draft) {
|
|
1056
1323
|
if (!draft) return '';
|
|
1057
|
-
if (draft.phase === 'submitting') return '
|
|
1058
|
-
if (draft.phase === 'readback') return '
|
|
1059
|
-
if (draft.phase === 'saved') return '
|
|
1060
|
-
if (draft.phase === 'unavailable') return
|
|
1061
|
-
if (draft.phase === 'unconfirmed') return
|
|
1062
|
-
if (draft.phase === 'error') return
|
|
1063
|
-
return draft.status || '
|
|
1324
|
+
if (draft.phase === 'submitting') return '記録しています…';
|
|
1325
|
+
if (draft.phase === 'readback') return '記録できたか確かめています…';
|
|
1326
|
+
if (draft.phase === 'saved') return '記録しました。';
|
|
1327
|
+
if (draft.phase === 'unavailable') return `記録できませんでした。${draft.status || '記録先に接続できません。'}`;
|
|
1328
|
+
if (draft.phase === 'unconfirmed') return `送りましたが、記録できたかを確かめられませんでした。${draft.status || ''}再読み込みして確かめてください。`;
|
|
1329
|
+
if (draft.phase === 'error') return draft.status || '記録できませんでした。';
|
|
1330
|
+
return draft.status || '';
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function feedbackAnswer(value) {
|
|
1334
|
+
return FEEDBACK_ANSWERS.find((answer) => answer.value === value) ?? null;
|
|
1064
1335
|
}
|
|
1065
1336
|
|
|
1066
1337
|
function appendNormalFeedbackComposer(doc, record, {
|
|
1067
1338
|
detailReady = false,
|
|
1068
1339
|
detailPhase = 'idle',
|
|
1069
1340
|
draft,
|
|
1070
|
-
|
|
1341
|
+
onAnswerChange,
|
|
1071
1342
|
onSummaryChange,
|
|
1072
|
-
onOutcomeStatusChange,
|
|
1073
1343
|
onSubmitFeedback,
|
|
1074
1344
|
} = {}) {
|
|
1075
1345
|
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-feedback-composer' });
|
|
1076
1346
|
if (!detailReady) {
|
|
1077
1347
|
const message = detailPhase === 'loading'
|
|
1078
|
-
? '
|
|
1079
|
-
: '
|
|
1348
|
+
? 'この判断の記録を読み込んでいます…'
|
|
1349
|
+
: 'この判断の記録を読み込めないため、いまは記録できません。再読み込みしてください。';
|
|
1080
1350
|
wrap.append(makeWorkspaceElement(doc, 'p', { className: 'bb-jh-unrecorded', text: message }));
|
|
1081
1351
|
return wrap;
|
|
1082
1352
|
}
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1353
|
+
const phase = draft?.phase ?? 'idle';
|
|
1354
|
+
const busy = phase === 'submitting' || phase === 'readback';
|
|
1355
|
+
const saved = phase === 'saved';
|
|
1356
|
+
const selected = feedbackAnswer(draft?.answer);
|
|
1357
|
+
const answers = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-feedback-answers', attrs: { role: 'group', 'aria-label': 'この判断は良かったか' } });
|
|
1358
|
+
for (const answer of FEEDBACK_ANSWERS) {
|
|
1359
|
+
const active = answer.value === selected?.value;
|
|
1360
|
+
answers.append(workspaceButton(doc, {
|
|
1361
|
+
text: answer.label,
|
|
1362
|
+
variant: active ? 'primary' : 'quiet',
|
|
1363
|
+
disabled: busy || saved,
|
|
1364
|
+
attrs: { 'data-answer': answer.value, 'aria-pressed': active ? 'true' : 'false' },
|
|
1365
|
+
onClick: () => onAnswerChange?.(answer.value),
|
|
1089
1366
|
}));
|
|
1090
1367
|
}
|
|
1091
|
-
kind.value = draft?.kind ?? 'feedback';
|
|
1092
|
-
kind.addEventListener('change', (event) => onKindChange?.(event?.target?.value ?? kind.value));
|
|
1093
|
-
const summaryLabel = makeWorkspaceElement(doc, 'label', { text: '内容(保存公開フィールド)' });
|
|
1094
1368
|
const summary = makeWorkspaceElement(doc, 'textarea', {
|
|
1095
1369
|
className: 'bb-jh-feedback-content',
|
|
1096
|
-
attrs: {
|
|
1370
|
+
attrs: {
|
|
1371
|
+
rows: 3,
|
|
1372
|
+
'aria-label': '判断へのひとこと',
|
|
1373
|
+
placeholder: selected?.placeholder ?? '気づいたことを書いてください(任意)',
|
|
1374
|
+
},
|
|
1097
1375
|
});
|
|
1098
1376
|
summary.value = draft?.summary ?? '';
|
|
1377
|
+
if (busy || saved) summary.setAttribute('disabled', '');
|
|
1099
1378
|
summary.addEventListener('input', (event) => onSummaryChange?.(event?.target?.value ?? summary.value));
|
|
1100
|
-
const outcomeLabel = makeWorkspaceElement(doc, 'label', { text: '結果の確認状態(明示する場合)' });
|
|
1101
|
-
const outcomeStatus = makeWorkspaceElement(doc, 'select', { attrs: { 'aria-label': '結果の確認状態' } });
|
|
1102
|
-
outcomeStatus.append(makeWorkspaceElement(doc, 'option', { text: '記録しない', attrs: { value: '' } }));
|
|
1103
|
-
for (const value of ['unknown', 'unconfirmed', 'confirmed']) {
|
|
1104
|
-
const label = { unknown: '未確認', unconfirmed: '結果未確認', confirmed: '結果確認済み' }[value];
|
|
1105
|
-
outcomeStatus.append(makeWorkspaceElement(doc, 'option', { text: label, attrs: { value } }));
|
|
1106
|
-
}
|
|
1107
|
-
outcomeStatus.value = draft?.outcomeStatus ?? '';
|
|
1108
|
-
outcomeStatus.addEventListener('change', (event) => onOutcomeStatusChange?.(event?.target?.value ?? outcomeStatus.value));
|
|
1109
|
-
const eventId = makeWorkspaceElement(doc, 'code', { text: `event_id=${draft?.eventId ?? '未発行'}` });
|
|
1110
|
-
const phase = draft?.phase ?? 'idle';
|
|
1111
|
-
const busy = phase === 'submitting' || phase === 'readback';
|
|
1112
1379
|
const button = workspaceButton(doc, {
|
|
1113
|
-
text:
|
|
1380
|
+
text: saved ? '記録しました' : '記録する',
|
|
1114
1381
|
variant: 'primary',
|
|
1115
|
-
disabled: busy ||
|
|
1382
|
+
disabled: busy || saved,
|
|
1116
1383
|
attrs: { 'data-action': 'submit-judgment-feedback' },
|
|
1117
|
-
onClick: () => onSubmitFeedback?.({
|
|
1384
|
+
onClick: () => onSubmitFeedback?.({ summary }),
|
|
1118
1385
|
});
|
|
1119
1386
|
const status = makeWorkspaceElement(doc, 'p', {
|
|
1120
1387
|
className: `bb-jh-feedback-status is-${phase}`,
|
|
1121
1388
|
text: feedbackStatusText(draft),
|
|
1122
1389
|
attrs: { role: phase === 'error' || phase === 'unavailable' ? 'alert' : 'status' },
|
|
1123
1390
|
});
|
|
1124
|
-
wrap.append(
|
|
1391
|
+
wrap.append(answers, summary, button, status);
|
|
1125
1392
|
return wrap;
|
|
1126
1393
|
}
|
|
1127
1394
|
|
|
1395
|
+
/** Ids of the references, with kind and whether they can be opened, for the record details. */
|
|
1396
|
+
function referenceIdList(doc, references) {
|
|
1397
|
+
if (!Array.isArray(references) || !references.length) return null;
|
|
1398
|
+
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-id-list' });
|
|
1399
|
+
for (const ref of references) {
|
|
1400
|
+
const id = text(ref.ref ?? ref.id);
|
|
1401
|
+
// Personal KG records are shown only as "used" and a count, never by id.
|
|
1402
|
+
if (!id || judgmentReferenceKind(ref) === PERSONAL_KNOWLEDGE_KIND) continue;
|
|
1403
|
+
const availability = REFERENCE_AVAILABILITY_DETAIL[text(ref.availability)] ?? REFERENCE_AVAILABILITY_DETAIL.unknown;
|
|
1404
|
+
const version = text(ref.version) ? `・版 ${text(ref.version)}` : '';
|
|
1405
|
+
list.append(makeWorkspaceElement(doc, 'li', { text: `${id}(${referenceKindLabel(ref)}${version}・${availability})` }));
|
|
1406
|
+
}
|
|
1407
|
+
return list.children?.length ? list : null;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function appendNormalRecordDetails(doc, record, { onCopyReference, open = false, onToggle } = {}) {
|
|
1411
|
+
const details = makeWorkspaceElement(doc, 'details', { className: 'bb-jh-record-details' });
|
|
1412
|
+
details.open = open === true;
|
|
1413
|
+
if (typeof onToggle === 'function') details.addEventListener('toggle', () => onToggle(details.open));
|
|
1414
|
+
details.append(makeWorkspaceElement(doc, 'summary', { text: '記録の詳細' }));
|
|
1415
|
+
const referenceIds = referenceIdList(doc, record.selectedReferences);
|
|
1416
|
+
const events = Array.isArray(record.feedbackEvents) ? record.feedbackEvents : [];
|
|
1417
|
+
const eventIds = events.length ? makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-id-list' }) : null;
|
|
1418
|
+
for (const event of events) {
|
|
1419
|
+
eventIds.append(makeWorkspaceElement(doc, 'li', { text: `${FEEDBACK_KIND_LABELS[event.kind] ?? event.kind}: ${event.eventId}` }));
|
|
1420
|
+
}
|
|
1421
|
+
const missing = missingFieldLabels(record.missingFields);
|
|
1422
|
+
details.append(workspaceDefinition(doc, [
|
|
1423
|
+
['記録ID', record.recordId],
|
|
1424
|
+
['会話のID', record.turnRef || '記録されていません'],
|
|
1425
|
+
['判断の状態', judgmentStatusDetail(record.judgment?.status) ?? '記録されていません'],
|
|
1426
|
+
['参照のID', referenceIds ?? 'ありません'],
|
|
1427
|
+
['追記のID', eventIds ?? 'まだありません'],
|
|
1428
|
+
['記録されていない項目', missing.length ? `${missing.join('、')}(判断したAIが書き残していない項目です)` : 'ありません'],
|
|
1429
|
+
]));
|
|
1430
|
+
const handoff = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-handoff' });
|
|
1431
|
+
handoff.append(makeWorkspaceElement(doc, 'h4', { text: '次の判断に引き継ぐ' }));
|
|
1432
|
+
handoff.append(makeWorkspaceElement(doc, 'p', {
|
|
1433
|
+
className: 'bb-jh-reference-note',
|
|
1434
|
+
text: 'この判断を別の判断の根拠として使うときに、AIへ渡す参照です。コピーしてAIとの会話に貼ります。',
|
|
1435
|
+
}));
|
|
1436
|
+
handoff.append(appendNormalReferenceExport(doc, record, null, { onCopyReference }));
|
|
1437
|
+
for (const event of events) handoff.append(appendNormalReferenceExport(doc, record, event, { onCopyReference }));
|
|
1438
|
+
details.append(handoff);
|
|
1439
|
+
return details;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1128
1442
|
function renderNormalRail(doc, rail, row, detail, onClose, options = {}) {
|
|
1129
1443
|
if (!rail) return;
|
|
1130
1444
|
rail.replaceChildren();
|
|
@@ -1134,8 +1448,8 @@ function renderNormalRail(doc, rail, row, detail, onClose, options = {}) {
|
|
|
1134
1448
|
const homeRecord = row?.mode === 'normal' ? normalizeNormalRecord(row.record) : null;
|
|
1135
1449
|
const record = detail ?? homeRecord ?? row;
|
|
1136
1450
|
rail.append(workspaceRailHead(doc, {
|
|
1137
|
-
kicker: `${NORMAL_ENTRYPOINT_LABELS[record.entrypoint] ??
|
|
1138
|
-
title: text(record.judgment?.summary) || '
|
|
1451
|
+
kicker: `${NORMAL_ENTRYPOINT_LABELS[record.entrypoint] ?? NORMAL_ENTRYPOINT_LABELS.unknown}の判断`,
|
|
1452
|
+
title: text(record.judgment?.summary) || '判断の内容は記録されていません',
|
|
1139
1453
|
sub: formatDate(record.recordedAt),
|
|
1140
1454
|
}));
|
|
1141
1455
|
if (typeof onClose === 'function') {
|
|
@@ -1147,37 +1461,41 @@ function renderNormalRail(doc, rail, row, detail, onClose, options = {}) {
|
|
|
1147
1461
|
}));
|
|
1148
1462
|
}
|
|
1149
1463
|
rail.append(workspaceRailBlock(doc, {
|
|
1150
|
-
title: '
|
|
1151
|
-
|
|
1464
|
+
title: '判断の根拠',
|
|
1465
|
+
className: 'bb-jh-basis-block',
|
|
1466
|
+
content: appendJudgmentBasis(doc, record.selectedReferences, options.labels),
|
|
1467
|
+
}));
|
|
1468
|
+
rail.append(workspaceRailBlock(doc, {
|
|
1469
|
+
title: '本人の考え',
|
|
1470
|
+
content: appendOwnerJudgment(doc, record.selectedReferences),
|
|
1152
1471
|
}));
|
|
1153
1472
|
rail.append(workspaceRailBlock(doc, {
|
|
1154
1473
|
title: '判断の理由',
|
|
1155
1474
|
content: appendTextParts(doc, [text(record.judgment?.reason) || '理由は記録されていません。']),
|
|
1156
1475
|
}));
|
|
1157
1476
|
rail.append(workspaceRailBlock(doc, {
|
|
1158
|
-
title: '
|
|
1477
|
+
title: '比べた案',
|
|
1159
1478
|
content: appendNormalAlternatives(doc, record.alternatives),
|
|
1160
1479
|
}));
|
|
1161
1480
|
rail.append(workspaceRailBlock(doc, {
|
|
1162
1481
|
title: '実行と結果',
|
|
1163
|
-
content: appendNormalExecution(doc, record
|
|
1482
|
+
content: appendNormalExecution(doc, record),
|
|
1164
1483
|
}));
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
rail.append(workspaceRailBlock(doc, {
|
|
1170
|
-
|
|
1171
|
-
content: appendNormalFeedbackComposer(doc, record, options),
|
|
1172
|
-
}));
|
|
1173
|
-
const metadata = workspaceDefinition(doc, [
|
|
1174
|
-
['記録ID', record.recordId],
|
|
1484
|
+
const feedback = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-feedback' });
|
|
1485
|
+
const events = appendNormalFeedbackEvents(doc, record);
|
|
1486
|
+
if (events) feedback.append(events);
|
|
1487
|
+
feedback.append(appendNormalFeedbackComposer(doc, record, options));
|
|
1488
|
+
rail.append(workspaceRailBlock(doc, { title: 'この判断は良かったか', content: feedback }));
|
|
1489
|
+
const scope = workspaceDefinition(doc, [
|
|
1175
1490
|
['プロジェクト', record.projectCode],
|
|
1176
|
-
['Turn', record.turnRef],
|
|
1177
1491
|
]);
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1492
|
+
const range = makeWorkspaceElement(doc, 'div');
|
|
1493
|
+
range.append(scope, appendNormalRecordDetails(doc, record, {
|
|
1494
|
+
onCopyReference: options.onCopyReference,
|
|
1495
|
+
open: options.recordDetailsOpen,
|
|
1496
|
+
onToggle: options.onRecordDetailsToggle,
|
|
1497
|
+
}));
|
|
1498
|
+
rail.append(workspaceRailBlock(doc, { title: '記録の範囲', content: range }));
|
|
1181
1499
|
}
|
|
1182
1500
|
|
|
1183
1501
|
function renderRail(doc, rail, row, detail, onClose, options = {}) {
|
|
@@ -1211,10 +1529,15 @@ function renderRail(doc, rail, row, detail, onClose, options = {}) {
|
|
|
1211
1529
|
attrs: { 'aria-label': '判断の詳細を閉じる' },
|
|
1212
1530
|
}));
|
|
1213
1531
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1532
|
+
const references = makeWorkspaceElement(doc, 'div');
|
|
1533
|
+
references.append(appendReferenceList(doc, row.references));
|
|
1534
|
+
const ids = referenceIdList(doc, row.references);
|
|
1535
|
+
if (ids) {
|
|
1536
|
+
const details = makeWorkspaceElement(doc, 'details', { className: 'bb-jh-record-details' });
|
|
1537
|
+
details.append(makeWorkspaceElement(doc, 'summary', { text: '記録の詳細' }), workspaceDefinition(doc, [['参照のID', ids]]));
|
|
1538
|
+
references.append(details);
|
|
1539
|
+
}
|
|
1540
|
+
rail.append(workspaceRailBlock(doc, { title: '使った参照', content: references }));
|
|
1218
1541
|
rail.append(workspaceRailBlock(doc, {
|
|
1219
1542
|
title: '行った判断',
|
|
1220
1543
|
content: appendTextParts(doc, [
|
|
@@ -1321,9 +1644,7 @@ function statusNotice(doc, phase, error, home, onRetry, { loading = false, slow
|
|
|
1321
1644
|
reload,
|
|
1322
1645
|
});
|
|
1323
1646
|
}
|
|
1324
|
-
if (loading)
|
|
1325
|
-
return workspaceLoadingCard(doc, { title: 'ロード中' });
|
|
1326
|
-
}
|
|
1647
|
+
if (loading) return null;
|
|
1327
1648
|
if (home?.status === 'partial') {
|
|
1328
1649
|
return historyBanner(doc, {
|
|
1329
1650
|
label: '一部の記録だけを表示中',
|
|
@@ -1400,7 +1721,7 @@ function normalSourceStatusLabel(status) {
|
|
|
1400
1721
|
}
|
|
1401
1722
|
|
|
1402
1723
|
function sourceChipText(source) {
|
|
1403
|
-
const name = NORMAL_ENTRYPOINT_LABELS[normalEntrypoint(source.entrypoint)] ??
|
|
1724
|
+
const name = NORMAL_ENTRYPOINT_LABELS[normalEntrypoint(source.entrypoint)] ?? NORMAL_ENTRYPOINT_LABELS.unknown;
|
|
1404
1725
|
if (source?.status === 'pending' || source?.status === 'loading') return `${name}:確認中…`;
|
|
1405
1726
|
if (Number.isInteger(source?.retrieved)) return `${name}:${source.retrieved}件まで届きました`;
|
|
1406
1727
|
const statusLabel = normalSourceStatusLabel(source.status);
|
|
@@ -1409,21 +1730,21 @@ function sourceChipText(source) {
|
|
|
1409
1730
|
return `${name}:${statusLabel}${reason}`;
|
|
1410
1731
|
}
|
|
1411
1732
|
|
|
1412
|
-
function
|
|
1413
|
-
const wrap = makeWorkspaceElement(doc, '
|
|
1733
|
+
function createFilterCombobox(doc, { key, label, value, options, onChange, searchPlaceholder }) {
|
|
1734
|
+
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-filter' });
|
|
1414
1735
|
wrap.append(makeWorkspaceElement(doc, 'span', { text: label }));
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
wrap.append(
|
|
1426
|
-
return wrap;
|
|
1736
|
+
const combobox = workspaceCombobox(doc, {
|
|
1737
|
+
label,
|
|
1738
|
+
value: value ?? '',
|
|
1739
|
+
options: [{ value: '', label: 'すべて' }, ...options],
|
|
1740
|
+
searchPlaceholder,
|
|
1741
|
+
onChange: (next) => onChange(next ?? ''),
|
|
1742
|
+
});
|
|
1743
|
+
// A stable mark lets a redraw return focus to the same filter, whose
|
|
1744
|
+
// accessible name changes with the chosen value.
|
|
1745
|
+
combobox.trigger.setAttribute('data-filter', key);
|
|
1746
|
+
wrap.append(combobox.root);
|
|
1747
|
+
return { root: wrap, combobox };
|
|
1427
1748
|
}
|
|
1428
1749
|
|
|
1429
1750
|
function normalFilterControls(doc, home, state, onChange) {
|
|
@@ -1433,6 +1754,8 @@ function normalFilterControls(doc, home, state, onChange) {
|
|
|
1433
1754
|
for (const project of knownProjects) {
|
|
1434
1755
|
if (!projects.some((option) => option.value === project)) projects.push({ value: project, label: project });
|
|
1435
1756
|
}
|
|
1757
|
+
const sources = Array.isArray(home?.coverage?.sources) ? home.coverage.sources : [];
|
|
1758
|
+
const unconnected = new Set(sources.filter((source) => source.status === 'unavailable').map((source) => normalEntrypoint(source.entrypoint)));
|
|
1436
1759
|
const entrypoints = [...(filters.entrypoints ?? [])];
|
|
1437
1760
|
const knownEntrypoints = (home?.records ?? []).map((record) => record.entrypoint).filter(Boolean);
|
|
1438
1761
|
for (const entrypoint of [...NORMAL_ENTRYPOINTS, ...knownEntrypoints]) {
|
|
@@ -1440,20 +1763,29 @@ function normalFilterControls(doc, home, state, onChange) {
|
|
|
1440
1763
|
entrypoints.push({ value: entrypoint, label: NORMAL_ENTRYPOINT_LABELS[entrypoint] ?? entrypoint });
|
|
1441
1764
|
}
|
|
1442
1765
|
}
|
|
1766
|
+
const entrypointOptions = entrypoints.map((option) => ({
|
|
1767
|
+
...option,
|
|
1768
|
+
label: NORMAL_ENTRYPOINT_FILTER_LABELS[option.value] ?? NORMAL_ENTRYPOINT_LABELS[option.value] ?? option.label,
|
|
1769
|
+
hint: unconnected.has(option.value) ? 'まだつながっていません' : '',
|
|
1770
|
+
}));
|
|
1443
1771
|
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-normal-filters', attrs: { 'aria-label': '絞り込み' } });
|
|
1444
|
-
|
|
1772
|
+
const project = createFilterCombobox(doc, {
|
|
1773
|
+
key: 'project',
|
|
1445
1774
|
label: 'プロジェクト',
|
|
1446
1775
|
value: state.project,
|
|
1447
1776
|
options: projects,
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1777
|
+
searchPlaceholder: 'プロジェクトを探す',
|
|
1778
|
+
onChange: (next) => onChange({ project: next || null }),
|
|
1779
|
+
});
|
|
1780
|
+
const entrypoint = createFilterCombobox(doc, {
|
|
1781
|
+
key: 'entrypoint',
|
|
1782
|
+
label: '判断したAI',
|
|
1452
1783
|
value: state.entrypoint,
|
|
1453
|
-
options:
|
|
1454
|
-
onChange: (
|
|
1455
|
-
})
|
|
1456
|
-
|
|
1784
|
+
options: entrypointOptions,
|
|
1785
|
+
onChange: (next) => onChange({ entrypoint: next || null }),
|
|
1786
|
+
});
|
|
1787
|
+
wrap.append(project.root, entrypoint.root);
|
|
1788
|
+
return { root: wrap, comboboxes: [project.combobox, entrypoint.combobox] };
|
|
1457
1789
|
}
|
|
1458
1790
|
|
|
1459
1791
|
function paginationControls(doc, aggregate, onNext) {
|
|
@@ -1476,8 +1808,9 @@ function makeEntrypointStatus(doc, row) {
|
|
|
1476
1808
|
wrap.append(makeWorkspaceElement(doc, 'strong', { text: row.delegated ? 'Brainbase' : '本人' }));
|
|
1477
1809
|
return wrap;
|
|
1478
1810
|
}
|
|
1479
|
-
wrap.append(makeWorkspaceElement(doc, 'strong', { text: NORMAL_ENTRYPOINT_LABELS[row.entrypoint] ??
|
|
1480
|
-
|
|
1811
|
+
wrap.append(makeWorkspaceElement(doc, 'strong', { text: NORMAL_ENTRYPOINT_LABELS[row.entrypoint] ?? NORMAL_ENTRYPOINT_LABELS.unknown }));
|
|
1812
|
+
const status = judgmentStatusLabel(row.judgment?.status);
|
|
1813
|
+
if (status) wrap.append(makeWorkspaceElement(doc, 'small', { className: 'bb-jh-waiting', text: status }));
|
|
1481
1814
|
return wrap;
|
|
1482
1815
|
}
|
|
1483
1816
|
|
|
@@ -1494,26 +1827,37 @@ function skeletonRows(doc, count = 5) {
|
|
|
1494
1827
|
}));
|
|
1495
1828
|
}
|
|
1496
1829
|
|
|
1830
|
+
function loadingLine(doc) {
|
|
1831
|
+
return makeWorkspaceElement(doc, 'p', {
|
|
1832
|
+
className: 'bb-jh-summary bb-jh-loading',
|
|
1833
|
+
text: '判断の履歴を読み込んでいます…',
|
|
1834
|
+
attrs: { role: 'status', 'aria-live': 'polite' },
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1497
1838
|
function summaryLine(doc, aggregate, { loading = false } = {}) {
|
|
1498
|
-
const line = makeWorkspaceElement(doc, 'p', { className: 'bb-jh-summary' });
|
|
1499
1839
|
const retrieved = Number.isInteger(aggregate?.retrievedCount) ? aggregate.retrievedCount : (aggregate?.rows?.length ?? 0);
|
|
1500
1840
|
const complete = aggregate?.countComplete === true && Number.isInteger(aggregate?.totalCount);
|
|
1501
1841
|
const showCount = complete || retrieved > 0;
|
|
1502
|
-
if (showCount)
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const references = loading ? null : (aggregate?.stats?.referenceCount ?? aggregate?.stats?.references);
|
|
1510
|
-
const confirmed = loading ? null : (aggregate?.stats?.confirmedOutcomes ?? aggregate?.stats?.outcomes);
|
|
1842
|
+
if (loading && !showCount) return loadingLine(doc);
|
|
1843
|
+
const line = makeWorkspaceElement(doc, 'p', { className: 'bb-jh-summary' });
|
|
1844
|
+
line.append(makeWorkspaceElement(doc, 'strong', { text: `${complete ? aggregate.totalCount : retrieved}件` }));
|
|
1845
|
+
line.append(makeWorkspaceElement(doc, 'span', { text: loading ? ' 届きました(続きを読み込んでいます…)' : ' 取得済み' }));
|
|
1846
|
+
if (loading) return line;
|
|
1847
|
+
const references = aggregate?.stats?.referenceCount ?? aggregate?.stats?.references;
|
|
1848
|
+
const confirmed = aggregate?.stats?.confirmedOutcomes ?? aggregate?.stats?.outcomes;
|
|
1511
1849
|
line.append(makeWorkspaceElement(doc, 'span', { text: ' ・ 使った参照 ' }));
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
line.append(makeWorkspaceElement(doc, 'span', { text: ' ・
|
|
1515
|
-
|
|
1516
|
-
|
|
1850
|
+
line.append(makeWorkspaceElement(doc, Number.isInteger(references) ? 'strong' : 'span', { text: Number.isInteger(references) ? `${references}件` : '数えられません' }));
|
|
1851
|
+
if (aggregate?.mode !== 'normal') return line;
|
|
1852
|
+
line.append(makeWorkspaceElement(doc, 'span', { text: ' ・ 結果を確かめた判断 ' }));
|
|
1853
|
+
line.append(makeWorkspaceElement(doc, Number.isInteger(confirmed) ? 'strong' : 'span', { text: Number.isInteger(confirmed) ? `${confirmed}件` : '数えられません' }));
|
|
1854
|
+
// Results are never recorded automatically; say how a person adds one.
|
|
1855
|
+
if (Number.isInteger(confirmed) && confirmed < retrieved) {
|
|
1856
|
+
line.append(makeWorkspaceElement(doc, 'span', {
|
|
1857
|
+
className: 'bb-jh-summary-note',
|
|
1858
|
+
text: ' 結果は自動では記録されません。判断を開いて「良かったか」を記録すると数えられます。',
|
|
1859
|
+
}));
|
|
1860
|
+
}
|
|
1517
1861
|
return line;
|
|
1518
1862
|
}
|
|
1519
1863
|
|
|
@@ -1529,7 +1873,7 @@ function formatCheckedAt(value) {
|
|
|
1529
1873
|
}
|
|
1530
1874
|
}
|
|
1531
1875
|
|
|
1532
|
-
function makeRows(doc, aggregate, selectedKey, onSelect) {
|
|
1876
|
+
function makeRows(doc, aggregate, selectedKey, onSelect, labels = null) {
|
|
1533
1877
|
return aggregate.rows.map((row) => ({
|
|
1534
1878
|
key: row.key,
|
|
1535
1879
|
label: `${formatDate(row.recordedAt)} ${row.mode === 'normal' ? text(row.judgment?.summary) || '判断履歴' : text(row.proof?.decision?.summary) || '判断履歴'}`,
|
|
@@ -1539,7 +1883,7 @@ function makeRows(doc, aggregate, selectedKey, onSelect) {
|
|
|
1539
1883
|
cells: [
|
|
1540
1884
|
makeWorkspaceElement(doc, 'span', { className: 'bb-jh-date', text: formatDate(row.recordedAt) }),
|
|
1541
1885
|
makeDecisionSummary(doc, row),
|
|
1542
|
-
makeReferenceSummary(doc, row.references),
|
|
1886
|
+
makeReferenceSummary(doc, row.references, row.mode === 'normal' ? labels : null),
|
|
1543
1887
|
makeEntrypointStatus(doc, row),
|
|
1544
1888
|
],
|
|
1545
1889
|
}));
|
|
@@ -1560,6 +1904,9 @@ export function createJudgmentHistoryUI({
|
|
|
1560
1904
|
now = new Date(),
|
|
1561
1905
|
skeletonDelayMs = 300,
|
|
1562
1906
|
slowDelayMs = 10000,
|
|
1907
|
+
// Optional host port: (references: {ref, kind}[]) => Promise<Map|object of ref -> name>.
|
|
1908
|
+
// The host resolves names under the viewer's current permission.
|
|
1909
|
+
resolveReferenceLabels = null,
|
|
1563
1910
|
} = {}) {
|
|
1564
1911
|
if (!root) throw new TypeError('root is required');
|
|
1565
1912
|
const doc = safeDocument(explicitDocument);
|
|
@@ -1598,9 +1945,13 @@ export function createJudgmentHistoryUI({
|
|
|
1598
1945
|
checkedAt: null,
|
|
1599
1946
|
skeletonTimer: null,
|
|
1600
1947
|
slowTimer: null,
|
|
1948
|
+
labels: new Map(),
|
|
1601
1949
|
};
|
|
1602
1950
|
|
|
1603
1951
|
let filterPopover = null;
|
|
1952
|
+
let filterComboboxes = [];
|
|
1953
|
+
// The record details stay open across redraws of the same screen.
|
|
1954
|
+
let recordDetailsOpen = false;
|
|
1604
1955
|
let filterExpanded = false;
|
|
1605
1956
|
let drawer = null;
|
|
1606
1957
|
let drawerContent = null;
|
|
@@ -1619,19 +1970,13 @@ export function createJudgmentHistoryUI({
|
|
|
1619
1970
|
className: 'bb-ws-error-stack bb-jh-notifications', attrs: { 'aria-label': '判断履歴の通知' },
|
|
1620
1971
|
});
|
|
1621
1972
|
if (notificationRoot) notificationRoot.append(notifications);
|
|
1622
|
-
let loadingCard = null;
|
|
1623
|
-
let dismissLoading = null;
|
|
1624
1973
|
let statusCard = null;
|
|
1625
1974
|
let statusKey = null;
|
|
1626
1975
|
|
|
1976
|
+
// Loading is shown in the page (status line and placeholder rows), the same
|
|
1977
|
+
// way the other workspace screens do; the floating stack keeps only the
|
|
1978
|
+
// states that remain after loading, such as a partial or failed read.
|
|
1627
1979
|
function updateNotifications(loading) {
|
|
1628
|
-
if (loading) {
|
|
1629
|
-
dismissLoading?.(); dismissLoading = null;
|
|
1630
|
-
if (!loadingCard) loadingCard = workspaceLoadingCard(doc, { title: 'ロード中' });
|
|
1631
|
-
if (loadingCard.parentNode !== notifications) notifications.append(loadingCard);
|
|
1632
|
-
} else if (loadingCard && !dismissLoading) {
|
|
1633
|
-
dismissLoading = workspaceDismissNotification(loadingCard);
|
|
1634
|
-
}
|
|
1635
1980
|
const home = state.home;
|
|
1636
1981
|
const key = loading ? null : JSON.stringify([state.phase, state.error, home?.status, home?.coverage]);
|
|
1637
1982
|
if (statusKey !== key) {
|
|
@@ -1725,9 +2070,8 @@ export function createJudgmentHistoryUI({
|
|
|
1725
2070
|
draft = {
|
|
1726
2071
|
recordId,
|
|
1727
2072
|
eventId: newFeedbackEventId(),
|
|
1728
|
-
|
|
2073
|
+
answer: null,
|
|
1729
2074
|
summary: '',
|
|
1730
|
-
outcomeStatus: '',
|
|
1731
2075
|
phase: 'idle',
|
|
1732
2076
|
status: '',
|
|
1733
2077
|
};
|
|
@@ -1796,34 +2140,50 @@ export function createJudgmentHistoryUI({
|
|
|
1796
2140
|
const clipboard = globalThis.navigator?.clipboard;
|
|
1797
2141
|
if (!clipboard || typeof clipboard.writeText !== 'function') throw new Error('clipboard_unavailable');
|
|
1798
2142
|
await clipboard.writeText(serialized);
|
|
1799
|
-
status.textContent = '
|
|
2143
|
+
status.textContent = 'コピーしました。';
|
|
1800
2144
|
return true;
|
|
1801
2145
|
} catch {
|
|
1802
|
-
status.textContent = '
|
|
2146
|
+
status.textContent = 'コピーできませんでした。上の内容を選んでコピーしてください。';
|
|
1803
2147
|
return false;
|
|
1804
2148
|
}
|
|
1805
2149
|
}
|
|
1806
2150
|
|
|
2151
|
+
// Keep the list's "結果を確かめた判断" count in step with an answer that
|
|
2152
|
+
// was read back from the canonical record, without reloading the page.
|
|
2153
|
+
function applyFeedbackToHome(recordId, feedbackEvents) {
|
|
2154
|
+
if (state.home?.mode !== 'normal' || !Array.isArray(state.home.records)) return;
|
|
2155
|
+
const index = state.home.records.findIndex((record) => record.recordId === recordId);
|
|
2156
|
+
if (index < 0) return;
|
|
2157
|
+
const records = [...state.home.records];
|
|
2158
|
+
records[index] = { ...records[index], feedbackEvents: Array.isArray(feedbackEvents) ? feedbackEvents : [] };
|
|
2159
|
+
state.home = { ...state.home, records };
|
|
2160
|
+
state.aggregate = aggregateJudgmentHistory(state.home, { now: readNow(), period: state.period, query: state.query });
|
|
2161
|
+
}
|
|
2162
|
+
|
|
1807
2163
|
async function submitFeedback(row, controls) {
|
|
1808
2164
|
if (!row || row.mode !== 'normal' || !controls || !request || state.disposed) return false;
|
|
1809
2165
|
const current = feedbackDraft(row.recordId);
|
|
1810
|
-
const
|
|
1811
|
-
const
|
|
1812
|
-
|
|
2166
|
+
const answer = feedbackAnswer(current?.answer);
|
|
2167
|
+
const written = text(controls.summary?.value ?? current?.summary);
|
|
2168
|
+
if (!answer) {
|
|
2169
|
+
updateFeedbackDraft(row.recordId, { summary: written, phase: 'error', status: '「良かった」「直すところがある」「まだ分からない」から1つ選んでください。' });
|
|
2170
|
+
render();
|
|
2171
|
+
return false;
|
|
2172
|
+
}
|
|
2173
|
+
const summary = written || answer.fallbackSummary;
|
|
1813
2174
|
if (!summary) {
|
|
1814
|
-
updateFeedbackDraft(row.recordId, {
|
|
2175
|
+
updateFeedbackDraft(row.recordId, { summary: written, phase: 'error', status: 'どこを、どう直すかを書いてください。' });
|
|
1815
2176
|
render();
|
|
1816
2177
|
return false;
|
|
1817
2178
|
}
|
|
1818
|
-
const
|
|
2179
|
+
const { kind } = answer;
|
|
1819
2180
|
const content = { summary };
|
|
1820
|
-
if (
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
: displayedEventId || current?.eventId || newFeedbackEventId();
|
|
2181
|
+
if (answer.outcomeStatus) content.outcome_status = answer.outcomeStatus;
|
|
2182
|
+
// The draft keeps one event id per record, so a retry after an
|
|
2183
|
+
// unconfirmed save appends the same event instead of a second one.
|
|
2184
|
+
const eventId = current?.eventId || newFeedbackEventId();
|
|
1825
2185
|
const expected = { recordId: row.recordId, eventId, kind, content };
|
|
1826
|
-
updateFeedbackDraft(row.recordId, { eventId,
|
|
2186
|
+
updateFeedbackDraft(row.recordId, { eventId, summary: written, phase: 'submitting', status: '' });
|
|
1827
2187
|
render();
|
|
1828
2188
|
try {
|
|
1829
2189
|
const response = await request(`${state.activeBasePath}/feedback`, {
|
|
@@ -1839,7 +2199,7 @@ export function createJudgmentHistoryUI({
|
|
|
1839
2199
|
const unavailable = [401, 403, 404, 405, 501, 503].includes(statusCode);
|
|
1840
2200
|
updateFeedbackDraft(row.recordId, {
|
|
1841
2201
|
phase: unavailable ? 'unavailable' : 'unconfirmed',
|
|
1842
|
-
status: unavailable ?
|
|
2202
|
+
status: unavailable ? '記録先に接続できません。' : '',
|
|
1843
2203
|
});
|
|
1844
2204
|
render();
|
|
1845
2205
|
return false;
|
|
@@ -1855,18 +2215,16 @@ export function createJudgmentHistoryUI({
|
|
|
1855
2215
|
const responseReadback = responseFeedbackReadback(responsePayload, expected);
|
|
1856
2216
|
if (detailReadback || responseReadback) {
|
|
1857
2217
|
updateFeedbackDraft(row.recordId, { phase: 'saved', status: '' });
|
|
2218
|
+
if (detailReadback) applyFeedbackToHome(row.recordId, detail.feedbackEvents);
|
|
1858
2219
|
} else if (state.detailPhase === 'error') {
|
|
1859
|
-
updateFeedbackDraft(row.recordId, { phase: 'unavailable', status: '
|
|
2220
|
+
updateFeedbackDraft(row.recordId, { phase: 'unavailable', status: '記録を読み直せませんでした。' });
|
|
1860
2221
|
} else {
|
|
1861
|
-
updateFeedbackDraft(row.recordId, { phase: 'unconfirmed', status: '
|
|
2222
|
+
updateFeedbackDraft(row.recordId, { phase: 'unconfirmed', status: '' });
|
|
1862
2223
|
}
|
|
1863
2224
|
render();
|
|
1864
2225
|
return detailReadback || responseReadback;
|
|
1865
|
-
} catch
|
|
1866
|
-
updateFeedbackDraft(row.recordId, {
|
|
1867
|
-
phase: 'unavailable',
|
|
1868
|
-
status: `正本へ接続できません。${error instanceof Error ? error.message : String(error)}`,
|
|
1869
|
-
});
|
|
2226
|
+
} catch {
|
|
2227
|
+
updateFeedbackDraft(row.recordId, { phase: 'unavailable', status: '記録先に接続できません。' });
|
|
1870
2228
|
render();
|
|
1871
2229
|
return false;
|
|
1872
2230
|
}
|
|
@@ -1896,6 +2254,35 @@ export function createJudgmentHistoryUI({
|
|
|
1896
2254
|
void load({ append: false });
|
|
1897
2255
|
}
|
|
1898
2256
|
|
|
2257
|
+
function requestReferenceLabels(rows) {
|
|
2258
|
+
const pending = [];
|
|
2259
|
+
for (const row of rows) {
|
|
2260
|
+
if (row.mode !== 'normal' || !Array.isArray(row.references)) continue;
|
|
2261
|
+
for (const reference of row.references) {
|
|
2262
|
+
const ref = text(reference?.ref);
|
|
2263
|
+
const kind = judgmentReferenceKind(reference);
|
|
2264
|
+
if (!ref || kind === PERSONAL_KNOWLEDGE_KIND || referenceLabel(reference) || state.labels.has(ref)) continue;
|
|
2265
|
+
state.labels.set(ref, { status: typeof resolveReferenceLabels === 'function' ? 'loading' : 'missing' });
|
|
2266
|
+
pending.push({ ref, kind });
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
if (!pending.length || typeof resolveReferenceLabels !== 'function') return;
|
|
2270
|
+
const settle = (resolved) => {
|
|
2271
|
+
if (state.disposed) return;
|
|
2272
|
+
const lookup = resolved instanceof Map
|
|
2273
|
+
? (ref) => resolved.get(ref)
|
|
2274
|
+
: isRecord(resolved) ? (ref) => resolved[ref] : () => null;
|
|
2275
|
+
for (const { ref } of pending) {
|
|
2276
|
+
const label = text(lookup(ref));
|
|
2277
|
+
state.labels.set(ref, label ? { status: 'found', label } : { status: 'missing' });
|
|
2278
|
+
}
|
|
2279
|
+
render();
|
|
2280
|
+
};
|
|
2281
|
+
Promise.resolve()
|
|
2282
|
+
.then(() => resolveReferenceLabels(pending.map((entry) => ({ ...entry }))))
|
|
2283
|
+
.then(settle, () => settle(null));
|
|
2284
|
+
}
|
|
2285
|
+
|
|
1899
2286
|
async function loadDetail(row) {
|
|
1900
2287
|
if (!row || row.mode !== 'normal' || !request || state.disposed) return null;
|
|
1901
2288
|
const generation = ++state.detailGeneration;
|
|
@@ -1936,17 +2323,24 @@ export function createJudgmentHistoryUI({
|
|
|
1936
2323
|
if (state.disposed) return;
|
|
1937
2324
|
updateNotifications(state.phase === 'loading');
|
|
1938
2325
|
if (state.phase === 'loading' && !state.showSkeleton && !state.arrived) {
|
|
1939
|
-
|
|
2326
|
+
// Before the placeholder rows appear, say in the page what is happening.
|
|
2327
|
+
const pending = makeWorkspaceElement(doc, 'div', { className: 'bb-jh', attrs: { 'aria-busy': 'true' } });
|
|
2328
|
+
if (!notificationRoot) pending.append(notifications);
|
|
2329
|
+
pending.append(loadingLine(doc));
|
|
2330
|
+
root.replaceChildren(pending);
|
|
1940
2331
|
return;
|
|
1941
2332
|
}
|
|
1942
2333
|
const focusedFilter = filterExpanded && filterPopover
|
|
1943
2334
|
? findFirst(filterPopover.panel, node => node === doc.activeElement) : null;
|
|
1944
2335
|
const filterFocus = focusedFilter && {
|
|
2336
|
+
filter: attributeValue(focusedFilter, 'data-filter'),
|
|
1945
2337
|
label: attributeValue(focusedFilter, 'aria-label'), period: attributeValue(focusedFilter, 'data-period'),
|
|
1946
2338
|
};
|
|
1947
2339
|
filterPopover?.destroy();
|
|
1948
2340
|
filterPopover = null;
|
|
1949
|
-
const
|
|
2341
|
+
for (const combobox of filterComboboxes) combobox.destroy();
|
|
2342
|
+
filterComboboxes = [];
|
|
2343
|
+
const previousSearchInput = findFirst(root, isSearchInput);
|
|
1950
2344
|
const previousSearchSelection = previousSearchInput && Number.isInteger(previousSearchInput.selectionStart)
|
|
1951
2345
|
? { start: previousSearchInput.selectionStart, end: previousSearchInput.selectionEnd ?? previousSearchInput.selectionStart }
|
|
1952
2346
|
: null;
|
|
@@ -1995,7 +2389,11 @@ export function createJudgmentHistoryUI({
|
|
|
1995
2389
|
}
|
|
1996
2390
|
}));
|
|
1997
2391
|
const showNormalFilters = loading ? sourceHome?.mode !== 'legacy' : normalMode();
|
|
1998
|
-
if (showNormalFilters)
|
|
2392
|
+
if (showNormalFilters) {
|
|
2393
|
+
const controlsForFilters = normalFilterControls(doc, sourceHome ?? state.home, state, handleNormalFilterChange);
|
|
2394
|
+
filterComboboxes = controlsForFilters.comboboxes;
|
|
2395
|
+
filterFields.append(controlsForFilters.root);
|
|
2396
|
+
}
|
|
1999
2397
|
controls.append(createSearch(doc, state.query, (query) => {
|
|
2000
2398
|
state.query = String(query);
|
|
2001
2399
|
state.focusSearch = true;
|
|
@@ -2041,6 +2439,7 @@ export function createJudgmentHistoryUI({
|
|
|
2041
2439
|
const emptyText = loading ? '' : state.home?.status === 'partial' || (normalMode() && !aggregate.countComplete)
|
|
2042
2440
|
? 'この期間に取得できた判断履歴はありません。全件数が未確認のため0件とは確認できません。'
|
|
2043
2441
|
: 'この期間の判断履歴はありません。';
|
|
2442
|
+
requestReferenceLabels(aggregate.rows);
|
|
2044
2443
|
const dataRows = makeRows(doc, aggregate, state.selectedKey, (key) => {
|
|
2045
2444
|
state.lastFocusedKey = key;
|
|
2046
2445
|
state.selectedKey = key;
|
|
@@ -2049,11 +2448,11 @@ export function createJudgmentHistoryUI({
|
|
|
2049
2448
|
render();
|
|
2050
2449
|
const selectedRow = aggregate.rows.find((row) => row.key === key);
|
|
2051
2450
|
if (selectedRow?.mode === 'normal') void loadDetail(selectedRow);
|
|
2052
|
-
});
|
|
2451
|
+
}, state.labels);
|
|
2053
2452
|
wrapper.append(workspaceLedger(doc, {
|
|
2054
2453
|
className: 'bb-jh-ledger',
|
|
2055
2454
|
ariaLabel: '判断履歴',
|
|
2056
|
-
columns: ['日時', '行った判断', '使った参照', '
|
|
2455
|
+
columns: ['日時', '行った判断', '使った参照', '判断したAI'],
|
|
2057
2456
|
rows: loading ? [...dataRows, ...skeletonRows(doc)] : dataRows,
|
|
2058
2457
|
empty: emptyText,
|
|
2059
2458
|
}));
|
|
@@ -2064,20 +2463,22 @@ export function createJudgmentHistoryUI({
|
|
|
2064
2463
|
}
|
|
2065
2464
|
});
|
|
2066
2465
|
if (pagination) wrapper.append(pagination);
|
|
2067
|
-
const nextSearchInput = findFirst(wrapper,
|
|
2466
|
+
const nextSearchInput = findFirst(wrapper, isSearchInput);
|
|
2068
2467
|
if (previousSearchInput && nextSearchInput) {
|
|
2069
2468
|
previousSearchInput.value = state.query;
|
|
2070
2469
|
replaceNode(nextSearchInput, previousSearchInput);
|
|
2071
2470
|
}
|
|
2072
2471
|
root.replaceChildren(wrapper);
|
|
2073
2472
|
if (filterExpanded && filterFocus) {
|
|
2074
|
-
const field = findFirst(filterPopover.panel, node => filterFocus.
|
|
2075
|
-
? attributeValue(node, '
|
|
2076
|
-
: filterFocus.
|
|
2473
|
+
const field = findFirst(filterPopover.panel, node => (filterFocus.filter
|
|
2474
|
+
? attributeValue(node, 'data-filter') === filterFocus.filter
|
|
2475
|
+
: filterFocus.label
|
|
2476
|
+
? attributeValue(node, 'aria-label') === filterFocus.label
|
|
2477
|
+
: filterFocus.period && attributeValue(node, 'data-period') === filterFocus.period));
|
|
2077
2478
|
field?.focus?.();
|
|
2078
2479
|
}
|
|
2079
2480
|
if (state.focusSearch || previousSearchFocused) {
|
|
2080
|
-
const input = findFirst(root,
|
|
2481
|
+
const input = findFirst(root, isSearchInput);
|
|
2081
2482
|
input?.focus?.();
|
|
2082
2483
|
const start = previousSearchSelection?.start ?? state.query.length;
|
|
2083
2484
|
const end = previousSearchSelection?.end ?? start;
|
|
@@ -2091,14 +2492,21 @@ export function createJudgmentHistoryUI({
|
|
|
2091
2492
|
detailReady: state.detailPhase === 'ready' && Boolean(selectedDetail),
|
|
2092
2493
|
detailPhase: state.detailPhase,
|
|
2093
2494
|
draft: feedbackDraft(selected.recordId),
|
|
2094
|
-
|
|
2095
|
-
|
|
2495
|
+
onAnswerChange: (answer) => {
|
|
2496
|
+
const current = feedbackDraft(selected.recordId);
|
|
2497
|
+
updateFeedbackDraft(selected.recordId, {
|
|
2498
|
+
answer: feedbackAnswer(answer)?.value ?? null,
|
|
2499
|
+
phase: current?.phase === 'error' ? 'idle' : current?.phase,
|
|
2500
|
+
status: current?.phase === 'error' ? '' : current?.status,
|
|
2501
|
+
});
|
|
2096
2502
|
render();
|
|
2097
2503
|
},
|
|
2098
2504
|
onSummaryChange: (summary) => updateFeedbackDraft(selected.recordId, { summary: String(summary ?? '') }),
|
|
2099
|
-
|
|
2505
|
+
recordDetailsOpen,
|
|
2506
|
+
onRecordDetailsToggle: (open) => { recordDetailsOpen = open; },
|
|
2100
2507
|
onSubmitFeedback: (controls) => void submitFeedback(selected, controls),
|
|
2101
2508
|
onCopyReference: (payload) => void copyReference(payload),
|
|
2509
|
+
labels: state.labels,
|
|
2102
2510
|
}
|
|
2103
2511
|
: {};
|
|
2104
2512
|
if (nativeDrawer) {
|
|
@@ -2253,7 +2661,7 @@ export function createJudgmentHistoryUI({
|
|
|
2253
2661
|
state.generation += 1;
|
|
2254
2662
|
clearLoadTimers();
|
|
2255
2663
|
filterPopover?.destroy();
|
|
2256
|
-
|
|
2664
|
+
for (const combobox of filterComboboxes) combobox.destroy();
|
|
2257
2665
|
notifications.remove?.();
|
|
2258
2666
|
disposeDrawer();
|
|
2259
2667
|
root.removeEventListener?.('keydown', handleKeyDown);
|