-
Notifications
You must be signed in to change notification settings - Fork 407
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
feat: support add image to chat #4476
base: main
Are you sure you want to change the base?
Conversation
Walkthrough此次变更主要在多个模块中扩展了消息请求、处理和渲染的能力,以支持图像数据的传输与展示。方法签名、接口和数据结构中均增加了可选的 Changes
Sequence Diagram(s)sequenceDiagram
participant U as 用户
participant CI as ChatInput/ChatMentionInput
participant CV as ChatView
participant CIS as ChatInternalService
participant CMS as ChatManagerService
participant CA as 聊天代理服务
U->>CI: 输入消息及选择图片
CI->>CI: 触发图片上传(调用 onImageUpload)
CI->>CV: 调用 handleSend(message, images, …)
CV->>CIS: 调用 createRequest(…, images, …)
CIS->>CMS: 传递完整请求数据(含 images)
CMS->>CA: 调用 addRequest(含 prompt, agentId, command, images)
CA-->>CMS: 返回响应
CMS-->>CIS: 返回处理结果
CIS-->>CV: 返回消息结果(含图片预览数据)
CV-->>U: 显示文字与图片
Suggested reviewers
Warning There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure. 🔧 ESLint
yarn install v1.22.22 Tip ⚡🧪 Multi-step agentic review comment chat (experimental)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 8
🧹 Nitpick comments (33)
packages/ai-native/src/browser/context/llm-context.service.ts (8)
32-33
: 新增 IFileServiceClient 依赖项。
使用前建议评估现有错误处理逻辑,确保异步 fileService 调用过程中不会遗漏异常捕获。
40-40
: 将 maxAttachFoldersLimit 提取为可配置常量。
如果未来需要灵活调整或根据不同场景设定更大/更小的上限,放置在可配置信息(例如配置文件或环境变量)中更为恰当。
42-44
: 可进一步统一命名,避免后期维护歧义。
目前 attachedFiles、attachedFolders、attachedImages 三者概念相似但略有差异,建议保持命名一致或在注释中明确说明用途。另观察到 attachedImages 尚未实际使用,可确认后续实现计划。
65-75
: 与 addFileToList 存在明显重复逻辑,建议抽取公共方法。
当前代码几乎重复了查重、删除、追加逻辑,可将两者通用部分汇总为一个私有抽象函数,提升可维护性。
94-103
: 变量命名与方法目标不符。
函数内部将 folder 存储在名为 file 的常量中,易引发混淆。建议改用更贴切的命名,例如 const folder = { uri }。
197-206
: 异步序列化方法合理,但需注意大规模文件夹影响整体性能。
深层或大型文件夹在遍历时可能占用较长时间,建议在调用或内部对文件夹大小、层级进行限制或分批处理。
208-220
: 对文件夹路径去重并逐一转换为结构概览,性能消耗需谨慎。
若存在大量文件夹或深层次目录,循环多次 fileStat 调用可能冗长,可考虑预先做数量过滤或更多限制。
222-252
: 函数名应为 getPartialFolderStructure 并建议补充错误捕获。
“getPartiaFolderStructure” 存在拼写问题;同时对 fileService 异步调用建议使用 try/catch,防止意外抛出阻断后续流程。- private async getPartiaFolderStructure(folder: string, level = 2): Promise<string[]> { + private async getPartialFolderStructure(folder: string, level = 2): Promise<string[]> {packages/ai-native/src/browser/components/mention-input/mention-item.tsx (1)
1-24
: MentionItem 组件实现良好这个新组件实现了提及项的渲染,包括图标、文本和描述等信息,使用
classnames
实现条件样式应用,代码结构清晰,并且适当地处理了嵌套项的情况。作为提及输入功能的一部分,该组件有助于实现文件和图片的引用功能。不过,建议考虑以下几点优化:
- 可以使用 React.memo 包装组件以避免不必要的重渲染
- 为点击事件处理添加键盘可访问性支持(如 onKeyDown 处理 Enter 键)
-export const MentionItem: React.FC<MentionItemProps> = ({ item, isActive, onClick }) => ( +export const MentionItem: React.FC<MentionItemProps> = React.memo(({ item, isActive, onClick }) => ( - <div className={`${styles.mention_item} ${isActive ? styles.active : ''}`} onClick={() => onClick(item)}> + <div + className={`${styles.mention_item} ${isActive ? styles.active : ''}`} + onClick={() => onClick(item)} + onKeyDown={(e) => e.key === 'Enter' && onClick(item)} + tabIndex={0} + role="option" + aria-selected={isActive} + > <div className={styles.mention_item_left}> <Icon className={cls(styles.mention_item_icon, item.icon)} /> <span className={styles.mention_item_text}>{item.text}</span> <span className={styles.mention_item_description}>{item.description}</span> </div> {item.getItems && <Icon className={cls(styles.mention_item_right, getIcon('arrowright'))} />} </div> -); +));packages/ai-native/src/browser/contrib/image-upload/image-upload.feature.registry.ts (1)
1-19
: 实现图片上传提供者注册机制创建了
ImageUploadProviderRegistry
类来管理图片上传提供者。此类提供了注册和获取图片上传提供者的方法,使系统能够灵活地集成不同的图片上传实现。代码结构清晰,遵循了依赖注入模式。目前实现只支持单个上传提供者,未来可能需要考虑支持多个提供者的场景。
如果将来需要支持多个提供者,可以考虑以下实现:
- private imageUploadProvider: IImageUploadProvider | undefined; + private imageUploadProviders: Map<string, IImageUploadProvider> = new Map(); - registerImageUploadProvider(provider: IImageUploadProvider): void { - this.imageUploadProvider = provider; + registerImageUploadProvider(id: string, provider: IImageUploadProvider): void { + this.imageUploadProviders.set(id, provider); } - getImageUploadProvider(): IImageUploadProvider | undefined { - return this.imageUploadProvider; + getImageUploadProvider(id?: string): IImageUploadProvider | undefined { + if (id) { + return this.imageUploadProviders.get(id); + } + // 返回默认提供者或第一个注册的提供者 + return this.imageUploadProviders.values().next().value; }packages/ai-native/src/browser/components/mention-input/mention-panel.tsx (1)
1-89
: 实现提及菜单面板组件
MentionPanel
组件用于显示提及建议列表,支持筛选、滚动和选择功能。组件实现了以下功能:
- 根据过滤文本动态筛选列表项
- 支持多级菜单(通过level属性)
- 自动滚动到活动项
- 显示加载状态和无结果提示
代码结构良好,React Hooks 使用恰当。
建议对过滤逻辑和无结果显示逻辑进行优化:
- const getFilteredItems = () => { - let filteredItems = items; - - if (level === 0) { - // 一级菜单根据 @ 后面的文本过滤 - if (filter && filter.length > 1) { - const searchText = filter.substring(1).toLowerCase(); - filteredItems = items.filter((item) => item.text.toLowerCase().includes(searchText)); - } - } else { - // 二级菜单根据 @file: 后面的文本过滤 - if (filter && filter.length > 0) { - filteredItems = items.filter((item) => item.text.toLowerCase().includes(filter.toLowerCase())); - } - } - - return filteredItems; - }; - - const filteredItems = getFilteredItems(); + const filteredItems = React.useMemo(() => { + let result = items; + + if (level === 0 && filter && filter.length > 1) { + // 一级菜单根据 @ 后面的文本过滤 + const searchText = filter.substring(1).toLowerCase(); + result = items.filter((item) => item.text.toLowerCase().includes(searchText)); + } else if (level !== 0 && filter && filter.length > 0) { + // 二级菜单根据 @file: 后面的文本过滤 + result = items.filter((item) => item.text.toLowerCase().includes(filter.toLowerCase())); + } + + return result; + }, [items, filter, level]);同时,改进列表渲染逻辑:
- {items.length > 0 ? ( + {filteredItems.length > 0 ? ( <ul className={styles.mention_list}> - {items.map((item, index) => ( + {filteredItems.map((item, index) => ( <MentionItem key={item.id} item={item} isActive={index === activeIndex} onClick={() => onSelectItem(item, true)} /> ))} </ul> ) : ( <div className={styles.no_results}>{loading ? '正在搜索...' : '无匹配结果'}</div> )}packages/ai-native/src/browser/components/components.module.less (3)
7-9
: 请确认新的定位偏移不会导致界面元素重叠或错位
bottom: -15px
和left: -9px
相比原先数值有所变动,建议在小分辨率或特殊布局下多做验证,确保不会造成不期望的遮挡或重叠。
519-519
: 为.model_selector
设置最小宽度时,需留意宽度自适应策略
min-width: 150px
能保证下拉选择器在小屏幕上保持一定可视范围,但建议在更狭窄的屏幕上验证样式,避免出现溢出等问题。
569-639
: 新增.attachment
、.attachment_text
与.thumbnail_container
样式有助于增强可读性,但需审视删除按钮的定位
删除按钮的position: absolute; top: -5px; right: -5px;
可能在缩放或容器较小的场景下点击区域不灵敏,或出现图标局部不可见的情况,建议在容器环境中验证。packages/ai-native/src/browser/components/mention-input/mention-input.module.less (1)
46-73
: 交互式图标的悬浮效果可能需要进一步对比度优化
.ai_enhance_icon
中设置的background-color: var(--badge-background)
及color: var(--badge-foreground)
在深色主题或浅色主题下,需要保证仍能明显区分图标悬浮态。packages/ai-native/src/browser/components/mention-input/mention-input.tsx (3)
14-44
: 多层级的 mentionState 状态可读性高,但内部字段较多
如果项目不断拓展,建议考虑拆分或重构这部分逻辑,使用自定义 Hook 或 context 便于协同管理和单元测试。
176-277
: handleInput 对输入框内容做了多重判断,能防止无效渲染
其中对空标签<br>
的处理非常实用,但要谨防部分浏览器的兼容差异,如过去版本的 Firefox 等。建议做一次多浏览器测试以确认行为一致。
294-458
: 键盘事件处理兼顾 ESC、上下箭头、多级菜单和历史记录
逻辑丰富,但若后续再扩展更多组合键处理,会进一步增加维护难度。可考虑把导航逻辑提取到单独的函数或自定义 Hook,以提升可读性和可测试性。packages/ai-native/src/browser/chat/chat-agent.service.ts (2)
77-77
: 监听上下文文件变更事件。
当上下文文件版本变更时,标记shouldUpdateContext = true
。若此流程后续出错,建议考虑在事件处理环节添加异常捕获和相应的容错处理逻辑。
165-167
: 异步方法provideContextMessage
实现上下文注入逻辑。
使用llmContextService.serialize()
搭配promptProvider.provideContextPrompt
生成完整消息,功能明确。若远程或序列化过程可能失败,建议在此添加错误捕获,防止阻塞后续流程。packages/ai-native/src/browser/components/ChatMentionInput.tsx (1)
24-49
: 新定义的IChatMentionInputProps
接口。
- 属性命名语义明确,如
onSend
,onValueChange
,onExpand
等。- 支持外部注入
images
、agentId
、command
等,扩展性好。- 建议在注释中说明各属性的可选与默认值,方便后续维护。
packages/ai-native/src/browser/chat/chat.view.tsx (6)
24-24
: 引入CommandService
合理,但请确认实际使用场景。
CommandService
的注入一般在需要触发命令执行的场景下使用,若仅是占位请确认后续是否有用到。
118-119
: 在组件中注入aiNativeConfigService
与llmContextService
确保在组件卸载时,可以正确释放事件或监听,避免内存泄漏。
129-132
: 注入labelService
、workspaceService
、commandService
这些服务调用频率较高,建议统一在一个逻辑层中管理,以保证代码可读性。
202-205
: 根据配置条件选择输入组件的逻辑可读性良好。
若未来配置项增加,适当抽离该条件判断为独立函数可能更清晰。
278-283
: 异步调用handleSend
使用await
符合需求,但要注意并发场景。
若存在多次快速发送的需求,可在函数内部增加节流/队列逻辑,避免潜在并发问题。
463-474
:renderUserMessage
中对用户消息渲染加入images
参数是有用的。
建议后续补充对空数组或超大图列表的处理,防止渲染异常。packages/ai-native/src/common/llm-context.ts (2)
6-15
:startAutoCollection
和stopAutoCollection
的接口命名清晰,可考虑记录当前状态。
建议增加一个内部状态标记,方便排查在自动模式中多次调用开始或停止的情况。
63-63
:attachedFolders: string[]
属性是关键点。
后续若需要路径按层次组织,可以考虑改为更丰富的结构体。packages/ai-native/src/browser/types.ts (3)
144-147
:ChatUserRoleRender
接受可选images
属性,拓展了渲染场景。
需注意在渲染多张图片时的布局和性能,避免阻塞 UI。
294-297
:IImageUploadProvider
接口的定义明确,可扩展性不错。
建议对imageUpload
的上传错误进行更详细的处理或回退逻辑。
298-302
:IImageUploadProviderRegistry
支持注册/获取单个上传提供者。
若后续需要多种上传策略,可考虑支持多提供者并按优先级选择。packages/ai-native/src/browser/components/mention-input/types.ts (1)
26-38
: 提及状态管理设计
MentionState
接口定义了提及组件的状态管理结构,包括活动状态、位置、过滤文本等。注释用中文编写清晰地说明了level
字段的含义,但建议其他字段也添加类似的注释以提高可读性。建议为其他重要字段添加简短注释,特别是对于不太直观的属性,例如
inlineSearchActive
和secondLevelFilter
等。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
packages/components/src/icon/iconfont/iconfont.eot
is excluded by!**/*.eot
packages/components/src/icon/iconfont/iconfont.svg
is excluded by!**/*.svg
packages/components/src/icon/iconfont/iconfont.ttf
is excluded by!**/*.ttf
packages/components/src/icon/iconfont/iconfont.woff
is excluded by!**/*.woff
packages/components/src/icon/iconfont/iconfont.woff2
is excluded by!**/*.woff2
📒 Files selected for processing (44)
package.json
(1 hunks)packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts
(1 hunks)packages/ai-native/src/browser/ai-core.contribution.ts
(4 hunks)packages/ai-native/src/browser/chat/chat-agent.service.ts
(3 hunks)packages/ai-native/src/browser/chat/chat-manager.service.ts
(3 hunks)packages/ai-native/src/browser/chat/chat-proxy.service.ts
(1 hunks)packages/ai-native/src/browser/chat/chat.internal.service.ts
(1 hunks)packages/ai-native/src/browser/chat/chat.view.tsx
(16 hunks)packages/ai-native/src/browser/components/ChatEditor.tsx
(7 hunks)packages/ai-native/src/browser/components/ChatMentionInput.tsx
(1 hunks)packages/ai-native/src/browser/components/ChatThinking.tsx
(3 hunks)packages/ai-native/src/browser/components/change-list.module.less
(1 hunks)packages/ai-native/src/browser/components/chat-context/index.tsx
(1 hunks)packages/ai-native/src/browser/components/components.module.less
(3 hunks)packages/ai-native/src/browser/components/mention-input/mention-input.module.less
(1 hunks)packages/ai-native/src/browser/components/mention-input/mention-input.tsx
(1 hunks)packages/ai-native/src/browser/components/mention-input/mention-item.tsx
(1 hunks)packages/ai-native/src/browser/components/mention-input/mention-panel.tsx
(1 hunks)packages/ai-native/src/browser/components/mention-input/types.ts
(1 hunks)packages/ai-native/src/browser/context/llm-context.service.ts
(6 hunks)packages/ai-native/src/browser/contrib/image-upload/image-upload.feature.registry.ts
(1 hunks)packages/ai-native/src/browser/index.ts
(2 hunks)packages/ai-native/src/browser/model/msg-history-manager.ts
(1 hunks)packages/ai-native/src/browser/types.ts
(4 hunks)packages/ai-native/src/common/index.ts
(3 hunks)packages/ai-native/src/common/llm-context.ts
(3 hunks)packages/ai-native/src/common/prompts/context-prompt-provider.ts
(2 hunks)packages/ai-native/src/node/base-language-model.ts
(4 hunks)packages/components/src/icon/iconfont/iconMap.ts
(4 hunks)packages/components/src/icon/iconfont/iconfont.css
(3 hunks)packages/components/src/icon/iconfont/iconfont.html
(3 hunks)packages/components/src/markdown-react/parse.tsx
(4 hunks)packages/core-browser/src/ai-native/ai-config.service.ts
(0 hunks)packages/core-browser/src/style/icon/ide-iconfont.ts
(1 hunks)packages/core-common/src/types/ai-native/index.ts
(2 hunks)packages/design/src/browser/style/global.less
(1 hunks)packages/file-search/src/common/file-search.ts
(1 hunks)packages/file-search/src/node/file-search.service.ts
(5 hunks)packages/file-tree-next/src/browser/file-tree-contribution.ts
(1 hunks)packages/i18n/src/common/en-US.lang.ts
(2 hunks)packages/i18n/src/common/zh-CN.lang.ts
(2 hunks)packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
(2 hunks)packages/theme/src/common/color-tokens/chatColors.ts
(1 hunks)packages/theme/src/common/color-tokens/index.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- packages/core-browser/src/ai-native/ai-config.service.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/core-common/src/types/ai-native/index.ts
[error] 444-447: The enum declaration should not be const
Const enums are not supported by bundlers and are incompatible with the 'isolatedModules' mode. Their use can lead to import inexistent values.
See TypeScript Docs for more details.
Safe fix: Turn the const enum into a regular enum.
(lint/suspicious/noConstEnum)
packages/ai-native/src/browser/components/ChatEditor.tsx
[error] 169-169: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 170-170: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 189-189: Unnecessary continue statement
Unsafe fix: Delete the unnecessary continue statement
(lint/correctness/noUnnecessaryContinue)
🪛 GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
packages/ai-native/src/browser/chat/chat.view.tsx
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: unittest (ubuntu-latest, 18.x, node)
packages/ai-native/src/browser/chat/chat.view.tsx
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: build (ubuntu-latest, 20.x)
packages/ai-native/src/browser/chat/chat.view.tsx
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: unittest (macos-latest, 18.x, jsdom)
packages/ai-native/src/browser/chat/chat.view.tsx
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: ubuntu-latest, Node.js 20.x
packages/ai-native/src/browser/chat/chat.view.tsx
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Actions: E2E Test
packages/ai-native/src/browser/chat/chat.view.tsx
[error] 866-866: Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Actions: CI
packages/ai-native/src/browser/chat/chat.view.tsx
[error] 866-866: error TS2322: Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: WIP
- GitHub Check: WIP
- GitHub Check: build-windows
🔇 Additional comments (109)
packages/ai-native/src/browser/components/change-list.module.less (1)
106-107
: 增强页面布局的CSS属性调整
在.fileStats
类中新增flex: 1;
和justify-content: flex-end;
属性,可以使该元素在其容器内占据剩余空间并将内容右对齐。这将有助于改善整体布局和视觉对齐效果。请确认这些变更与整体UI设计保持一致。packages/components/src/icon/iconfont/iconMap.ts (4)
45-45
: 添加了新图标 'codebraces'这是支持聊天添加图片功能的一部分,新增了代码括号图标,能够更好地表示代码块或代码片段。
83-83
: 添加了新图标 'file'新增文件图标,可用于在聊天界面中表示文件附件,这与PR的目标"支持添加图片到聊天"相符。
119-119
: 添加了新图标 'mcp'新增了mcp图标,可能用于多内容面板(Multi-Content Panel)或特定的控制功能,它是此次功能实现的一部分。
168-168
: 将'send-hollow'重命名为'send-outlined'图标命名从'send-hollow'更改为'send-outlined',保持了命名的一致性,符合outlined样式的命名惯例。
packages/components/src/icon/iconfont/iconfont.html (4)
7-7
: 更新了图标字体引用链接CSS文件引用路径已更新为新版本(
font_1432262_1ef4fl5cm51.css
),确保新添加的图标能够正确加载。
88-89
: 更新了版本号和引用路径OpenSumi版本已更新至v3.8.1,并且相应地更新了图标字体引用路径。这保证了版本信息的一致性。
95-112
: 添加了三个新图标的HTML展示元素为
codebraces
、file
和mcp
三个新图标添加了对应的HTML展示元素,使它们能在图标预览页面中正确显示,便于开发者查看和使用。
143-147
: 将'send-hollow'图标重命名为'send-outlined'在HTML预览中也对应修改了图标名称,保持了与iconMap.ts中的命名一致性。
packages/components/src/icon/iconfont/iconfont.css (3)
3-8
: 更新了字体文件的引用链接所有字体文件的引用时间戳从
t=1709176848484
更新为t=1741593634138
,确保加载最新的图标资源。
19-29
: 添加了三个新图标的CSS定义为
codebraces
、file
和mcp
三个新图标添加了相应的CSS类和Unicode内容值,使它们能够在应用中正确显示。
51-51
: 将'.kticon-send-hollow'重命名为'.kticon-send-outlined'CSS类名从
.kticon-send-hollow
更改为.kticon-send-outlined
,保持与其他文件中命名的一致性。packages/ai-native/src/browser/context/llm-context.service.ts (4)
1-1
: 导入 DataContent 用于图片处理,符合本次新增需求。
15-15
: 确保 fileService 的使用具备必要的错误处理。
在后续异步调用时,若文件系统发生异常,需要进一步确认是否应捕获并处理异常,以防止意外崩溃或数据不一致。
109-112
: 清空文件上下文时同步清理文件夹上下文,保证一致性。
此举确保上下文中所有已附加内容都被完整重置,没有遗留信息。
119-120
: 返回文件夹信息以保持与已附加文件的一致性。
将 attachedFolders 一并纳入返回值,与整体上下文数据形成完整打包。package.json (1)
22-22
: 新增的构建脚本有助于组件库开发添加了新的构建脚本
build:components:lib
,与现有的build:components
脚本形成互补。这个脚本专门用于构建组件库,而不是分发版本,有助于支持聊天图片功能所需的组件开发。packages/components/src/markdown-react/parse.tsx (4)
62-64
: 代码格式调整将
React.cloneElement
调用从多行格式调整为单行格式,提高了代码的简洁性。这种格式变更不影响功能,仅为样式优化。
86-88
: 代码格式调整表格头部行的
React.cloneElement
调用从多行格式变更为单行格式,与其他相似调用保持一致的代码风格。
102-104
: 代码格式调整表格主体行的
React.cloneElement
调用同样调整为单行格式,保持了整个文件中此类调用的一致性。
188-188
: 代码格式调整将
replace
方法链调整为单行格式,与前面的格式变更保持一致,提高代码的可读性和一致性。packages/design/src/browser/style/global.less (1)
221-221
: 增加模态框内边距改善用户体验为模态框主体添加了 24px 的内边距,这将提供更好的内容间距和视觉层次,特别是当模态框中包含图片内容时,能够确保内容不会过于紧凑,提升用户体验。
packages/file-search/src/common/file-search.ts (1)
28-28
: 新增文件夹筛选选项支持图片上传场景在
BaseOptions
接口中添加了onlyFolders
可选属性,允许文件搜索服务专门针对文件夹进行搜索。这个功能对于支持图片上传到聊天中非常有用,用户可以更容易地浏览和选择图片所在的文件夹位置。packages/theme/src/common/color-tokens/index.ts (1)
38-38
: 导出 chatColors 模块的颜色令牌这个变更添加了对
./chatColors
模块的导出,使聊天相关的颜色令牌可以从索引文件访问,符合当前代码库的模块组织模式。packages/ai-native/src/browser/components/chat-context/index.tsx (1)
18-18
: 更新了文件导入路径将
ContextSelector
组件的导入路径从./ContextSelector
更改为./context-selector
,这是从 PascalCase 命名转向 kebab-case 命名的标准化重构。packages/ai-native/src/browser/chat/chat-proxy.service.ts (1)
154-154
: 添加了对图片数据的支持将
images: request.images
添加到传递给requestStream
方法的对象中,使聊天请求能够包含图片数据。这个更改与 PR 的目标(支持在聊天中添加图片)保持一致。packages/core-common/src/types/ai-native/index.ts (2)
174-174
: 添加了图片支持到 AI 服务请求选项为
IAIBackServiceOption
接口添加了可选的images
属性,类型为string[]
,允许在 AI 后端服务请求中包含多个图片引用。
444-444
: 添加了图片支持到历史聊天消息为
IHistoryChatMessage
接口添加了可选的images
属性,类型为string[]
,使聊天历史记录能够保存和显示消息中包含的图片。🧰 Tools
🪛 Biome (1.9.4)
[error] 444-447: The enum declaration should not be const
Const enums are not supported by bundlers and are incompatible with the 'isolatedModules' mode. Their use can lead to import inexistent values.
See TypeScript Docs for more details.
Safe fix: Turn the const enum into a regular enum.(lint/suspicious/noConstEnum)
packages/core-browser/src/style/icon/ide-iconfont.ts (1)
1-1
: 图标字体资源已更新此更改更新了图标字体的 CSS 文件 URL,这很可能是为了支持新的聊天图片上传功能所需的图标。确保已测试新图标在各种环境中的正确显示。
packages/file-tree-next/src/browser/file-tree-contribution.ts (1)
447-453
: 增强了文件定位命令的用户体验这段新代码确保在执行文件定位命令时,如果资源管理器面板未显示,则会自动激活并展开它。这是一个不错的用户体验改进,尤其是当用户从聊天界面点击文件或图片引用时非常有用。
200毫秒的延迟给UI足够的时间来更新视图状态,确保后续的文件展示操作能正确执行。
packages/i18n/src/common/zh-CN.lang.ts (2)
1221-1221
: 聊天输入提示文本已更新将提示文本从"可以问我任何问题,或键入主题 '/'" 更改为"可以问我任何问题,输入 @ 可引用内容",以反映新的文件和文件夹引用功能。这使用户更清楚如何在聊天中引用内容。
1230-1231
: 新增了文件和文件夹引用的本地化文本添加了"当前文件"和"当前文件夹"的本地化条目,支持聊天中的文件和文件夹引用功能。这些条目将用于在聊天界面中显示引用的上下文信息。
packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts (1)
28-28
: 更新了 provideContextPrompt 为异步方法测试代码中的
provideContextPrompt
函数已从同步变为异步 (添加了async
关键字)。这反映了主代码库中对应方法的更改,使其能够处理更复杂的操作,如在聊天中加载图片或文件引用时可能需要的异步文件操作。确保所有调用此方法的代码都已经更新以处理返回的 Promise。
packages/ai-native/src/browser/components/ChatThinking.tsx (4)
15-15
: 扩展接口以支持更多内容类型扩展
ITinkingProps
接口中的children
属性类型,以支持字符串和 React 节点数组,有利于提升组件的灵活性,方便处理更复杂的内容展示,包括图片等多媒体内容。
34-39
: 优化空内容判断逻辑新增的
isEmptyChildren
变量使用 useMemo 缓存判断结果,并针对数组类型做了特殊处理。这种实现方式比直接判断children
更加健壮,可以正确处理各种 React 节点类型(包括图片内容)的空值检查。
42-42
: 使用优化后的空值检查将条件判断从直接检查
children
改为使用isEmptyChildren
,确保了组件能够一致地处理各种类型的内容,包括新增支持的图片内容。
61-61
: 保持进度条渲染逻辑一致性将进度条的条件渲染也更新为使用
isEmptyChildren
,确保了组件在处理不同类型内容(包括图片)时的行为一致性。packages/ai-native/src/browser/model/msg-history-manager.ts (1)
65-65
:❓ Verification inconclusive
消息历史管理器支持图片属性
将
images
属性添加到用户消息的必需属性中,确保了消息历史可以正确记录和管理带有图片的消息。这是实现聊天中添加图片功能的关键部分之一。不过,可能需要考虑以下几点:
- 是否需要在
addAssistantMessage
和updateAssistantMessage
方法中也添加对 images 的支持?- 确保
toJSON
方法能够正确序列化和反序列化包含图片的消息
🏁 Script executed:
#!/bin/bash # 检查是否有其他地方使用了 images 属性但未更新 # 搜索使用 addUserMessage 方法的地方,检查它们是否已更新以提供 images 参数 rg -A 2 "addUserMessage\(" --type typescriptLength of output: 81
请确认消息方法对 images 属性的支持一致性
目前在文件
packages/ai-native/src/browser/model/msg-history-manager.ts
中,已经将images
属性添加到用户消息的必需项中,确保带图片消息的正确记录。请注意以下几点:
- 检查
addAssistantMessage
和updateAssistantMessage
方法是否也需要支持传递images
参数。- 确保
toJSON
方法能正确序列化和反序列化包含images
的消息数据。请运行上面的新脚本确认相关调用是否更新完毕,或手动检查这些方法,确保整体功能一致正确。
🏁 Script executed:
#!/bin/bash # 搜索用户消息调用(addUserMessage)的地方,检查是否传递 images 属性 rg -A 2 "addUserMessage\(" -g "*.ts" # 搜索助手消息新增方法(addAssistantMessage)的调用,检查是否传递 images 属性 rg -A 2 "addAssistantMessage\(" -g "*.ts" # 搜索助手消息更新方法(updateAssistantMessage)的调用,检查是否传递 images 属性 rg -A 2 "updateAssistantMessage\(" -g "*.ts"Length of output: 1206
请确认图片消息支持的一致性
经过脚本验证,当前代码在
packages/ai-native/src/browser/model/msg-history-manager.ts
文件中:
- 在
addUserMessage
方法中,已通过必需属性内包含images
(第65行处)确保用户消息记录支持图片。- 但在
addAssistantMessage
和updateAssistantMessage
方法中,函数签名依然维持为Omit<IExcludeMessage, 'role'>
,未显示添加对images
属性的更新。请开发者:
- 确认是否需要在
addAssistantMessage
与updateAssistantMessage
方法中也明确支持或传递images
属性。- 检查
toJSON
方法,确保其能正确序列化和反序列化包含图片属性的消息。packages/ai-native/src/browser/chat/chat.internal.service.ts (1)
65-66
: 很好的修改,支持了聊天中的图片功能方法签名的变更符合PR的目标,允许在创建请求时传递图片数据。实现简单明了,将图片数组作为可选参数传递给底层服务。
packages/ai-native/src/browser/index.ts (2)
49-52
: 添加图片上传提供者的导入正确引入了图片上传相关的类和令牌,为图片上传功能提供必要的类型定义。
193-196
: 正确注册图片上传提供者将图片上传提供者注册到依赖注入系统中,确保其可被其他组件正确访问和使用。遵循了代码库中的标准服务注册模式。
packages/i18n/src/common/en-US.lang.ts (2)
1452-1452
: 更新聊天输入框提示文本将提示文本从"Ask Copilot or type / for commands"改为"Ask anything, @ to mention",清晰地指导用户可以使用@符号来提及或引用内容,这与新增的图片上传功能交互模式一致。
1462-1463
: 添加上下文菜单本地化文本新增了"Current File"和"Current Folder"的本地化字符串,这些字符串很可能用于在使用@提及功能时,显示当前文件和当前文件夹的选项标签,完善了图片和文件上传功能的用户体验。
packages/file-search/src/node/file-search.service.ts (3)
43-43
: 添加仅文件夹搜索选项添加了
onlyFolders
选项,默认为false
,为文件搜索功能增加了过滤目录的能力,这对于支持图片上传时的文件夹浏览功能很有用。
97-107
: 实现文件夹过滤逻辑通过使用
fs.statSync
检查文件是否为目录,正确实现了文件夹过滤功能。代码也适当处理了可能出现的异常情况。
134-134
: 修复了路径深度比较的错误将
Path.pathDepth(a)
更正为Path.pathDepth(b)
,修复了排序逻辑中的错误。这个修复看起来与主要功能无直接关系,但增强了代码的正确性。packages/ai-native/src/common/index.ts (3)
55-58
: 增加图片支持到消息结构中为
IChatMessageStructure
接口添加了可选的images
属性,这是实现图片聊天功能的基础。接口定义清晰,并提供了适当的 JSDoc 注释说明。
207-207
: 代理请求中添加图片支持在
IChatAgentRequest
接口中添加了images
可选属性,与IChatMessageStructure
接口保持一致,确保图片数据可以在请求处理流程中传递。
245-245
: 请求消息添加图片属性在
IChatRequestMessage
接口中添加了images
可选属性,完成了图片数据在所有相关接口中的一致性支持。packages/ai-native/src/browser/chat/chat-manager.service.ts (3)
153-153
: 更新方法签名以支持图片参数
createRequest
方法签名已更新,新增了可选的images
参数,与接口变更保持一致。
163-163
: 将图片数据传递给请求对象将
images
参数正确地传递到model.addRequest
方法中,确保图片数据被包含在创建的请求对象中。
194-194
: 在请求属性中包含图片数据将
request.message.images
添加到requestProps
对象中,确保图片数据能够被正确传递给代理服务进行处理。packages/ai-native/src/browser/components/mention-input/mention-input.module.less (3)
1-34
: 整体布局规则明确,易于维护
.input_container
及其子元素的基础布局清晰,但需要注意.model_selector
、.editor_area
与.editor
等内部元素在高度受限时的自适应表现,防止文本或控件被裁剪。
75-132
:.footer
与.send_button
的交互逻辑可读性较高
使用统一的悬浮与点击反馈能提升一致性;建议确保transition
动画效果在低配置设备或低帧率情况下,也能保持流畅,避免卡顿。
192-334
: 下拉列表、空状态与加载状态均有独立样式,设计完善
加载动画及空状态提示能够有效提升用户体验,但仍需定期检查颜色或字体,以适配更多主题场景。packages/ai-native/src/browser/components/mention-input/mention-input.tsx (1)
90-159
: 二级菜单的防抖与异步加载实现避免了多余请求
现有逻辑对高优先级项目的即时展示与异步加载的合并思路清晰,但要留意可能存在竞争条件(例如组件卸载后或快速切换筛选输入)的情况,需确保异步请求返回时不会导致更新已卸载的组件。packages/ai-native/src/browser/ai-core.contribution.ts (4)
98-98
: 引入新依赖模块的导入语句。
此处引入ImageUploadProviderRegistryToken
用于图像上传功能的注册,路径看起来合理,未发现命名冲突或循环依赖。
118-118
: 新增的IImageUploadProviderRegistry
类型导入。
导入定义清晰,与周边接口风格一致。暂未发现命名或引用问题。
201-203
: 在类中使用依赖注入注入imageUploadProviderRegistry
。
此处通过@Autowired
注解实现构造注入,字段命名与接口名相符,易于理解和维护。
461-461
: 在registerFeature
方法中注册图像上传功能。
使用可选链调用contribution.registerImageUploadProvider?.(this.imageUploadProviderRegistry)
,确保在不存在实现时不会抛出异常。整体逻辑无明显问题。packages/ai-native/src/browser/chat/chat-agent.service.ts (3)
62-62
: 将llmContextService
注入为受保护属性。
从命名上看更能体现其与 LLM(大语言模型)相关的用途,增强可读性。
155-155
: 在首次发送消息时为请求追加上下文。
通过await this.provideContextMessage(...)
将上下文融合进request.message
,逻辑清晰。留意上下文为空或异常情况时对提示的影响。
157-157
: 在部分条件下重新获取上下文信息。
当需要 regenerate 或聊天历史为空时,也会执行等待上下文处理。应确保对shouldUpdateContext
的赋值逻辑足够准确,避免重复或漏掉更新。packages/ai-native/src/browser/components/ChatMentionInput.tsx (3)
1-23
: 导入与基础模块设置。
- 包含了 React 相关依赖与 IDE 核心服务,如
LabelService
、CommandService
等。- 使用
styles
从.less
文件导入样式,符合组件化的做法。
整体无显著问题,命名清晰,依赖结构合理。
51-314
: 核心组件ChatMentionInput
逻辑。
- 状态管理
- 使用
useState
管理输入框内容value
和上传图片数组images
,简单明了。- 通过
useEffect
同步更新外部传入的props.value
,防止数据源不一致。- 默认菜单项
- 将文件和文件夹分别封装为提及项 (
MentionType.FILE
/MentionType.FOLDER
),并提供获取最高级及搜索结果的回调函数。- 依赖
workspaceService
、searchService
、recentFilesManager
等服务,逻辑清晰。- 操作按钮与配置
defaultMentionInputFooterOptions
中自定义按钮如MCP Server
打开相应命令,UI 扩展方式灵活。- 发送及停止功能
handleSend
在被调用时会携带images
,实现文本和图片一起发送,满足多媒体需求。handleStop
通过调用aiChatService.cancelRequest();
终止当前请求。- 图片上传处理
handleImageUpload
中先获取imageUploadProvider
,若不存在则提示错误;已考虑用户体验。- 上传成功后使用
setImages
更新组件内图片列表。
整体结构层次分明,逻辑易读,无明显性能隐患。
316-335
: 子组件ImagePreviewer
预览并管理已上传图片。
- 简单直观地在缩略图区域显示图片,并提供删除按钮。
- 采用
index
作为 key,对小规模图片数组而言基本可行。若后续需要处理图片顺序变化,需进一步优化 key 生成方式。- 视觉与交互逻辑合理,删除功能通过回调向父组件传递。
packages/ai-native/src/common/prompts/context-prompt-provider.ts (12)
3-3
: 导入工作区服务用于获取文件路径,逻辑合理。
14-14
: 方法签名改为异步以返回Promise<string>
,适配新的异步需求,没问题。
22-24
: 注入workspaceService
便于后续获取工作区路径,使用方式恰当。
25-35
: 在provideContextPrompt
中异步获取当前文件信息并构建模板,结构清晰,易于维护。
37-53
:getCurrentFileInfo
通过 URI 转相对路径后返回文件内容和语言信息,逻辑简明且符合使用场景。
55-82
:buildPromptTemplate
将所有上下文片段组装到一个多段字符串里,结构清楚且易于扩展。
84-92
:buildRecentFilesSection
用来构造<recently_viewed_files>
部分,逻辑简单明了。
94-112
:buildAttachedFilesSection
依次创建文件块与错误信息区块,清晰易读。
114-120
:buildFileContentSection
以三引号格式输出文件内容,符合 Markdown 语法,没问题。
122-128
:buildLineErrorsSection
用<linter_errors>
标签包裹错误文本,形式统一且易于识别。
130-136
:buildAttachedFoldersSection
以<attached_folders>
包裹文件夹信息,逻辑与文件处理一致。
138-147
:buildCurrentFileSection
将当前文件的语言、高亮内容结构化输出,易于后续解析。packages/theme/src/common/color-tokens/chatColors.ts (1)
1-63
: 新增多组聊天相关色彩令牌,涵盖了边框、背景和前景等多种场景,定义完备且便于主题管理。packages/ai-native/src/browser/components/ChatEditor.tsx (12)
5-14
: 新增对多种服务与方法的导入,如EDITOR_COMMANDS
、FILE_COMMANDS
、LabelService
等,符合后续操作需求。
20-22
: 导入CommandService
与URI
,为后续命令执行和路径解析提供支持。
37-37
: 引入IWorkspaceService
的类型声明,便于在后文使用工作区相关方法。
172-173
: 新增正则rgAttachedFile
和rgAttachedFolder
,用于匹配文件与文件夹标签,思路清晰。
174-194
:handleAttachmentClick
根据工作区根路径依次尝试执行命令,如出错则继续下一个根路径;continue
用于跳过后续逻辑并非多余,可忽略静态分析提示。🧰 Tools
🪛 Biome (1.9.4)
[error] 189-189: Unnecessary continue statement
Unsafe fix: Delete the unnecessary continue statement
(lint/correctness/noUnnecessaryContinue)
195-204
:renderAttachment
动态渲染点击区域和对应图标,便于用户快速打开文件或文件夹。
249-289
: 在处理内联与代码块时,增添对<attached_file>
与<attached_folder>
的检测与解析,内容拆分与片段插入逻辑较完善。
308-310
: 在CodeBlockWrapper
Props 中增加注入labelService
、commandService
和workspaceService
,与子组件处理保持一致。
322-330
: 将外部服务与数据透传给CodeBlock
,简化组件间依赖注入,写法合理。
337-337
: 在CodeBlockWrapperInput
中增加images
、labelService
等 Props,扩展功能以支持图片显示和命令操作。Also applies to: 341-343
373-377
: 使用images?.map
动态渲染上传的图片,实现聊天时的图片可视化功能。
390-398
: 为最终渲染插入了CodeBlock
,同时将命令与工作区服务一并传递,避免重复注入。packages/ai-native/src/browser/chat/chat.view.tsx (4)
4-11
: 新增依赖注入的导入项看起来正确。
这些服务被正确注入,对于增强聊天功能的可扩展性很有帮助,目前未发现问题。
40-40
: 注入IWorkspaceService
合理。
工作区服务对获取相对/绝对路径等功能有帮助,理论上无冲突。
43-43
:LLMContextService
的引入符合上下文需求。
这在后续处理文件/文件夹上下文时会非常实用,暂无问题。
606-610
:handleAgentReply
中对images
属性的支持完善了流程。
继续保持与上层数据结构的同步,不然可能导致数据不一致。packages/ai-native/src/common/llm-context.ts (3)
1-1
: 从ai
模块引入DataContent
仅是基础类型导入,功能方面暂不影响。
21-25
:addFolderToContext
为上下文添加文件夹的功能与当前需求匹配。
请确认后续若文件夹过大,是否需要可视化或分页管理,避免上下文递归深度过大。
43-43
: 序列化改为返回Promise<SerializedContext>
符合异步场景。
必须确保调用方知道这是异步操作,不要在逻辑里产生阻塞。packages/ai-native/src/browser/types.ts (2)
1-1
: 新增import { DataContent } from 'ai'
仅是类型/数据结构的引入,可以简化对图片内容的通用处理。
347-350
:registerImageUploadProvider
为核心贡献点添加图片上传能力。
在多模块协作时,需要保证注册逻辑简单、调用方易于使用。packages/ai-native/src/node/base-language-model.ts (4)
1-10
: 导入的模块支持图片消息功能导入了
CoreUserMessage
、ImagePart
和TextPart
类型,这些是支持图片消息所必需的。这些导入与 PR 的目标(支持在聊天中添加图片)一致。
60-60
: 正确传递图片参数在
request
方法中传递了options.images
参数给handleStreamingRequest
方法,这是实现图片支持功能的关键部分。
90-90
: 添加图片参数声明为
handleStreamingRequest
方法添加了images?: string[]
可选参数,类型声明正确,保持了与调用者接口的一致性。
103-114
: 实现了图片和文本消息的组合逻辑代码能够正确处理包含图片的消息情况。当存在图片时,将内容构造为包含
TextPart
和ImagePart
的数组;当没有图片时,保持原有的纯文本行为。这种条件处理方式保证了向后兼容性。packages/ai-native/src/browser/components/mention-input/types.ts (5)
1-3
: 引入了必要的依赖导入了
LabelService
和IWorkspaceService
类型,用于组件的标签和工作区功能支持,是组件实现的基础。
4-14
: 定义了提及项的核心接口
MentionItem
接口定义了提及项的核心属性,包括 id、类型、文本等基本信息,以及获取子项的方法。接口设计合理,有良好的可扩展性。
50-54
: 定义了支持文件和文件夹的提及类型
MentionType
枚举定义了三种提及类型:FILE、FOLDER 和 CODE,这与 PR 目标中支持添加图片到聊天相关,因为这些类型将用于处理文件附件。
71-83
: 组件接口设计支持图片上传功能
MentionInputProps
接口包含了onImageUpload
属性,用于处理图片上传事件,完全符合 PR 的目标。接口设计考虑了组件所需的各种交互事件和配置选项,结构清晰。
78-78
: 图片上传处理函数定义添加了
onImageUpload
回调函数,用于处理图片上传事件,这是实现 PR 目标(支持在聊天中添加图片)的关键部分。
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
Outdated
Show resolved
Hide resolved
if (options.onlyFolders) { | ||
args.push('--type-list', 'd:dir'); | ||
args.push('--type', 'd'); | ||
} |
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.
🛠️ Refactor suggestion
代码重复:相同的文件夹过滤参数在两处添加
在doFind
和getSearchArgs
方法中都添加了相同的文件夹过滤参数。由于doFind
方法已经调用了getSearchArgs
获取参数,因此在doFind
中再次添加这些参数会导致重复。
建议删除doFind
方法中的重复代码(157-160行),仅保留getSearchArgs
中的实现。
- if (options.onlyFolders) {
- args.push('--type-list', 'd:dir');
- args.push('--type', 'd');
- }
Also applies to: 186-189
.mention_panel_container { | ||
position: absolute; | ||
top: -20px; | ||
left: 0; | ||
right: 0; | ||
width: 100%; | ||
z-index: 1000; | ||
transform: translateY(-100%); | ||
display: flex; | ||
justify-content: center; | ||
} | ||
|
||
.mention_panel { | ||
background-color: var(--editor-background); | ||
color: var(--editor-foreground); | ||
border-radius: 6px; | ||
max-height: 300px; | ||
overflow-y: auto; | ||
z-index: 1000; | ||
padding: 8px 4px; | ||
width: 100%; | ||
} | ||
|
||
.mention_item { | ||
padding: 0 8px; | ||
border-radius: 8px; | ||
cursor: pointer; | ||
line-height: 22px; | ||
height: 22px; | ||
display: flex; | ||
align-items: center; | ||
transition: all 0.2s ease; | ||
justify-content: space-between; | ||
color: var(--foreground); | ||
border-radius: 4px; | ||
margin-bottom: 5px; | ||
|
||
&:last-child { | ||
margin-bottom: 0; | ||
} | ||
} | ||
|
||
.mention_list { | ||
margin: 0; | ||
padding: 0; | ||
list-style: none; | ||
} | ||
|
||
.mention_item.active { | ||
color: var(--kt-tree-inactiveSelectionForeground); | ||
background: var(--kt-tree-inactiveSelectionBackground); | ||
} | ||
|
||
.mention_item:hover:not(.active) { | ||
color: var(--kt-tree-inactiveSelectionForeground); | ||
background: var(--kt-tree-inactiveSelectionBackground); | ||
} |
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.
🛠️ Refactor suggestion
提及面板位置固定在输入框上方时,应留意在小屏幕或折叠面板场景的可见性
.mention_panel_container
使用了 transform: translateY(-100%)
,如在极小屏幕或外层容器高度不足的情况下,需要确保面板不会超出视窗。
// 原有的处理逻辑(用于非内联搜索情况) | ||
// 创建一个带样式的提及标签 | ||
const mentionTag = document.createElement('span'); | ||
mentionTag.className = styles.mention_tag; | ||
mentionTag.dataset.id = item.id; | ||
mentionTag.dataset.type = item.type; | ||
mentionTag.dataset.contextId = item.contextId || ''; | ||
mentionTag.contentEditable = 'false'; | ||
|
||
// 为 file 和 folder 类型添加图标 | ||
if (item.type === 'file' || item.type === 'folder') { | ||
// 创建图标容器 | ||
const iconSpan = document.createElement('span'); | ||
iconSpan.className = cls( | ||
styles.mention_icon, | ||
item.type === 'file' ? labelService?.getIcon(new URI(item.text)) : getIcon('folder'), | ||
); | ||
mentionTag.appendChild(iconSpan); | ||
} | ||
const workspace = workspaceService?.workspace; | ||
let relativePath = item.text; | ||
if (workspace && item.contextId) { | ||
relativePath = item.contextId.replace(new URI(workspace.uri).codeUri.fsPath, '').slice(1); | ||
} | ||
// 创建文本内容容器 | ||
const textSpan = document.createTextNode(relativePath); | ||
mentionTag.appendChild(textSpan); | ||
|
||
// 定位到 @ 符号的位置 | ||
let charIndex = 0; | ||
let foundStart = false; | ||
const textNodes: Array<{ node: Node; start: number; end: number }> = []; | ||
|
||
function findPosition(node: Node) { | ||
if (node.nodeType === 3) { | ||
// 文本节点 | ||
textNodes.push({ | ||
node, | ||
start: charIndex, | ||
end: charIndex + node.textContent!.length, | ||
}); | ||
charIndex += node.textContent!.length; | ||
} else if (node.nodeType === 1) { | ||
// 元素节点 | ||
const children = node.childNodes; | ||
for (const child of Array.from(children)) { | ||
findPosition(child); | ||
} | ||
} | ||
} | ||
|
||
findPosition(editorRef.current); | ||
|
||
const tempRange = document.createRange(); | ||
|
||
if (mentionState.startPos !== null) { | ||
for (const textNode of textNodes) { | ||
if (mentionState.startPos - 1 >= textNode.start && mentionState.startPos - 1 <= textNode.end) { | ||
const startOffset = mentionState.startPos - 1 - textNode.start; | ||
tempRange.setStart(textNode.node, startOffset); | ||
foundStart = true; | ||
} | ||
|
||
if (foundStart) { | ||
// 如果是点击触发,使用过滤文本的长度来确定结束位置 | ||
const cursorPos = isTriggerByClick | ||
? mentionState.startPos + mentionState.filter.length - 1 | ||
: getCursorPosition(editorRef.current); | ||
|
||
if (cursorPos >= textNode.start && cursorPos <= textNode.end) { | ||
const endOffset = cursorPos - textNode.start; | ||
tempRange.setEnd(textNode.node, endOffset); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (foundStart) { | ||
tempRange.deleteContents(); | ||
tempRange.insertNode(mentionTag); | ||
|
||
// 将光标移到提及标签后面 | ||
const newRange = document.createRange(); | ||
newRange.setStartAfter(mentionTag); | ||
newRange.setEndAfter(mentionTag); | ||
selection.removeAllRanges(); | ||
selection.addRange(newRange); | ||
|
||
// 添加一个空格,增加间隔 | ||
const spaceNode = document.createTextNode('\u00A0'); // 使用不间断空格 | ||
newRange.insertNode(spaceNode); | ||
newRange.setStartAfter(spaceNode); | ||
newRange.setEndAfter(spaceNode); | ||
selection.removeAllRanges(); | ||
selection.addRange(newRange); | ||
} | ||
setMentionState((prev) => ({ ...prev, active: false })); | ||
editorRef.current.focus(); | ||
}; |
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.
在编辑器中插入 DOM 元素时需注意可能的 XSS 风险
虽然此处使用 document.createTextNode()
替换了文本节点,但整体仍依赖 editorRef.current.innerHTML
进行处理。若任何不受信任的外部输入或字符串未正确过滤,即便在本地应用场景下也可能潜藏风险,建议在处理粘贴内容或外部文本时增加 HTML Sanitization。
|
||
const handleStop = React.useCallback(() => { | ||
if (onStop) { | ||
onStop(); | ||
} | ||
}, [onStop]); | ||
|
||
// 渲染自定义按钮 | ||
const renderButtons = React.useCallback( | ||
(position: FooterButtonPosition) => | ||
(footerConfig.buttons || []) | ||
.filter((button) => button.position === position) | ||
.map((button) => ( | ||
<Popover | ||
key={button.id} | ||
overlayClassName={styles.popover_icon} | ||
id={`ai-chat-${button.id}`} | ||
position={PopoverPosition.top} | ||
title={button.title} | ||
> | ||
<EnhanceIcon | ||
className={cls(getIcon(button.icon), styles[`${button.id}_logo`])} | ||
tabIndex={0} | ||
role='button' | ||
ariaLabel={button.title} | ||
onClick={button.onClick} | ||
/> | ||
</Popover> | ||
)), | ||
[footerConfig.buttons], | ||
); | ||
|
||
return ( | ||
<div className={styles.input_container}> | ||
{mentionState.active && ( | ||
<div className={styles.mention_panel_container}> | ||
<MentionPanel | ||
items={getCurrentItems()} | ||
activeIndex={mentionState.activeIndex} | ||
onSelectItem={(item) => handleSelectItem(item, true)} | ||
position={{ top: 0, left: 0 }} | ||
filter={mentionState.level === 0 ? mentionState.filter : mentionState.secondLevelFilter} | ||
visible={true} | ||
level={mentionState.level} | ||
loading={mentionState.loading} | ||
/> | ||
</div> | ||
)} | ||
<div className={styles.editor_area}> | ||
<div | ||
ref={editorRef} | ||
className={styles.editor} | ||
contentEditable={true} | ||
onInput={handleInput} | ||
onKeyDown={handleKeyDown} | ||
onPaste={handlePaste} | ||
onCompositionEnd={handleCompositionEnd} | ||
/> | ||
</div> | ||
<div className={styles.footer}> | ||
<div className={styles.left_control}> | ||
{footerConfig.showModelSelector && ( | ||
<Select | ||
options={footerConfig.modelOptions || []} | ||
value={selectedModel} | ||
onChange={handleModelChange} | ||
className={styles.model_selector} | ||
size='small' | ||
/> | ||
)} | ||
{renderButtons(FooterButtonPosition.LEFT)} | ||
</div> | ||
<div className={styles.right_control}> | ||
{renderButtons(FooterButtonPosition.RIGHT)} | ||
<Popover | ||
overlayClassName={styles.popover_icon} | ||
id={'ai-chat-send'} | ||
position={PopoverPosition.top} | ||
content={!loading ? 'Send' : 'Stop'} | ||
> | ||
{!loading ? ( | ||
<EnhanceIcon | ||
wrapperClassName={styles.send_logo} | ||
className={cls(getIcon('send-outlined'), styles.send_logo_icon)} | ||
tabIndex={0} | ||
role='button' | ||
onClick={handleSend} | ||
ariaLabel={'Send'} | ||
/> | ||
) : ( | ||
<EnhanceIcon | ||
wrapperClassName={styles.stop_logo} | ||
className={cls(getIcon('stop'), styles.stop_logo_icon)} | ||
tabIndex={0} | ||
role='button' | ||
ariaLabel={'Stop'} | ||
onClick={handleStop} | ||
/> | ||
)} | ||
</Popover> |
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.
🛠️ Refactor suggestion
提及标签处理与模型选择功能集成度高,建议加强单元测试覆盖
当前 handleSend
等流程将最终生成替换后的内容再提交,有一定字符串操作复杂度;添加针对各种 mention、粘贴场景和多选项模型的测试用例可降低回归风险。
如果需要,我可以提供单元测试示例来帮助验证各种边界场景,并协助完善测试覆盖率。
const reportExtra = { | ||
actionSource: ActionSourceEnum.Chat, | ||
actionType: ActionTypeEnum.Send, | ||
}; | ||
agentId = agentId ? agentId : ChatProxyService.AGENT_ID; | ||
// 提取并替换 {{@file:xxx}} 中的文件内容 | ||
let processedContent = message; | ||
const filePattern = /\{\{@file:(.*?)\}\}/g; | ||
const fileMatches = message.match(filePattern); | ||
let isCleanContext = false; | ||
if (fileMatches) { | ||
for (const match of fileMatches) { | ||
const filePath = match.replace(/\{\{@file:(.*?)\}\}/, '$1'); | ||
if (filePath && !isCleanContext) { | ||
isCleanContext = true; | ||
llmContextService.cleanFileContext(); | ||
} | ||
const fileUri = new URI(filePath); | ||
llmContextService.addFileToContext(fileUri, undefined, true); | ||
const relativePath = (await workspaceService.asRelativePath(fileUri))?.path || fileUri.displayName; | ||
// 获取文件内容 | ||
// 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签 | ||
processedContent = processedContent.replace(match, `\`<attached_file>${relativePath}\``); | ||
} | ||
} | ||
|
||
const agentId = value.agentId ? value.agentId : ChatProxyService.AGENT_ID; | ||
return handleAgentReply({ message, agentId, command, reportExtra }); | ||
const folderPattern = /\{\{@folder:(.*?)\}\}/g; | ||
const folderMatches = processedContent.match(folderPattern); | ||
if (folderMatches) { | ||
for (const match of folderMatches) { | ||
const folderPath = match.replace(/\{\{@folder:(.*?)\}\}/, '$1'); | ||
const folderUri = new URI(folderPath); | ||
llmContextService.addFolderToContext(folderUri); | ||
const relativePath = (await workspaceService.asRelativePath(folderUri))?.path || folderUri.displayName; | ||
// 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签 | ||
processedContent = processedContent.replace(match, `\`<attached_folder>${relativePath}\``); | ||
} | ||
} | ||
return handleAgentReply({ message: processedContent, images, agentId, command, reportExtra }); |
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.
🛠️ Refactor suggestion
在 handleSend
中对文件/文件夹占位符进行处理逻辑恰当,但需注意性能。
在复杂场景下,正则匹配和多次 addFileToContext
/addFolderToContext
可能改变性能表现,后续若规模扩大可考虑批量处理。
}, | ||
}) | ||
} | ||
onSend={handleSend} |
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.
类型不匹配导致管线报错,需尽快修复。
当前的 onSend
类型定义为 (...args) => void
,但实际实现为异步 Promise<void>
。请在 ChatInputRender
或此处对类型进行统一修改,避免后续构建失败。
- onSend: (value: string, images?: string[], agentId?: string, command?: string) => void;
+ onSend: (value: string, images?: string[], agentId?: string, command?: string) => Promise<void>;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
onSend={handleSend} | |
// File: packages/ai-native/src/browser/chat/chat.view.tsx | |
// Assuming the original interface looked like this: | |
interface ChatInputRenderProps { | |
// Other props... | |
- onSend: (value: string, images?: string[], agentId?: string, command?: string) => void; | |
+ onSend: (value: string, images?: string[], agentId?: string, command?: string) => Promise<void>; | |
// Other props... | |
} | |
export const ChatInputRender: React.FC<ChatInputRenderProps> = ({ onSend, ...props }) => { | |
// Component implementation... | |
return ( | |
<div> | |
{/* Other component code */} | |
{/* Usage remains unchanged */} | |
<button onClick={() => onSend("sample message")}>Send</button> | |
</div> | |
); | |
}; | |
// And elsewhere in the file, where the component is used: | |
export const ChatView = () => { | |
const handleSend = async ( | |
value: string, | |
images?: string[], | |
agentId?: string, | |
command?: string | |
): Promise<void> => { | |
// Async implementation... | |
}; | |
return ( | |
<ChatInputRender | |
onSend={handleSend} | |
// Other props... | |
/> | |
); | |
}; |
🧰 Tools
🪛 GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: unittest (ubuntu-latest, 18.x, node)
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: build (ubuntu-latest, 20.x)
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: unittest (macos-latest, 18.x, jsdom)
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Check: ubuntu-latest, Node.js 20.x
[failure] 866-866:
Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Actions: E2E Test
[error] 866-866: Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
🪛 GitHub Actions: CI
[error] 866-866: error TS2322: Type '(message: string, images?: string[], agentId?: string, command?: string) => Promise' is not assignable to type '((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, agentId?: string | undefined, command?: string | undefined) => void) & ((value: string, images?: string[] | undefined, agentId?: string | undefined, command?: string | undefined, op...'.
agentId?: string; | ||
command?: string; | ||
}) => React.ReactElement | React.JSX.Element; | ||
export type ChatThinkingRender = (props: { thinkingText?: string }) => React.ReactElement | React.JSX.Element; | ||
export type ChatThinkingResultRender = (props: { thinkingResult?: string }) => React.ReactElement | React.JSX.Element; | ||
export type ChatInputRender = (props: { | ||
onSend: (value: string, agentId?: string, command?: string) => void; | ||
onSend: (value: string, images?: string[], agentId?: string, command?: string) => void; |
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.
ChatInputRender
的 onSend
方法签名也扩展了 images
。
保持了前后端一致性,但请确保与实际实现对齐并解决返回类型不符的问题。
@@ -508,4 +509,11 @@ export class AINativeContribution implements AINativeCoreContribution { | |||
useClass: DefaultChatAgentPromptProvider, | |||
}); | |||
} | |||
|
|||
registerImageUploadProvider(registry: IImageUploadProviderRegistry): void { |
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.
感觉可以注册在 IChatFeatureRegistry 里,语义更明确一些。就能知道这个 image upload 是服务于 chat 面板的
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.
很有道理,我改下
|
||
registerImageUploadProvider(registry: IImageUploadProviderRegistry): void { | ||
registry.registerImageUploadProvider({ | ||
imageUpload: async (file) => |
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.
这个删了吧。
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.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
packages/ai-native/src/browser/chat/chat.view.tsx (1)
866-878
:⚠️ Potential issue注意:ChatInputWrapperRender 的 onSend 属性类型可能不匹配
当前代码将
handleSend
(返回Promise<void>
)传递给onSend
属性,但根据先前的评论,这里可能存在类型不匹配问题。请确保ChatInputRender
接口已更新,以避免构建失败。根据之前的评论和构建错误,需要更新
ChatInputRender
接口中onSend
的类型定义:- onSend: (value: string, images?: string[], agentId?: string, command?: string) => void; + onSend: (value: string, images?: string[], agentId?: string, command?: string) => Promise<void>;如果此问题尚未解决,将导致类型检查失败和构建错误。
♻️ Duplicate comments (1)
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts (1)
513-518
:⚠️ Potential issue图片上传提供者实现需要改进
当前实现将任何上传的图片都映射到一个硬编码的图片 URL,适合样例展示但不适合生产环境。
建议:
- 实现真实的文件上传逻辑,处理不同的文件类型和大小
- 添加错误处理机制
- 考虑添加进度反馈
registerImageUploadProvider(registry: IImageUploadProviderRegistry): void { registry.registerImageUploadProvider({ - imageUpload: async (file) => - 'https://idealab-platform.oss-accelerate.aliyuncs.com/20231125/33808fdb-10ad-428c-8a76-11532ad93b15_idealab2.png?Expires=4102329600&OSSAccessKeyId=LTAI5tFJF3QLwHzEmkhLs9dB&Signature=YcbZrM98pHRRMd%2BGFaI2OiFf8Z8%3D', + imageUpload: async (file) => { + try { + // 实际上传逻辑,例如: + // const formData = new FormData(); + // formData.append('file', file); + // const response = await fetch('/api/upload', { method: 'POST', body: formData }); + // const data = await response.json(); + // return data.url; + + // 开发环境临时实现: + await new Promise(resolve => setTimeout(resolve, 500)); // 模拟网络延迟 + return 'https://idealab-platform.oss-accelerate.aliyuncs.com/20231125/33808fdb-10ad-428c-8a76-11532ad93b15_idealab2.png?Expires=4102329600&OSSAccessKeyId=LTAI5tFJF3QLwHzEmkhLs9dB&Signature=YcbZrM98pHRRMd%2BGFaI2OiFf8Z8%3D'; + } catch (error) { + console.error('图片上传失败', error); + throw new Error('图片上传失败'); + } + }, }); }另外,根据过去的评论,可以考虑将此功能注册在
IChatFeatureRegistry
中,这样语义会更加明确,表明这个图片上传功能是服务于聊天面板的。
🧹 Nitpick comments (4)
packages/components/src/image/index.tsx (1)
1-24
: 实现了基础的图片组件,但可以进一步优化这个新组件使用
rc-image
库实现了基础的图片显示和预览功能。实现比较简洁,但有几点建议:
- 预览容器通过简单的选择器查找可能会导致在某些布局中出现问题
- 组件没有处理加载失败或加载中的状态
- 缺少对图片尺寸限制的处理,可能导致大图片显示问题
建议添加更多自定义配置选项,比如错误处理和尺寸控制。
export const Image = (props: ImageProps) => ( <RcImage prefixCls='kt-image' preview={{ getContainer: () => document.getElementById('main') || document.getElementsByTagName('body')?.[0], mask: ( <div className='mask'> <Icon iconClass='codicon codicon-eye' /> </div> ), + // 添加更多预览配置选项 + // onVisibleChange: (visible) => { /* 处理预览状态变化 */ }, }} + // 添加错误处理和加载状态 + // fallback="data:image/png;base64,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" {...props} /> )packages/components/src/style/mixins.less (1)
145-172
: 新增 modal-mask mixin 但缺少响应式和暗色模式支持这个 mixin 很好地封装了模态框遮罩层的样式,包括动画和定位。但有几点建议:
- 背景色使用了变量
@modal-mask-bg
,但没有为暗色模式提供变体- z-index 使用了变量
@zindex-modal-mask
,确保此变量在 variable.less 中已定义- 缺少响应式设计考虑,特别是在移动设备上的表现
建议添加媒体查询和暗色模式支持,以提升用户体验。
.modal-mask() { pointer-events: none; &.@{prefix}-zoom-enter, &.@{prefix}-zoom-appear { transform: none; // reset scale avoid mousePosition bug opacity: 0; animation-duration: @animation-duration-slow; user-select: none; // https://github.com/ant-design/ant-design/issues/11777 } &-mask { .box(fixed); z-index: @zindex-modal-mask; height: 100%; background-color: @modal-mask-bg; + // 暗色模式支持 + .kt-dark & { + background-color: @modal-mask-bg-dark; + } &-hidden { display: none; } } &-wrap { .box(fixed); overflow: auto; outline: 0; + // 移动设备适配 + @media (max-width: @screen-xs) { + padding: 0; + } } }packages/ai-native/src/browser/components/ChatMentionInput.tsx (1)
301-318
: 建议增强图片上传处理功能图片上传处理函数有几处可以改进:
- 缺少文件大小限制,可能导致大文件上传性能问题
- 上传过程缺少错误处理,如果
imageUploadProvider.imageUpload(file)
调用失败,可能导致程序崩溃- 允许的文件类型硬编码在函数中,建议移至常量或配置中
建议进行以下修改:
+ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; const handleImageUpload = useCallback( async (file: File) => { - const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; - if (!allowedTypes.includes(file.type)) { + if (!ALLOWED_IMAGE_TYPES.includes(file.type)) { messageService.error('Only JPG, PNG, WebP and GIF images are supported'); return; } + + if (file.size > MAX_FILE_SIZE) { + messageService.error(`File size exceeds ${MAX_FILE_SIZE / (1024 * 1024)}MB limit`); + return; + } const imageUploadProvider = imageUploadProviderRegistry.getImageUploadProvider(); if (!imageUploadProvider) { messageService.error('No image upload provider found'); return; } - const data = await imageUploadProvider.imageUpload(file); - setImages([...images, data]); + try { + const data = await imageUploadProvider.imageUpload(file); + setImages([...images, data]); + } catch (error) { + messageService.error(`Failed to upload image: ${error.message || 'Unknown error'}`); + } }, [images], );packages/components/src/image/styles.less (1)
108-157
: 操作控件样式定义完整操作区域样式设计合理,包括禁用状态、悬停效果等。注意到操作包装器被设置为
display: none
,说明部分功能可能为将来准备。进度条位置使用了绝对定位和 transform 实现了良好的居中效果。注释掉的代码表明这部分功能可能正在开发中。建议在注释中添加更多上下文,说明这些样式将在何时启用及其预期功能。例如:
- // 暂时不需要控制 + // 暂时不需要显示操作控件,将在实现高级图片编辑功能时启用
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (18)
packages/ai-native/src/browser/chat/chat.view.tsx
(9 hunks)packages/ai-native/src/browser/components/ChatEditor.tsx
(4 hunks)packages/ai-native/src/browser/components/ChatInput.tsx
(2 hunks)packages/ai-native/src/browser/components/ChatMentionInput.tsx
(6 hunks)packages/ai-native/src/browser/components/components.module.less
(1 hunks)packages/ai-native/src/browser/components/mention-input/mention-input.module.less
(1 hunks)packages/ai-native/src/browser/components/mention-input/mention-input.tsx
(4 hunks)packages/ai-native/src/browser/components/mention-input/types.ts
(2 hunks)packages/ai-native/src/browser/context/llm-context.service.ts
(1 hunks)packages/ai-native/src/common/index.ts
(3 hunks)packages/ai-native/src/node/base-language-model.ts
(5 hunks)packages/components/package.json
(1 hunks)packages/components/src/image/index.tsx
(1 hunks)packages/components/src/image/styles.less
(1 hunks)packages/components/src/style/mixins.less
(1 hunks)packages/components/src/style/variable.less
(3 hunks)packages/file-tree-next/src/browser/file-tree-contribution.ts
(1 hunks)packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/ai-native/src/browser/context/llm-context.service.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/ai-native/src/browser/components/ChatEditor.tsx
- packages/ai-native/src/browser/components/mention-input/mention-input.module.less
- packages/file-tree-next/src/browser/file-tree-contribution.ts
- packages/ai-native/src/browser/components/mention-input/types.ts
- packages/ai-native/src/common/index.ts
- packages/ai-native/src/browser/components/mention-input/mention-input.tsx
- packages/ai-native/src/node/base-language-model.ts
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: build (ubuntu-latest, 20.x)
- GitHub Check: build (macos-latest, 20.x)
- GitHub Check: build-windows
- GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
- GitHub Check: unittest (ubuntu-latest, 18.x, node)
- GitHub Check: unittest (macos-latest, 18.x, jsdom)
- GitHub Check: unittest (macos-latest, 18.x, node)
- GitHub Check: ubuntu-latest, Node.js 20.x
🔇 Additional comments (30)
packages/components/package.json (1)
29-29
: 添加了rc-image依赖,但版本控制策略需要考虑引入了rc-image依赖用于实现图片预览功能,当前使用的版本为^7.11.0。
建议考虑:
- 确保版本兼容性,特别是与其他RC组件的版本协调
- 评估是否需要将依赖锁定到特定版本而不是使用^符号允许次要版本更新
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts (1)
16-16
: API导入顺序调整在保持排序规则一致性的基础上添加了
IImageUploadProviderRegistry
接口的导入。packages/components/src/style/mixins.less (1)
137-143
: 添加了通用的盒模型定位 mixin新增的
.box()
mixin 提供了一种快捷方式来设置元素的绝对/固定定位并填充其容器,代码简洁有效。这是一个良好的抽象,可以在多个组件中重用,减少重复代码。
packages/ai-native/src/browser/components/ChatInput.tsx (2)
164-165
: 方法签名更新准确接口中的
onSend
方法签名已正确更新,包含了可选的images
参数,与新功能要求一致。
343-345
: 正确传递图片参数
handleSendLogic
函数已正确更新以传递空图片数组作为默认值,确保与更新后的接口兼容。packages/ai-native/src/browser/components/components.module.less (2)
605-646
: 缩略图样式实现良好新增的缩略图容器和相关组件的样式结构合理,视觉效果一致。删除按钮的hover效果增强了用户体验。
648-650
: 图片包装器样式简洁明了
.image_wrapper
类定义简单明了,符合当前样式指南。packages/components/src/style/variable.less (3)
62-62
: z-index变量设置合理新增的
@zindex-image
变量值为1080,在现有z-index体系中位置合适,不会导致层级冲突。
116-123
: 新增间距变量定义清晰新增的
@padding-xss
和@margin-xss
变量为样式系统增加了更细粒度的间距控制,定义合理。
218-226
: 图片相关变量定义完善新增的图片相关变量定义全面,包括尺寸、颜色、字体大小等关键属性,并正确引用了现有颜色变量,保持了样式系统的一致性。
packages/ai-native/src/browser/components/ChatMentionInput.tsx (6)
24-31
: 接口更新符合需求
IChatMentionInputProps
接口中的onSend
方法签名已正确更新,添加了图片参数支持。
40-40
: 新增图片属性类型定义合理为
IChatMentionInputProps
接口添加了images
属性,类型为Array<DataContent | URL>
,定义合理。
56-56
: 正确初始化图片状态组件正确初始化了图片状态,并从 props 中获取初始值。
257-274
: 上传图片按钮实现良好上传图片按钮的实现简洁有效,使用了原生文件选择对话框,并正确处理了文件选择事件。
290-296
: 发送时正确包含图片数据
handleSend
方法正确地将图片数据包含在发送请求中,并将图片 URL 转换为字符串。
345-364
: 预览组件实现良好
ImagePreviewer
组件实现简洁明了,正确使用了现有的样式类和组件。删除功能实现正确,通过索引删除对应图片。packages/ai-native/src/browser/chat/chat.view.tsx (9)
284-284
: 已更新handleSend
调用以支持图片参数函数调用已正确更新,现在包含了
message.images
参数,与新的函数签名保持一致。
465-474
: 函数签名已正确更新以支持图片参数
renderUserMessage
函数的签名已适当扩展,增加了对images?: string[]
参数的支持,并在解构赋值中正确引用了该参数。这个更改与 PR 目标(支持在聊天中添加图片)完全一致。
483-483
: 适当地向 ChatUserRoleRender 组件传递了图片数据组件属性已更新,确保图片数据能够传递到渲染组件,保持了数据流的完整性。
489-489
: CodeBlockWrapperInput 组件现在支持图片属性已正确更新组件属性以包含图片数据,确保在不使用自定义渲染器的情况下也能显示图片。
608-611
: handleAgentReply 函数已更新以处理图片数据函数参数和
createRequest
调用已正确更新,确保图片数据能够正确传递到 AI 聊天服务。此更改符合功能需求,使得聊天请求可以包含图片数据。
637-637
: 确保在历史记录中保存图片数据消息历史管理器的
addUserMessage
调用已更新,添加了images: images || []
属性,确保即使没有提供图片也能使用空数组作为默认值。这样可以保持数据结构的一致性,避免潜在的 null/undefined 错误。
646-646
: 向用户消息渲染器传递图片数据在调用
renderUserMessage
时正确传递了图片参数,保持了数据流的连贯性。
762-762
: 恢复功能现已支持图片数据在恢复历史会话时正确传递图片数据,确保用户消息的完整性。这对于保持会话状态的完整性非常重要。
679-720
:❓ Verification inconclusive
handleSend 函数已适当更新以支持图片上传功能
函数签名和实现已更新,现在支持接收和处理图片数据。文件和文件夹占位符处理逻辑保持不变,并在最后正确地将处理后的消息和图片数据传递给
handleAgentReply
。不过,需要注意以下几点:
onSend
的异步返回类型可能与接口定义不匹配,需确保ChatInputRender
接口也已更新。- 多个文件/文件夹占位符的处理可能在大量数据时影响性能。
请检查
ChatInputRender
接口的定义是否已更新为返回Promise<void>
,以避免类型不匹配错误:
🏁 Script executed:
#!/bin/bash # 查找 ChatInputRender 接口定义 rg -A 3 "interface.*ChatInputRender" --type=typescriptLength of output: 91
更新建议:请确认 ChatInputRender 接口的返回类型
handleSend 函数现已更新,支持图片上传,并保持对文件和文件夹占位符的处理逻辑,最终将处理后的内容与图片数据传递给 handleAgentReply。但请注意:
- 请检查 ChatInputRender 接口中 onSend 方法的返回类型是否已更新为
Promise<void>
,以避免类型不匹配问题。(由于之前用于定位该接口定义的脚本因文件类型识别问题未返回结果,请手动验证或采用类似以下命令重新检查:)rg -A 3 "interface.*ChatInputRender" -g "*.{ts,tsx}"- 注意:在处理多个文件/文件夹占位符时,如数据量较大,可能存在性能隐患,建议进一步评估。
packages/components/src/image/styles.less (5)
1-6
: 样式文件结构组织良好文件开始部分导入了必要的变量和混合器,并定义了清晰的类名前缀,这有助于保持命名一致性和避免样式冲突。
7-24
: 图片容器和基本样式定义完善图片容器使用了相对定位,便于后续元素的绝对定位。图片样式设置合理,特别是占位符的背景图片使用了 base64 编码的 SVG,减少了额外的网络请求。
25-58
: 遮罩层效果实现优雅遮罩层实现了良好的交互效果,使用透明度过渡动画提升了用户体验。文本信息样式设置合理,处理了文本溢出的情况。占位符使用了
.box()
混合器保持了代码的简洁性。
59-107
: 预览功能实现全面且交互流畅图片预览部分设计全面,包含了图片缩放、光标样式变化等交互细节。尤其是使用了
scale3d
和 transition 实现平滑的缩放效果,以及针对拖动状态的特殊处理。
158-205
: 切换按钮实现完整且考虑了各种状态左右切换按钮的样式定义全面,包括位置、大小、交互效果和禁用状态。使用固定定位确保了按钮在滚动时仍然可见,增强了用户体验。
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4476 +/- ##
==========================================
- Coverage 53.19% 53.14% -0.06%
==========================================
Files 1665 1665
Lines 102749 102628 -121
Branches 22228 22213 -15
==========================================
- Hits 54654 54537 -117
+ Misses 40012 40008 -4
Partials 8083 8083
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
@@ -50,6 +50,7 @@ | |||
display: flex; | |||
align-items: center; | |||
cursor: pointer; | |||
text-wrap: nowrap; |
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.
这个属性要删了
@image-bg: #f5f5f5; | ||
@image-color: #fff; |
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.
这里的颜色没考虑主题色,可以参考 hisotry 面板用一下背景色
@image-preview-operation-color: @text-color-dark; | ||
@image-preview-operation-disabled-color: fade(@image-preview-operation-color, 25%); |
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.
这里也是,尽量使用 token 颜色替换吧
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.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/ai-native/src/browser/components/mention-input/mention-input.tsx (2)
280-292
: 图片粘贴处理逻辑实现完整且安全该函数正确地实现了从剪贴板检测和提取图片文件的功能,并在找到图片时阻止默认粘贴行为,转而调用提供的
onImageUpload
回调。代码注意事项:
- 代码中使用了 ESLint 禁用注释来允许传统 for 循环,建议考虑使用
for...of
循环以符合项目代码风格- 防御性编程做得很好,确保了
file
存在且onImageUpload
已定义才继续处理您可以考虑将循环改为更现代的
for...of
语法,如下:- // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < items.length; i++) { - if (items[i].kind === 'file' && items[i].type.startsWith('image/')) { - const file = items[i].getAsFile(); + for (const item of Array.from(items)) { + if (item.kind === 'file' && item.type.startsWith('image/')) { + const file = item.getAsFile(); if (file && onImageUpload) { e.preventDefault(); onImageUpload(file); } } }
280-292
: 建议增加针对图片上传功能的单元测试新增的图片上传功能缺少相应的单元测试,为确保功能稳定性和防止未来回归,建议添加测试用例。
测试应覆盖以下场景:
- 粘贴不同格式的图片文件
- 粘贴大尺寸图片
- 粘贴非图片内容时的行为
- 未提供
onImageUpload
回调时的行为需要我提供单元测试示例代码来帮助实现这些测试用例吗?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/ai-native/src/browser/components/mention-input/mention-input.tsx
(4 hunks)packages/components/src/index.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
- GitHub Check: unittest (ubuntu-latest, 18.x, node)
- GitHub Check: build-windows
- GitHub Check: unittest (macos-latest, 18.x, jsdom)
- GitHub Check: build (ubuntu-latest, 20.x)
- GitHub Check: unittest (macos-latest, 18.x, node)
- GitHub Check: build (macos-latest, 20.x)
- GitHub Check: ubuntu-latest, Node.js 20.x
🔇 Additional comments (4)
packages/components/src/index.ts (1)
12-12
: 模块导出新增成功添加代码变更清晰地添加了对
image
模块的导出,符合现有的导出模式,这允许该模块在整个应用中被访问和使用,为新增的图片上传功能提供了必要的基础支持。packages/ai-native/src/browser/components/mention-input/mention-input.tsx (3)
21-21
: 新增onImageUpload
属性以支持图片上传功能通过在
MentionInputProps
接口中添加onImageUpload
属性,组件现在能够接收和处理图片上传的回调函数,这是实现图片上传到聊天功能的关键一步。
920-920
: 成功集成粘贴事件处理器
onPaste
事件处理器已正确绑定到可编辑的 div 元素上,确保了当用户粘贴图片内容时可以触发handlePaste
函数。这完成了图片粘贴功能的事件链接,是功能正常工作的必要条件。
886-886
: 图标渲染灵活性增强修改后的代码通过条件判断同时支持了两种图标显示方式:使用
getIcon(button.icon)
或直接使用button.iconClass
,提高了组件的扩展性和适应性。
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.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts (2)
513-520
: 改进图片上传提供者实现当前实现直接将图片转为base64并返回,但缺少类型验证和详细的错误处理。对于实际生产环境,这种实现可能存在问题:
- 缺少图片类型验证(如确保仅接受jpeg、png等常见图片格式)
- base64编码会使数据大小增加约33%,这对于接近大小限制的图片可能导致意外错误
- 没有完整的错误处理机制(上传失败的详细提示)
建议改进实现如下:
registerImageUploadProvider(registry: IImageUploadProviderRegistry): void { registry.registerImageUploadProvider({ imageUpload: async (file) => { + const validImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + + if (!validImageTypes.includes(file.type)) { + throw new Error('不支持的图片格式,请上传JPG、PNG、GIF或WEBP格式'); + } + + try { const base64 = await imageToBase64(file); return new URL(base64); + } catch (error) { + console.error('图片上传处理失败', error); + throw new Error(`图片上传失败: ${error.message}`); + } }, }); }
523-541
: 改进图片文件处理逻辑当前的
imageToBase64
函数已经实现了基本的大小限制检查,但仍有改进空间:
- 可以添加图片类型验证
- base64编码对于大型图片可能导致性能问题
- 考虑添加进度反馈机制以提升用户体验
建议如下改进:
const MAX_IMAGE_SIZE = 3 * 1024 * 1024; const imageToBase64 = (file: File) => new Promise<string>((resolve, reject) => { if (file.size > MAX_IMAGE_SIZE) { - reject(new Error('Image size exceeds 3MB limit')); + reject(new Error('图片大小超过3MB限制')); return; } + + const validImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + if (!validImageTypes.includes(file.type)) { + reject(new Error('不支持的图片格式,请上传JPG、PNG、GIF或WEBP格式')); + return; + } const reader = new FileReader(); reader.onload = () => { const base64String = reader.result as string; resolve(base64String); }; reader.onerror = () => { - reject(new Error('Failed to convert image to base64')); + reject(new Error('图片转换失败')); }; + // 可选:添加进度反馈 + reader.onprogress = (event) => { + if (event.lengthComputable) { + const progress = Math.round((event.loaded / event.total) * 100); + console.log(`图片转换进度: ${progress}%`); + // 这里可以触发进度更新事件 + } + }; reader.readAsDataURL(file); });长期来看,对于生产环境,应考虑替代base64的解决方案,如使用实际的文件上传服务,避免在聊天消息中嵌入过大的base64数据。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/ai-native/src/browser/components/components.module.less
(1 hunks)packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ai-native/src/browser/components/components.module.less
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
- GitHub Check: build (ubuntu-latest, 20.x)
- GitHub Check: unittest (ubuntu-latest, 18.x, node)
- GitHub Check: build (macos-latest, 20.x)
- GitHub Check: unittest (macos-latest, 18.x, jsdom)
- GitHub Check: unittest (macos-latest, 18.x, node)
- GitHub Check: build-windows
- GitHub Check: ubuntu-latest, Node.js 20.x
🔇 Additional comments (1)
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts (1)
16-16
: 接口导入正确正确导入了
IImageUploadProviderRegistry
接口,用于支持图片上传功能。
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.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/ai-native/src/browser/chat/chat-model.ts (1)
333-341
: 实现了聊天历史中的图片内容支持代码现在可以将图片作为聊天内容的一部分添加到消息历史中,实现方式合理。不过有两个需要注意的地方:
- 代码假设
request.message.images
在检查.length
时始终已定义。如果images
是 undefined,这可能会导致运行时错误。- 直接使用
new URL(image)
创建URL对象可能存在安全风险,如果输入的URL格式不正确可能会抛出异常。建议修改代码以更安全地处理这些情况:
history.push({ role: 'user', - content: request.message.images?.length + content: request.message.images && request.message.images.length > 0 ? [ { type: 'text', text: request.message.prompt }, - ...request.message.images.map((image) => ({ type: 'image', image: new URL(image) } as ImagePart)), + ...request.message.images.map((image) => { + try { + return { type: 'image', image: new URL(image) } as ImagePart + } catch (e) { + console.warn('Invalid image URL:', image); + return null; + } + }).filter(Boolean), ] : request.message.prompt, });packages/ai-native/src/browser/components/ChatMentionInput.tsx (2)
257-274
: 实现了图片上传按钮上传图片的按钮实现得当,但有几点可以优化:
- 每次点击都创建新的input元素不太高效
- 缺少上传过程中的加载状态指示
建议使用React的ref存储input元素并重用,同时添加上传状态指示:
+const fileInputRef = React.useRef<HTMLInputElement>(null); +const [isUploading, setIsUploading] = useState(false); +useEffect(() => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + handleImageUpload(file); + } + }; + fileInputRef.current = input; +}, [handleImageUpload]); // 在按钮配置中 { id: 'upload-image', iconClass: 'codicon codicon-file-media', title: 'Upload Image', - onClick: () => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) { - handleImageUpload(file); - } - }; - input.click(); - }, + onClick: () => fileInputRef.current?.click(), + disabled: isUploading, position: FooterButtonPosition.LEFT, }
346-365
: 图片预览组件需要增强可访问性
ImagePreviewer
组件实现了基本功能,但可访问性方面有待改进。建议增强删除按钮的可访问性属性:
<button onClick={() => onDelete(index)} className={styles.delete_button} + aria-label="删除图片" + title="删除图片" > <Icon iconClass='codicon codicon-close' /> </button>另外,建议为图片添加alt文本以提高可访问性:
-<Image src={image.toString()} /> +<Image + src={image.toString()} + alt={`上传的图片 ${index + 1}`} + onError={() => messageService.error(`无法加载图片 ${index + 1}`)} +/>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/ai-native/src/browser/chat/chat-model.ts
(2 hunks)packages/ai-native/src/browser/components/ChatMentionInput.tsx
(6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: build (ubuntu-latest, 20.x)
- GitHub Check: build-windows
- GitHub Check: build (macos-latest, 20.x)
- GitHub Check: ubuntu-latest, Node.js 20.x
🔇 Additional comments (6)
packages/ai-native/src/browser/chat/chat-model.ts (1)
30-30
: 导入了必要的 ImagePart 类型新增导入的 ImagePart 类型用于支持聊天消息中的图片内容,这是实现图片上传功能的基础。
packages/ai-native/src/browser/components/ChatMentionInput.tsx (5)
1-1
: 添加了处理图片上传所需的导入这些新的导入很好地支持了图片上传和预览功能的实现,包括:
- 从'ai'导入
DataContent
类型- 图片组件的导入
- 消息服务的导入
- 图片上传提供者注册相关的导入
Also applies to: 4-4, 11-11, 16-16, 18-18
24-31
: 正确地更新了接口以支持图片上传功能
IChatMentionInputProps
接口得到了良好的扩展,增加了可选的images
参数到onSend
方法签名中,以及新增了images
属性,使组件能够接收和处理图片数据。Also applies to: 40-40
56-56
: 新增了图片状态和服务注入组件正确地添加了图片状态管理,并注入了必要的消息服务和图片上传提供者注册服务。
Also applies to: 64-65
290-297
: 正确更新了消息发送处理
handleSend
方法现在包含图片数据,并在发送后重置图片状态,符合预期行为。
330-330
: 添加了图片预览组件的条件渲染良好地使用条件渲染,只在有图片时显示预览组件。
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/ai-native/src/browser/components/ChatMentionInput.tsx (1)
300-317
: 🛠️ Refactor suggestion图片上传处理函数缺少文件大小限制
图片上传处理函数中检查了文件类型,但没有检查文件大小限制,可能导致上传过大的图片。虽然在
imageToBase64
函数中有大小限制,但在上传前就应该进行检查,以提供更好的用户体验。建议添加文件大小限制和上传状态管理:
const handleImageUpload = useCallback( async (file: File) => { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; if (!allowedTypes.includes(file.type)) { messageService.error('Only JPG, PNG, WebP and GIF images are supported'); return; } + const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3MB,与 ai-native.contribution.ts 中保持一致 + if (file.size > MAX_FILE_SIZE) { + messageService.error('图片大小超过 3MB 限制'); + return; + } const imageUploadProvider = chatFeatureRegistry.getImageUploadProvider(); if (!imageUploadProvider) { messageService.error('No image upload provider found'); return; } + try { + // 可以添加一个状态来显示加载中 + // setIsUploading(true); const data = await imageUploadProvider.imageUpload(file); setImages([...images, data]); + } catch (error) { + messageService.error(`上传图片失败: ${error.message}`); + } finally { + // setIsUploading(false); + } }, [images], );
🧹 Nitpick comments (1)
packages/ai-native/src/browser/components/ChatMentionInput.tsx (1)
344-363
: 实现了图片预览组件添加了一个新组件来显示上传的图片,并提供删除功能。实现干净简洁,遵循 React 模式。建议考虑添加一些辅助功能,如图片加载状态、失败回退和可访问性支持。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/ai-native/src/browser/chat/chat.feature.registry.ts
(2 hunks)packages/ai-native/src/browser/components/ChatMentionInput.tsx
(7 hunks)packages/ai-native/src/browser/types.ts
(4 hunks)packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: build-windows
- GitHub Check: unittest (macos-latest, 18.x, jsdom)
- GitHub Check: build (macos-latest, 20.x)
- GitHub Check: ubuntu-latest, Node.js 20.x
🔇 Additional comments (17)
packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts (3)
235-237
: 添加了图片上传提供者注册功能该变更在
ChatFeatureRegistry
上添加了图片上传功能支持,并使用了imageToBase64
函数作为上传处理器,实现了基本的图片转换与上传功能。
513-514
: 添加了合理的图片大小限制设置了 3MB 的图片大小限制常量,这有助于防止过大图片导致的性能问题和存储压力。
515-531
: 良好的图片转换实现与错误处理
imageToBase64
函数实现了完整的图片转换流程:
- 检查文件大小是否超过限制
- 使用 FileReader 将文件转换为 base64 字符串
- 适当处理错误情况
代码实现了完善的错误处理,并在转换过程中提供了明确的错误信息。
packages/ai-native/src/browser/chat/chat.feature.registry.ts (3)
5-5
: 导入了必要的图片上传提供者接口添加了
IImageUploadProvider
到导入列表中,用于支持图片上传功能的类型定义。
15-15
: 添加了图片上传提供者的私有成员变量添加了
imageUploadProvider
私有成员变量用于存储注册的图片上传提供者实例。
17-23
: 实现了图片上传提供者的注册与获取方法添加了两个公共方法:
registerImageUploadProvider
: 注册图片上传提供者getImageUploadProvider
: 获取注册的图片上传提供者这两个方法实现简洁明了,与类中其他注册方法保持一致的模式,符合代码库的设计风格。
packages/ai-native/src/browser/types.ts (5)
1-1
: 添加必要的数据类型导入从 'ai' 包导入了
DataContent
类型,用于支持图片上传功能的类型定义。
133-133
: 扩展了聊天功能注册接口在
IChatFeatureRegistry
接口中添加了registerImageUploadProvider
方法,使图片上传提供者可以被注册到聊天功能中。这与ChatFeatureRegistry
类中的实现保持一致。
145-145
: 扩展了用户角色渲染类型在
ChatUserRoleRender
类型中添加了可选的images
属性,使渲染函数能够显示图片。
152-152
: 更新了聊天输入渲染类型的发送方法签名更新了
ChatInputRender
类型的onSend
方法签名,添加了可选的images
参数,使输入组件能够发送图片。
295-297
: 定义了图片上传提供者接口添加了
IImageUploadProvider
接口,定义了imageUpload
方法,该方法接受一个File
并返回一个Promise<DataContent | URL>
。接口定义清晰明确,为图片上传功能提供了良好的类型支持。packages/ai-native/src/browser/components/ChatMentionInput.tsx (6)
24-30
: 更新了聊天输入属性接口在
IChatMentionInputProps
接口中更新了onSend
方法签名,添加了images
参数,并添加了images
属性,使组件能够接收和发送图片。这与types.ts
文件中的变更保持一致。
55-55
: 添加了图片状态管理在组件中添加了图片状态管理,从 props 初始化。这是管理组件状态的良好实践。
255-272
: 添加了图片上传按钮功能添加了用于上传图片的按钮和相关功能,包括:
- 创建文件输入元素
- 设置接受的文件类型为图片
- 处理文件选择事件
- 调用图片上传处理函数
这是一个常见且有效的文件上传模式。
288-296
: 更新了发送方法以包含图片更新了
handleSend
方法,以便在发送消息时包含图片。实现还在发送后清除图片状态,这是防止重复发送相同图片的良好做法。
319-324
: 添加了图片删除功能添加了从预览中删除图片的方法。实现简洁明了,按预期工作。
328-328
: 添加了图片预览组件添加了
ImagePreviewer
组件来显示上传的图片。这是一个好的添加,为用户提供了视觉反馈。
Types
Background or solution
Changelog
feat: support add image to chat
Summary by CodeRabbit
新功能
样式