-
Notifications
You must be signed in to change notification settings - Fork 227
feat: milky 合并转发外显 #683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: milky 合并转发外显 #683
Conversation
审阅者指南为转发消息增加可自定义元数据(title、preview、summary、prompt)的支持,并通过 API 层一路传递到 forward message encoder,使生成的转发消息载荷和 UI 字段能够反映这些自定义值,而不是使用静态默认值。 通过 API 传递的可自定义转发消息元数据时序图sequenceDiagram
actor User
participant Client as MilkyClient
participant Api as SendPrivateMessageAPI
participant Transform as transformOutgoingForwardMessages
participant Encoder as ForwardMessageEncoder
participant PMHQ as pmhqUpload
participant App as AppSendMessage
User->>Client: Choose forward messages
User->>Client: Set title, preview, summary, prompt
Client->>Api: SendPrivateMessage(forwardData)
Api->>Transform: transformOutgoingForwardMessages(ctx, forwardData.messages, peer, options)
Transform->>Encoder: new ForwardMessageEncoder(ctx, peer)
Transform->>Encoder: generate(messages, options)
Encoder-->>Transform: raw{multiMsgItems, tsum, source, summary, news, prompt}
Transform-->>Api: raw
Api->>PMHQ: uploadForward(peer, raw.multiMsgItems)
PMHQ-->>Api: resid
Api->>App: sendMessage(ctx, peer, payload with desc=raw.prompt, prompt=raw.prompt)
App-->>Client: Message sent response
Client-->>User: UI shows customized forward card metadata
更新后的转发消息编码元数据流程类图classDiagram
class Context
class Peer
class OutgoingForwardedMessage
class ForwardMessageEncoder {
- Context ctx
- Peer peer
- number tsum
- boolean isGroup
- any news
+ ForwardMessageEncoder(ctx, peer)
+ render(content)
+ generate(content, options)
}
class TransformOutgoingForwardMessages {
+ transformOutgoingForwardMessages(ctx, messages, peer, options)
}
class ForwardMetadataOptions {
<<interface>>
+ string title
+ PreviewItem[] preview
+ string summary
+ string prompt
}
class PreviewItem {
+ string text
}
class ForwardGenerateResult {
+ any[] multiMsgItems
+ number tsum
+ string source
+ string summary
+ any[] news
+ string prompt
}
Context <.. TransformOutgoingForwardMessages : uses
Peer <.. TransformOutgoingForwardMessages : uses
OutgoingForwardedMessage <.. TransformOutgoingForwardMessages : uses
ForwardMetadataOptions <.. TransformOutgoingForwardMessages : uses
TransformOutgoingForwardMessages ..> ForwardMessageEncoder : creates
ForwardMessageEncoder ..> ForwardGenerateResult : returns
ForwardMessageEncoder ..> ForwardMetadataOptions : reads
ForwardMetadataOptions <.. ForwardMessageEncoder : options
PreviewItem <.. ForwardMetadataOptions : contains
文件级变更
提示与命令与 Sourcery 交互
自定义你的使用体验访问你的 dashboard 以:
获取帮助Original review guide in EnglishReviewer's GuideAdds support for customizable metadata (title, preview, summary, prompt) on forwarded messages and wires it through the API layer into the forward message encoder so that these values are reflected in the generated forward message payloads and UI fields instead of static defaults. Sequence diagram for customizable forward message metadata through APIsequenceDiagram
actor User
participant Client as MilkyClient
participant Api as SendPrivateMessageAPI
participant Transform as transformOutgoingForwardMessages
participant Encoder as ForwardMessageEncoder
participant PMHQ as pmhqUpload
participant App as AppSendMessage
User->>Client: Choose forward messages
User->>Client: Set title, preview, summary, prompt
Client->>Api: SendPrivateMessage(forwardData)
Api->>Transform: transformOutgoingForwardMessages(ctx, forwardData.messages, peer, options)
Transform->>Encoder: new ForwardMessageEncoder(ctx, peer)
Transform->>Encoder: generate(messages, options)
Encoder-->>Transform: raw{multiMsgItems, tsum, source, summary, news, prompt}
Transform-->>Api: raw
Api->>PMHQ: uploadForward(peer, raw.multiMsgItems)
PMHQ-->>Api: resid
Api->>App: sendMessage(ctx, peer, payload with desc=raw.prompt, prompt=raw.prompt)
App-->>Client: Message sent response
Client-->>User: UI shows customized forward card metadata
Class diagram for updated forward message encoding metadata flowclassDiagram
class Context
class Peer
class OutgoingForwardedMessage
class ForwardMessageEncoder {
- Context ctx
- Peer peer
- number tsum
- boolean isGroup
- any news
+ ForwardMessageEncoder(ctx, peer)
+ render(content)
+ generate(content, options)
}
class TransformOutgoingForwardMessages {
+ transformOutgoingForwardMessages(ctx, messages, peer, options)
}
class ForwardMetadataOptions {
<<interface>>
+ string title
+ PreviewItem[] preview
+ string summary
+ string prompt
}
class PreviewItem {
+ string text
}
class ForwardGenerateResult {
+ any[] multiMsgItems
+ number tsum
+ string source
+ string summary
+ any[] news
+ string prompt
}
Context <.. TransformOutgoingForwardMessages : uses
Peer <.. TransformOutgoingForwardMessages : uses
OutgoingForwardedMessage <.. TransformOutgoingForwardMessages : uses
ForwardMetadataOptions <.. TransformOutgoingForwardMessages : uses
TransformOutgoingForwardMessages ..> ForwardMessageEncoder : creates
ForwardMessageEncoder ..> ForwardGenerateResult : returns
ForwardMessageEncoder ..> ForwardMetadataOptions : reads
ForwardMetadataOptions <.. ForwardMessageEncoder : options
PreviewItem <.. ForwardMetadataOptions : contains
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你好——我发现了两个问题,并留下了一些总体反馈:
- 内联的
forwardData类型在SendPrivateMessage和SendGroupMessage中都被重复定义;建议提取一个共享的类型/接口(例如ForwardMessagePayload),以保持结构一致并便于更新。 transformOutgoingForwardMessages的options对象结构在函数签名和ForwardMessageEncoder.generate中都重复了一遍;建议定义一个单独导出的 options 类型,以减少重复和潜在的偏差。- 现在
transformOutgoingForwardMessages会返回prompt,你可以考虑返回一个强类型对象(例如包含prompt的接口),并在消费raw的地方复用该类型,从而避免对未类型化属性的访问。
给 AI 代理的提示
请根据这次代码审查中的评论进行修改:
## 总体评论
- 内联的 `forwardData` 类型在 `SendPrivateMessage` 和 `SendGroupMessage` 中都被重复定义;建议提取一个共享的类型/接口(例如 `ForwardMessagePayload`),以保持结构一致并便于更新。
- `transformOutgoingForwardMessages` 的 `options` 对象结构在函数签名和 `ForwardMessageEncoder.generate` 中都重复了一遍;建议定义一个单独导出的 options 类型,以减少重复和潜在的偏差。
- 现在 `transformOutgoingForwardMessages` 会返回 `prompt`,你可以考虑返回一个强类型对象(例如包含 `prompt` 的接口),并在消费 `raw` 的地方复用该类型,从而避免对未类型化属性的访问。
## 单独评论
### 评论 1
<location> `src/milky/api/message.ts:47-53` </location>
<code_context>
let result: RawMessage
if (payload.message[0].type === 'forward') {
+ const forwardData = payload.message[0].data as {
+ messages: OutgoingForwardedMessage[]
+ title?: string
+ preview?: { text: string }[]
+ summary?: string
+ prompt?: string
+ }
const raw = await transformOutgoingForwardMessages(
ctx,
</code_context>
<issue_to_address>
**suggestion:** 转发消息 payload 的结构在私聊和群聊处理器中都被重复并手动类型断言;建议提取一个共享的类型/辅助方法。
`payload.message[0].data` 的这个结构性类型断言现在在 `SendPrivateMessage` 和 `SendGroupMessage` 中都出现了一次,因此只要转发 payload 的结构发生变更(例如新增字段),就必须在多个地方同步修改。定义一个共享的 `ForwardPayload` 类型和辅助方法(例如 `getForwardPayload(payload.message[0])`)可以把这部分结构集中管理,降低两个处理器实现产生不一致的风险。
推荐实现方式:
```typescript
type ForwardPayload = {
messages: OutgoingForwardedMessage[]
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
return message.data as ForwardPayload
}
let result: RawMessage
```
```typescript
if (payload.message[0].type === 'forward') {
const forwardData = getForwardPayload(payload.message[0])
const raw = await transformOutgoingForwardMessages(
ctx,
forwardData.messages,
peer,
{
title: forwardData.title,
preview: forwardData.preview,
summary: forwardData.summary,
prompt: forwardData.prompt
}
```
你提到这个结构性类型断言在 `SendPrivateMessage` 和 `SendGroupMessage` 中都被重复使用。请在群聊处理器中做同样的替换:
1. 找到群聊消息处理器中与之对应的代码块,即检查 `payload.message[0].type === 'forward'` 并对 `payload.message[0].data` 进行相同结构的内联类型断言的地方。
2. 用 `const forwardData = getForwardPayload(payload.message[0])` 来替换这段内联断言,其余使用方式保持不变(例如 `forwardData.messages`、`forwardData.title` 等)。
由于 `ForwardPayload` 和 `getForwardPayload` 只在第一次使用附近定义一次,它们会被这两个处理器共享,而无需在其他地方再做额外修改。
</issue_to_address>
### 评论 2
<location> `src/milky/transform/message/outgoing.ts:90-94` </location>
<code_context>
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
- peer: Peer
+ peer: Peer,
+ options?: {
+ title?: string
+ preview?: { text: string }[]
</code_context>
<issue_to_address>
**suggestion:** `options` 的结构在 `transformOutgoingForwardMessages` 和 `ForwardMessageEncoder.generate` 中都被重复定义;建议提取一个共享的类型别名。
`{ title?, preview?, summary?, prompt? }` 这个 options 对象分别在函数和类方法中单独定义,一旦属性发生变化,它们可能会在没有任何提示的情况下产生偏差。请提取一个共享类型(例如 `ForwardRenderOptions`),并在两个签名中使用它,以保持一致并提高可读性。
推荐实现方式:
```typescript
type ForwardRenderOptions = {
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
peer: Peer,
options?: ForwardRenderOptions
) {
const encoder = new ForwardMessageEncoder(ctx, peer)
return await encoder.generate(messages, options)
}
class ForwardMessageEncoder {
```
请在本文件中相应地更新 `ForwardMessageEncoder.generate` 方法签名以使用共享类型:
- 将它的参数从内联的 options 结构替换为 `options?: ForwardRenderOptions`(或者如果目前还没有该参数,则添加它)。
- 删除任何与 `{ title?, preview?, summary?, prompt? }` 相关的重复 options 类型定义,统一改为引用 `ForwardRenderOptions`,以确保该函数和类方法始终保持同步。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续审查。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The inline
forwardDatatype is duplicated in bothSendPrivateMessageandSendGroupMessage; consider extracting a shared type/interface (e.g.ForwardMessagePayload) to keep the shape consistent and easier to update. - The
optionsobject shape fortransformOutgoingForwardMessagesis repeated in both the function signature andForwardMessageEncoder.generate; defining a single exported options type would reduce duplication and potential drift. - Now that
transformOutgoingForwardMessagesreturnsprompt, you might consider returning a strongly typed object (e.g. an interface includingprompt) and reusing that type whererawis consumed to avoid untyped property access.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The inline `forwardData` type is duplicated in both `SendPrivateMessage` and `SendGroupMessage`; consider extracting a shared type/interface (e.g. `ForwardMessagePayload`) to keep the shape consistent and easier to update.
- The `options` object shape for `transformOutgoingForwardMessages` is repeated in both the function signature and `ForwardMessageEncoder.generate`; defining a single exported options type would reduce duplication and potential drift.
- Now that `transformOutgoingForwardMessages` returns `prompt`, you might consider returning a strongly typed object (e.g. an interface including `prompt`) and reusing that type where `raw` is consumed to avoid untyped property access.
## Individual Comments
### Comment 1
<location> `src/milky/api/message.ts:47-53` </location>
<code_context>
let result: RawMessage
if (payload.message[0].type === 'forward') {
+ const forwardData = payload.message[0].data as {
+ messages: OutgoingForwardedMessage[]
+ title?: string
+ preview?: { text: string }[]
+ summary?: string
+ prompt?: string
+ }
const raw = await transformOutgoingForwardMessages(
ctx,
</code_context>
<issue_to_address>
**suggestion:** The forward message payload shape is duplicated and manually cast in both private and group handlers; consider extracting a shared type/helper.
This structural cast of `payload.message[0].data` is now duplicated in both `SendPrivateMessage` and `SendGroupMessage`, so any change to the forward payload (e.g., new fields) must be updated in multiple places. A shared `ForwardPayload` type and helper (e.g., `getForwardPayload(payload.message[0])`) would centralize this shape and reduce the risk of the handlers drifting out of sync.
Suggested implementation:
```typescript
type ForwardPayload = {
messages: OutgoingForwardedMessage[]
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
return message.data as ForwardPayload
}
let result: RawMessage
```
```typescript
if (payload.message[0].type === 'forward') {
const forwardData = getForwardPayload(payload.message[0])
const raw = await transformOutgoingForwardMessages(
ctx,
forwardData.messages,
peer,
{
title: forwardData.title,
preview: forwardData.preview,
summary: forwardData.summary,
prompt: forwardData.prompt
}
```
You mentioned this structural cast is duplicated in both `SendPrivateMessage` and `SendGroupMessage`. Apply the same replacement in the group handler:
1. Locate the analogous block in the group message handler where `payload.message[0].type === 'forward'` is checked and `payload.message[0].data` is cast inline to the same shape.
2. Replace that inline cast with `const forwardData = getForwardPayload(payload.message[0])` and keep the rest of the usage (`forwardData.messages`, `forwardData.title`, etc.) the same.
Because `ForwardPayload` and `getForwardPayload` are defined once near the first usage, they will be shared by both handlers without further changes.
</issue_to_address>
### Comment 2
<location> `src/milky/transform/message/outgoing.ts:90-94` </location>
<code_context>
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
- peer: Peer
+ peer: Peer,
+ options?: {
+ title?: string
+ preview?: { text: string }[]
</code_context>
<issue_to_address>
**suggestion:** The `options` shape is duplicated in both `transformOutgoingForwardMessages` and `ForwardMessageEncoder.generate`; consider a shared type alias.
The `{ title?, preview?, summary?, prompt? }` options object is defined separately in the function and the class method, so they can silently diverge if properties change. Please extract a shared type (e.g., `ForwardRenderOptions`) and use it in both signatures to keep them aligned and easier to read.
Suggested implementation:
```typescript
type ForwardRenderOptions = {
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
peer: Peer,
options?: ForwardRenderOptions
) {
const encoder = new ForwardMessageEncoder(ctx, peer)
return await encoder.generate(messages, options)
}
class ForwardMessageEncoder {
```
Update the `ForwardMessageEncoder.generate` method signature elsewhere in this file to also use the shared type:
- Change its parameter from an inline options shape to `options?: ForwardRenderOptions` (or add the parameter if it doesn’t exist yet).
- Remove any duplicated options type definitions related to `{ title?, preview?, summary?, prompt? }` and reference `ForwardRenderOptions` instead, so both the function and the class method stay in sync.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const forwardData = payload.message[0].data as { | ||
| messages: OutgoingForwardedMessage[] | ||
| title?: string | ||
| preview?: { text: string }[] | ||
| summary?: string | ||
| prompt?: string | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: 转发消息 payload 的结构在私聊和群聊处理器中都被重复并手动类型断言;建议提取一个共享的类型/辅助方法。
payload.message[0].data 的这个结构性类型断言现在在 SendPrivateMessage 和 SendGroupMessage 中都出现了一次,因此只要转发 payload 的结构发生变更(例如新增字段),就必须在多个地方同步修改。定义一个共享的 ForwardPayload 类型和辅助方法(例如 getForwardPayload(payload.message[0]))可以把这部分结构集中管理,降低两个处理器实现产生不一致的风险。
推荐实现方式:
type ForwardPayload = {
messages: OutgoingForwardedMessage[]
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
return message.data as ForwardPayload
}
let result: RawMessage if (payload.message[0].type === 'forward') {
const forwardData = getForwardPayload(payload.message[0])
const raw = await transformOutgoingForwardMessages(
ctx,
forwardData.messages,
peer,
{
title: forwardData.title,
preview: forwardData.preview,
summary: forwardData.summary,
prompt: forwardData.prompt
}你提到这个结构性类型断言在 SendPrivateMessage 和 SendGroupMessage 中都被重复使用。请在群聊处理器中做同样的替换:
- 找到群聊消息处理器中与之对应的代码块,即检查
payload.message[0].type === 'forward'并对payload.message[0].data进行相同结构的内联类型断言的地方。 - 用
const forwardData = getForwardPayload(payload.message[0])来替换这段内联断言,其余使用方式保持不变(例如forwardData.messages、forwardData.title等)。
由于 ForwardPayload 和 getForwardPayload 只在第一次使用附近定义一次,它们会被这两个处理器共享,而无需在其他地方再做额外修改。
Original comment in English
suggestion: The forward message payload shape is duplicated and manually cast in both private and group handlers; consider extracting a shared type/helper.
This structural cast of payload.message[0].data is now duplicated in both SendPrivateMessage and SendGroupMessage, so any change to the forward payload (e.g., new fields) must be updated in multiple places. A shared ForwardPayload type and helper (e.g., getForwardPayload(payload.message[0])) would centralize this shape and reduce the risk of the handlers drifting out of sync.
Suggested implementation:
type ForwardPayload = {
messages: OutgoingForwardedMessage[]
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
return message.data as ForwardPayload
}
let result: RawMessage if (payload.message[0].type === 'forward') {
const forwardData = getForwardPayload(payload.message[0])
const raw = await transformOutgoingForwardMessages(
ctx,
forwardData.messages,
peer,
{
title: forwardData.title,
preview: forwardData.preview,
summary: forwardData.summary,
prompt: forwardData.prompt
}You mentioned this structural cast is duplicated in both SendPrivateMessage and SendGroupMessage. Apply the same replacement in the group handler:
- Locate the analogous block in the group message handler where
payload.message[0].type === 'forward'is checked andpayload.message[0].datais cast inline to the same shape. - Replace that inline cast with
const forwardData = getForwardPayload(payload.message[0])and keep the rest of the usage (forwardData.messages,forwardData.title, etc.) the same.
Because ForwardPayload and getForwardPayload are defined once near the first usage, they will be shared by both handlers without further changes.
| export async function transformOutgoingForwardMessages( | ||
| ctx: Context, | ||
| messages: OutgoingForwardedMessage[], | ||
| peer: Peer | ||
| peer: Peer, | ||
| options?: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: options 的结构在 transformOutgoingForwardMessages 和 ForwardMessageEncoder.generate 中都被重复定义;建议提取一个共享的类型别名。
{ title?, preview?, summary?, prompt? } 这个 options 对象分别在函数和类方法中单独定义,一旦属性发生变化,它们可能会在没有任何提示的情况下产生偏差。请提取一个共享类型(例如 ForwardRenderOptions),并在两个签名中使用它,以保持一致并提高可读性。
推荐实现方式:
type ForwardRenderOptions = {
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
peer: Peer,
options?: ForwardRenderOptions
) {
const encoder = new ForwardMessageEncoder(ctx, peer)
return await encoder.generate(messages, options)
}
class ForwardMessageEncoder {请在本文件中相应地更新 ForwardMessageEncoder.generate 方法签名以使用共享类型:
- 将它的参数从内联的 options 结构替换为
options?: ForwardRenderOptions(或者如果目前还没有该参数,则添加它)。 - 删除任何与
{ title?, preview?, summary?, prompt? }相关的重复 options 类型定义,统一改为引用ForwardRenderOptions,以确保该函数和类方法始终保持同步。
Original comment in English
suggestion: The options shape is duplicated in both transformOutgoingForwardMessages and ForwardMessageEncoder.generate; consider a shared type alias.
The { title?, preview?, summary?, prompt? } options object is defined separately in the function and the class method, so they can silently diverge if properties change. Please extract a shared type (e.g., ForwardRenderOptions) and use it in both signatures to keep them aligned and easier to read.
Suggested implementation:
type ForwardRenderOptions = {
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
peer: Peer,
options?: ForwardRenderOptions
) {
const encoder = new ForwardMessageEncoder(ctx, peer)
return await encoder.generate(messages, options)
}
class ForwardMessageEncoder {Update the ForwardMessageEncoder.generate method signature elsewhere in this file to also use the shared type:
- Change its parameter from an inline options shape to
options?: ForwardRenderOptions(or add the parameter if it doesn’t exist yet). - Remove any duplicated options type definitions related to
{ title?, preview?, summary?, prompt? }and referenceForwardRenderOptionsinstead, so both the function and the class method stay in sync.
拆分的 #678
我们等 SaltifyDev/milky#44 决定
Summary by Sourcery
为转发消息提供可自定义的元数据支持,并在 Milky API 中对外暴露。
New Features:
Enhancements:
Original summary in English
Summary by Sourcery
Support customizable metadata for forwarded messages and surface it in Milky APIs.
New Features:
Enhancements: