feat(feedback): add the Web surface for message feedback

Consume the durable message-feedback sidecar from #2217 in the browser:
per-message Like/Dislike with an optional note, contributed through a
declared assistant-actions slot.

- carry MessageId on finalized AssistantMessageNode so a target is nameable
- declare conversation.chat.assistant-actions and render it in the
  IconActions row between copy and branch
- hold one FeedbackController per Session with per-item ifVersion CAS,
  reconciling a version-conflict from the reply's authoritative item
- mount messageFeedbackRemote alongside goalsRemote
This commit is contained in:
Chinesezjc
2026-08-11 17:14:21 +08:00
parent 5d591e55c1
commit 526febc44d
72 changed files with 2095 additions and 22 deletions
+2
View File
@@ -56,6 +56,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-message-feedback": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-typert-registry": "workspace:^",
@@ -65,6 +66,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-message-feedback": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-typert-registry": "workspace:^",
+9 -1
View File
@@ -2,10 +2,12 @@
import type { Context } from '@deepseek-ai/cordis'
import goalsRemote from '@deepseek-ai/dsh-goal/remote'
import messageFeedbackRemote from '@deepseek-ai/dsh-message-feedback/remote'
import type { TypeRTClientRemote } from '@deepseek-ai/dsh-type-meta'
export type { TypeRTClientRemote as ClientRemote } from '@deepseek-ai/dsh-type-meta'
export type {} from '@deepseek-ai/dsh-goal/remote'
export type {} from '@deepseek-ai/dsh-message-feedback/remote'
declare module '@deepseek-ai/cordis' {
interface Context {
@@ -23,5 +25,11 @@ export const inject = ['remote']
* @returns disposer after every selected Remote namespace is ready.
*/
export async function apply(ctx: Context): Promise<() => Promise<void>> {
return await ctx.remote.$mount(goalsRemote)
const mounted = [
await ctx.remote.$mount(goalsRemote),
await ctx.remote.$mount(messageFeedbackRemote),
]
return async () => {
for (const dispose of mounted.reverse()) await dispose()
}
}
@@ -15,6 +15,9 @@
{
"path": "../../goal/goal"
},
{
"path": "../../feedback/message-feedback"
},
{
"path": "../../typert/type-meta"
}