Skip to content

Commit 6895fac

Browse files
committed
chat(p2): 任务事件模型入DAG,agent触发因果链展示,侧边栏agent可视化
1 parent c641a48 commit 6895fac

6 files changed

Lines changed: 160 additions & 1 deletion

File tree

src/public/locales/zh-CN.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,13 @@
845845
"remoteTypingMany": "${name} 和另外 ${count} 人正在输入…",
846846
"mentionEmpty": "(无可用角色,请先在与该聊天会话中添加角色)",
847847
"lateDelivery": "延迟送达",
848+
"taskCreate": "任务",
849+
"taskUpdate": "执行中",
850+
"taskResultSuccess": "完成",
851+
"taskResultFailed": "失败",
852+
"taskTriggeredBy": "触发来源",
853+
"agentBadge": "Agent",
854+
"activeAgentCount": "活跃 Agent:${count} 个",
848855
"aiStreaming": "AI 生成中…",
849856
"avStart": "开始媒体",
850857
"avMute": "静音",

src/public/parts/shells/chat/public/src/ui/dagMessageUtils.mjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,44 @@ async function formatGroupMessageLine(line, allMessages) {
386386
})
387387
return el
388388
}
389+
if (line.type === 'task_create') {
390+
const el = document.createElement('span')
391+
setLocalizeLogic(el, () => {
392+
const label = geti18n('chat.group.taskCreate')
393+
const desc = String(line.content?.description || '')
394+
const charId = String(line.content?.charId || line.charId || '')
395+
el.textContent = `[${label}] ${desc}${charId ? ` (${charId})` : ''}`
396+
const parentEventId = line.content?.parentEventId
397+
if (parentEventId) {
398+
const ref = document.createElement('div')
399+
ref.className = 'text-[10px] opacity-60 mt-0.5'
400+
ref.textContent = `${geti18n('chat.group.taskTriggeredBy')}: ${parentEventId}`
401+
el.appendChild(ref)
402+
}
403+
})
404+
return el
405+
}
406+
if (line.type === 'task_update') {
407+
const el = document.createElement('span')
408+
setLocalizeLogic(el, () => {
409+
const label = geti18n('chat.group.taskUpdate')
410+
const step = String(line.content?.stepDescription || '')
411+
el.textContent = `[${label}] ${step}`
412+
})
413+
return el
414+
}
415+
if (line.type === 'task_result') {
416+
const el = document.createElement('span')
417+
setLocalizeLogic(el, () => {
418+
const outcome = line.content?.outcome
419+
const label = outcome === 'success'
420+
? geti18n('chat.group.taskResultSuccess')
421+
: geti18n('chat.group.taskResultFailed')
422+
const summary = String(line.content?.summary || '')
423+
el.textContent = `[${label}] ${summary}`
424+
})
425+
return el
426+
}
389427
if (line.type === 'message_delete') {
390428
const el = document.createElement('span')
391429
setLocalizeLogic(el, () => {

src/public/parts/shells/chat/public/src/ui/messageItem.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,12 @@ export function createMessageItemRenderer(ctx) {
412412
editedBadge.dataset.i18n = 'chat.group.editedLabel'
413413
header.appendChild(editedBadge)
414414
}
415+
if (m.charId) {
416+
const agentBadge = document.createElement('span')
417+
agentBadge.className = 'ml-2 text-[9px] px-1 py-0.5 rounded bg-primary/20 text-primary font-mono opacity-80'
418+
agentBadge.textContent = `⚡ ${m.charId}`
419+
header.appendChild(agentBadge)
420+
}
415421
const bubble = document.createElement('div')
416422
bubble.className = bubbleClass
417423
const messageRaw = m.type === 'message'
@@ -436,6 +442,19 @@ export function createMessageItemRenderer(ctx) {
436442
})
437443
main.appendChild(header)
438444
main.appendChild(bubble)
445+
const parentEventId = m.content?.parentEventId
446+
if (parentEventId) {
447+
const refBlock = document.createElement('div')
448+
refBlock.className = 'mt-1 text-[10px] opacity-60 cursor-pointer hover:opacity-100 border-l-2 border-primary/40 pl-1.5 truncate'
449+
setLocalizeLogic(refBlock, () => {
450+
refBlock.textContent = `${geti18n('chat.group.taskTriggeredBy')}: ${parentEventId}`
451+
})
452+
refBlock.addEventListener('click', () => {
453+
const target = msgBox.querySelector(`[data-event-anchor="${CSS.escape(String(parentEventId))}"]`)
454+
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'center' })
455+
})
456+
main.appendChild(refBlock)
457+
}
439458
const lateMs = 30_000
440459
const ra = Number(m.receivedAt)
441460
const msgTsNum = Number(m.timestamp)

src/public/parts/shells/chat/public/src/ui/sidebar.mjs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { geti18n } from '../../../../../scripts/i18n.mjs'
1+
import { geti18n, setLocalizeLogic } from '../../../../../scripts/i18n.mjs'
22
import { renderMarkdown } from '../../../../../scripts/markdown.mjs'
33
import {
44
getPartList,
@@ -142,6 +142,26 @@ async function renderPersonaDetails(personaName) {
142142
personaDetailsContainer.appendChild(cachedDom.persona[personaName])
143143
}
144144

145+
/**
146+
* 更新侧边栏 Agent 计数徽章。
147+
*/
148+
function updateAgentCountBadge() {
149+
let badge = document.getElementById('agent-count-badge')
150+
if (!badge) {
151+
const charSection = document.getElementById('char-list')
152+
const titleRow = charSection?.querySelector('.flex.items-center.gap-2')
153+
if (!titleRow) return
154+
badge = document.createElement('span')
155+
badge.id = 'agent-count-badge'
156+
badge.className = 'ml-auto text-[10px] px-1.5 py-0.5 rounded-full bg-primary/20 text-primary font-mono'
157+
titleRow.appendChild(badge)
158+
}
159+
const count = charList.length
160+
setLocalizeLogic(badge, () => {
161+
badge.textContent = geti18n('chat.group.activeAgentCount', { count })
162+
})
163+
}
164+
145165
/**
146166
* 渲染聊天角色列表
147167
* @param {object} data - 包含角色列表和频率数据的对象。
@@ -177,6 +197,8 @@ async function renderCharList(data) {
177197
frequencySlider.value = newFrequency
178198
}
179199

200+
updateAgentCountBadge()
201+
180202
// 更新可用角色列表
181203
const availableChars = allChars.filter(char => !charList.includes(char))
182204
const charSelectOldList = Array.from(charSelect.options).map(option => option.value)
@@ -210,6 +232,13 @@ async function renderCharDetails(charName, frequency_num) {
210232
frequency_num
211233
})
212234
charCard.dataset.charName = charName
235+
const agentIcon = document.createElement('span')
236+
agentIcon.className = 'inline-flex items-center text-[10px] px-1 py-0.5 rounded bg-primary/20 text-primary font-mono ml-1'
237+
agentIcon.title = geti18n('chat.group.agentBadge')
238+
agentIcon.textContent = '⚡ Agent'
239+
const cardTitle = charCard.querySelector('.card-title, [class*="title"], h2, h3')
240+
if (cardTitle) cardTitle.appendChild(agentIcon)
241+
else charCard.prepend(agentIcon)
213242
addCardEventListeners(charCard, charData)
214243
// 添加滑动条的事件监听
215244
const frequencySlider = charCard.querySelector('.frequency-slider')

src/public/parts/shells/chat/src/chat/dag.mjs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const PERSIST_MESSAGE_TYPES = new Set([
137137
'message', 'message_edit', 'message_delete', 'message_feedback',
138138
'vote_cast', 'pin_message', 'unpin_message',
139139
'reaction_add', 'reaction_remove',
140+
'task_create', 'task_update', 'task_result',
140141
])
141142

142143
/**
@@ -805,6 +806,64 @@ export async function appendReactionEvent(username, chatId, opts) {
805806
})
806807
}
807808

809+
// ─── 任务事件 ─────────────────────────────────────────────────────────────────
810+
811+
/**
812+
* 创建任务请求事件(记录 agent 被触发开始一项任务)。
813+
* @param {string} username 用户名
814+
* @param {string} chatId 群组 ID
815+
* @param {{ channelId: string, charId: string, description: string, parentEventId?: string, sender?: string }} body 任务参数(频道、角色、描述、可选父事件与发件人)
816+
* @returns {Promise<object>} `appendEvent` 返回的签名事件对象
817+
*/
818+
export async function appendTaskCreate(username, chatId, body) {
819+
const { channelId, charId, description, parentEventId, sender = 'local' } = body
820+
const taskId = randomUUID()
821+
return appendEvent(username, chatId, {
822+
type: 'task_create',
823+
channelId,
824+
sender,
825+
charId,
826+
timestamp: Date.now(),
827+
content: { taskId, charId, description, parentEventId },
828+
})
829+
}
830+
831+
/**
832+
* 更新任务进度。
833+
* @param {string} username 用户名
834+
* @param {string} chatId 群组 ID
835+
* @param {{ channelId: string, taskId: string, status: 'running'|'paused', stepDescription?: string, sender?: string }} body 进度参数(频道、任务 ID、状态、可选步骤描述与发件人)
836+
* @returns {Promise<object>} `appendEvent` 返回的签名事件对象
837+
*/
838+
export async function appendTaskUpdate(username, chatId, body) {
839+
const { channelId, taskId, status, stepDescription, sender = 'local' } = body
840+
return appendEvent(username, chatId, {
841+
type: 'task_update',
842+
channelId,
843+
sender,
844+
timestamp: Date.now(),
845+
content: { taskId, status, stepDescription },
846+
})
847+
}
848+
849+
/**
850+
* 记录任务完成结果。
851+
* @param {string} username 用户名
852+
* @param {string} chatId 群组 ID
853+
* @param {{ channelId: string, taskId: string, outcome: 'success'|'failure', summary: string, sender?: string }} body 结果参数(频道、任务 ID、成败标记、摘要与可选发件人)
854+
* @returns {Promise<object>} `appendEvent` 返回的签名事件对象
855+
*/
856+
export async function appendTaskResult(username, chatId, body) {
857+
const { channelId, taskId, outcome, summary, sender = 'local' } = body
858+
return appendEvent(username, chatId, {
859+
type: 'task_result',
860+
channelId,
861+
sender,
862+
timestamp: Date.now(),
863+
content: { taskId, outcome, summary },
864+
})
865+
}
866+
808867
// ─── AI 定频自动触发 ──────────────────────────────────────────────────────────
809868

810869
/** chatId → { lastTriggeredAt: number, msgCount: number } */

src/scripts/p2p/constants.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export const AUTHZ_EVENT_TYPES = new Set([
4848
'member_profile_update',
4949
])
5050

51+
/** 任务类 DAG 事件 type */
52+
export const TASK_EVENT_TYPES = new Set([
53+
'task_create',
54+
'task_update',
55+
'task_result',
56+
])
57+
5158
/**
5259
* 默认晚消息冻结时间(毫秒)
5360
*/

0 commit comments

Comments
 (0)