story-summary: facts UI split + relationships from facts
This commit is contained in:
@@ -76,6 +76,27 @@ export function isRelationFact(f) {
|
||||
return /^对.+的/.test(f.p);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// 从 facts 提取关系(供关系图 UI 使用)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export function extractRelationshipsFromFacts(facts) {
|
||||
return (facts || [])
|
||||
.filter(f => !f.retracted && isRelationFact(f))
|
||||
.map(f => {
|
||||
const match = f.p.match(/^对(.+)的/);
|
||||
const to = match ? match[1] : '';
|
||||
if (!to) return null;
|
||||
return {
|
||||
from: f.s,
|
||||
to,
|
||||
label: f.o,
|
||||
trend: f.trend || '陌生',
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 fact 的唯一键(s + p)
|
||||
*/
|
||||
|
||||
@@ -1862,52 +1862,39 @@ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "
|
||||
|
||||
|
||||
function renderFacts(facts) {
|
||||
summaryData.facts = facts || [];
|
||||
summaryData.facts = facts || [];
|
||||
|
||||
const container = $('facts-list');
|
||||
if (!container) return;
|
||||
const container = $('facts-list');
|
||||
if (!container) return;
|
||||
|
||||
const activeFacts = (facts || []).filter(f => !f.retracted);
|
||||
const isRelation = f => /^对.+的/.test(f.p);
|
||||
const stateFacts = (facts || []).filter(f => !f.retracted && !isRelation(f));
|
||||
|
||||
if (!activeFacts.length) {
|
||||
setHtml(container, '<div class="empty">暂无事实记录</div>');
|
||||
return;
|
||||
}
|
||||
if (!stateFacts.length) {
|
||||
setHtml(container, '<div class="empty">暂无状态记录</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
const relations = activeFacts.filter(f => /^对.+的/.test(f.p));
|
||||
const states = activeFacts.filter(f => !/^对.+的/.test(f.p));
|
||||
const grouped = new Map();
|
||||
for (const f of stateFacts) {
|
||||
if (!grouped.has(f.s)) grouped.set(f.s, []);
|
||||
grouped.get(f.s).push(f);
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
if (states.length) {
|
||||
html += `<div class="fact-group">
|
||||
<div class="fact-group-title">状态/属性</div>
|
||||
${states.map(f => `
|
||||
let html = '';
|
||||
for (const [subject, items] of grouped) {
|
||||
html += `<div class="fact-group">
|
||||
<div class="fact-group-title">${h(subject)}</div>
|
||||
${items.map(f => `
|
||||
<div class="fact-item">
|
||||
<span class="fact-subject">${h(f.s)}</span>
|
||||
<span class="fact-predicate">${h(f.p)}</span>
|
||||
<span class="fact-object">${h(f.o)}</span>
|
||||
<span class="fact-since">#${(f.since || 0) + 1}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (relations.length) {
|
||||
html += `<div class="fact-group">
|
||||
<div class="fact-group-title">人物关系</div>
|
||||
${relations.map(f => `
|
||||
<div class="fact-item">
|
||||
<span class="fact-subject">${h(f.s)}</span>
|
||||
<span class="fact-predicate">${h(f.p)}</span>
|
||||
<span class="fact-object">${h(f.o)}</span>
|
||||
${f.trend ? `<span class="fact-trend ${TREND_CLASS[f.trend] || ''}">${h(f.trend)}</span>` : ''}
|
||||
<span class="fact-since">#${(f.since || 0) + 1}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>`;
|
||||
setHtml(container, html);
|
||||
}
|
||||
|
||||
setHtml(container, html);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
|
||||
.fact-group {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.fact-group:last-child {
|
||||
@@ -29,65 +29,43 @@
|
||||
}
|
||||
|
||||
.fact-group-title {
|
||||
font-size: 0.6875rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--txt3);
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--bdr2);
|
||||
color: var(--hl);
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px dashed var(--bdr2);
|
||||
}
|
||||
|
||||
.fact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 6px;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
margin-bottom: 4px;
|
||||
background: var(--bg3);
|
||||
border: 1px solid var(--bdr2);
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fact-item:hover {
|
||||
border-color: var(--bdr);
|
||||
background: var(--bg2);
|
||||
}
|
||||
|
||||
.fact-subject {
|
||||
font-weight: 600;
|
||||
color: var(--txt);
|
||||
}
|
||||
|
||||
.fact-predicate {
|
||||
color: var(--txt3);
|
||||
font-size: 0.75rem;
|
||||
color: var(--txt2);
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.fact-predicate::before {
|
||||
content: '→';
|
||||
margin-right: 4px;
|
||||
.fact-predicate::after {
|
||||
content: ':';
|
||||
}
|
||||
|
||||
.fact-object {
|
||||
color: var(--hl);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fact-trend {
|
||||
font-size: 0.6875rem;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
white-space: nowrap;
|
||||
color: var(--txt);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fact-since {
|
||||
font-size: 0.625rem;
|
||||
color: var(--txt3);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<!-- Facts -->
|
||||
<section class="card facts">
|
||||
<div class="sec-head">
|
||||
<div class="sec-title">事实图谱</div>
|
||||
<div class="sec-title">世界状态</div>
|
||||
<button class="sec-btn" data-section="facts">编辑</button>
|
||||
</div>
|
||||
<div class="facts-list scroll" id="facts-list"></div>
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
calcHideRange,
|
||||
rollbackSummaryIfNeeded,
|
||||
clearSummaryData,
|
||||
extractRelationshipsFromFacts,
|
||||
} from "./data/store.js";
|
||||
|
||||
// prompt text builder
|
||||
@@ -848,14 +849,19 @@ async function sendFrameBaseData(store, totalFloors) {
|
||||
function sendFrameFullData(store, totalFloors) {
|
||||
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
||||
if (store?.json) {
|
||||
const facts = store.json.facts || [];
|
||||
const relationships = extractRelationshipsFromFacts(facts);
|
||||
postToFrame({
|
||||
type: "SUMMARY_FULL_DATA",
|
||||
payload: {
|
||||
keywords: store.json.keywords || [],
|
||||
events: store.json.events || [],
|
||||
characters: store.json.characters || { main: [], relationships: [] },
|
||||
characters: {
|
||||
main: store.json.characters?.main || [],
|
||||
relationships,
|
||||
},
|
||||
arcs: store.json.arcs || [],
|
||||
world: store.json.world || [],
|
||||
facts,
|
||||
lastSummarizedMesId: lastSummarized,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user