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