Filter goal refreshes by conversation note

This commit is contained in:
kris
2026-04-07 17:29:42 +08:00
parent 8fc94f1849
commit b06b084438
4 changed files with 64 additions and 13 deletions

View File

@@ -26,7 +26,11 @@ export default async function GoalsPage({
return (
<AppShell bottomNav={false}>
<RealtimeRefresh projectId={projectId} events={["conversation.updated"]} />
<RealtimeRefresh
projectId={projectId}
events={["conversation.updated"]}
conversationUpdatedNotes={["project_goals.updated"]}
/>
<StatusBar />
<PageNav title="项目目标" backHref={`/conversations/${projectId}`} />
<div className="flex flex-col gap-3 px-[18px] pb-6">

View File

@@ -26,6 +26,7 @@ export default async function VersionsPage({
<RealtimeRefresh
projectId={projectId}
events={["conversation.updated", "project.messages.updated", "ota.updated"]}
conversationUpdatedNotes={["project_goals.updated"]}
/>
<StatusBar />
<PageNav title="版本迭代记录" backHref={`/conversations/${projectId}`} />

View File

@@ -254,9 +254,11 @@ export function NativeAppBridge() {
export function RealtimeRefresh({
events,
projectId,
conversationUpdatedNotes,
}: {
events: BossEventName[];
projectId?: string;
conversationUpdatedNotes?: string[];
}) {
const router = useRouter();
@@ -268,21 +270,40 @@ export function RealtimeRefresh({
...events,
]));
const shouldRefresh = (event: Event) => {
if (!projectId || !("data" in event) || typeof event.data !== "string" || !event.data.trim()) {
return true;
let payload: { projectId?: string; note?: string } | null = null;
const eventData = "data" in event && typeof event.data === "string" ? event.data : "";
const hasPayloadData = Boolean(eventData.trim());
if (hasPayloadData) {
try {
const parsed = JSON.parse(eventData) as { projectId?: string; note?: string };
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
payload = parsed;
}
} catch {
payload = null;
}
}
try {
const payload = JSON.parse(event.data) as { projectId?: string };
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
if (projectId) {
if (!payload || typeof payload.projectId !== "string" || !payload.projectId.trim()) {
return true;
}
if (typeof payload.projectId !== "string" || !payload.projectId.trim()) {
return true;
if (payload.projectId !== projectId) {
return false;
}
return payload.projectId === projectId;
} catch {
return true;
}
if (event.type === "conversation.updated" && conversationUpdatedNotes?.length) {
if (!payload || typeof payload.note !== "string" || !payload.note.trim()) {
return false;
}
if ((payload.note) && !conversationUpdatedNotes.includes(payload.note)) {
return false;
}
}
return true;
};
const listenerMap = new Map<string, (event: Event) => void>();
@@ -306,7 +327,7 @@ export function RealtimeRefresh({
}
source.close();
};
}, [events, projectId, router]);
}, [conversationUpdatedNotes, events, projectId, router]);
return null;
}

View File

@@ -14,7 +14,21 @@ test("RealtimeRefresh supports project-scoped refresh filtering", async () => {
const source = await readWorkspaceFile("src/components/app-runtime.tsx");
assert.match(source, /projectId\?: string/, "expected RealtimeRefresh to accept an optional projectId");
assert.match(source, /payload\.projectId === projectId/, "expected RealtimeRefresh to filter by matching projectId");
assert.match(
source,
/conversationUpdatedNotes\?: string\[]/,
"expected RealtimeRefresh to accept optional conversationUpdatedNotes",
);
assert.match(
source,
/payload\.projectId !== projectId[\s\S]*return false/,
"expected RealtimeRefresh to filter by matching projectId",
);
assert.match(
source,
/payload\.note\)\s*&&\s*!conversationUpdatedNotes\.includes\(payload\.note\)/,
"expected RealtimeRefresh to ignore unmatched conversation.updated notes",
);
});
test("project conversation pages wire project-scoped realtime refresh", async () => {
@@ -36,4 +50,15 @@ test("project conversation pages wire project-scoped realtime refresh", async ()
assert.match(source, /projectId=\{projectId\}/, `expected ${label} page to scope refreshes to the current project`);
assert.match(source, /"conversation\.updated"/, `expected ${label} page to listen to conversation updates`);
}
assert.match(
goalsPage,
/conversationUpdatedNotes=\{\["project_goals\.updated"\]\}/,
"expected goals page to refresh only on project goal markers",
);
assert.match(
versionsPage,
/conversationUpdatedNotes=\{\["project_goals\.updated"\]\}/,
"expected versions page to refresh only on project goal markers",
);
});