diff --git a/UI-DESIGN.md b/UI-DESIGN.md index e0d62e90..0ae352ee 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -210,6 +210,8 @@ - 选项数量为 2 至 4 个。 - 使用带 `aria-pressed` 的按钮组语义,支持方向键切换。 - 不能用于多选筛选、页面导航或执行即时命令。 +- 与 `PageTabs` 一样,共享控件根节点必须 `flex: 0 0 auto`,不能被同级长表单或滚动区域 + 压缩高度。横向空间不足时保留控件自身滚动,不把标签裁成一条窄缝。 - 控件宽度由内容决定,除移动窄屏外不默认等分整行。 ### 6.3 筛选工具栏 @@ -395,6 +397,10 @@ 顶栏,不能保留一条未被遮罩的窗口控制色块。关闭操作由 Modal 自身提供。 - 整窗固定遮罩通过 `createPortal(..., document.body)` 挂载,避免页面层叠上下文影响; 保留 React Context、关闭与焦点管理,表单样式不得依赖已移出的页面祖先选择器。 +- 原生浏览器 `WebContentsView` 不受网页 `z-index` 控制。应用级 Modal 打开时统一释放 + 浏览器 viewport;非模态对话框、菜单、局部确认或应用通知与浏览器区域相交时也释放。 + 浮层关闭后只恢复当前活动实例的 viewport,不关闭、重建或刷新网页。接线与测试见 + [浏览器多实例技术设计](./docs/features/assistant-workbar/browser-tabs-technical-design.md#4-renderer-与-viewport)。 - 遮罩必须 `inset: 0` 铺满整窗,并由 `styles.css` 中统一的遮罩选择器声明 `-webkit-app-region: no-drag`,否则 `.brand` 与 `.topbar` 的原生拖动区域会截获指针, 使遮罩上的控件无法悬停和点击。新增整窗遮罩时扩展该共享选择器,不要在各自规则里复制 diff --git a/docs/features/assistant-workbar/browser-tabs-technical-design.md b/docs/features/assistant-workbar/browser-tabs-technical-design.md index 06dac33f..0952de51 100644 --- a/docs/features/assistant-workbar/browser-tabs-technical-design.md +++ b/docs/features/assistant-workbar/browser-tabs-technical-design.md @@ -77,8 +77,16 @@ Record> 非活动 Browser Tab 保持页面和状态,但隐藏 `WebContentsView` 并停止非必要画面采集。工作栏 收起、切换主页面和窗口隐藏时采用相同规则,不销毁 Tab。 普通导航、快照与页面操作只同步轻量导航状态,不自动截图、等待画面或传输 JPEG;只有显式 -`browser_screenshot` 调用生成图片。侧栏内终端关闭确认显示期间,同样隐藏原生浏览器视图, -确认或取消后恢复当前实例,避免原生子视图遮挡 HTML 确认按钮。 +`browser_screenshot` 调用生成图片。 + +`BrowserViewport` 统一观察 body Portal 的插入、移除和可见属性变化,以及浮层尺寸、 +窗口尺寸、滚动和过渡结束。`browser-viewport-occlusion.ts` 按共享语义识别 +`aria-modal="true"`、dialog、alertdialog、menu 和应用通知:可见应用级 Modal 始终释放 +viewport,其余浮层只在与浏览器区域相交时释放。隐藏节点和隐藏祖先不阻塞浏览器。 +最后一个遮挡消失后,只有当前活动实例恢复同一 viewport 租约;网页、历史和页面状态不变。 +终端关闭确认复用此机制,不再维护单独的隐藏条件。页面无需逐个登记弹窗,也不能靠提高 +CSS `z-index` 覆盖原生子视图。此逻辑仅涉及 Desktop Renderer 的显示租约,不改变 +Runtime、MCP 或远端 Agent 的请求、导航与生命周期。 浏览器应用声明 `visibleAcrossContextSwitches: true`,打开的 Tab 跨项目、会话切换继续显示。 切换活动 Conversation 不改变当前选中的浏览器或其 `targetRef.conversationId`;用户在会话 B diff --git a/docs/features/assistant-workbar/progress.md b/docs/features/assistant-workbar/progress.md index b40aa7a4..9c4a7c4c 100644 --- a/docs/features/assistant-workbar/progress.md +++ b/docs/features/assistant-workbar/progress.md @@ -1,5 +1,38 @@ # 工作栏实现与验证进度 +## 2026-09-13 原生浏览器与应用浮层修复 + +- 用户在“添加实例”中复现原生网页盖住弹窗。原先只有终端关闭确认释放 viewport, + 现在所有应用 Modal 与相交浮层共用 + [浏览器遮挡机制](./browser-tabs-technical-design.md#4-renderer-与-viewport)。 + 新增五个组件回归在修复前全部失败,修复后通过;独立检测器另有十项回归。 +- Windows 当前 Main、Preload、Renderer,通过 `electron-vite dev` 和独立 profile + 启动完整 App,不是旧安装包或仅有 Renderer 的 fixture。浏览器导航到本机合成页后, + 实例管理与项目设置均验证原生视图 `visible → hidden → visible`;视图 ID 不变且未销毁。 + 本次不修改 Runtime、Agent、MCP 或远程生产路径。 +- 实例编辑器在浅深主题、目标 1280×800、960×720、720×640、640×420 下完成八组 + 几何、关闭按钮命中和原生视图隐藏断言。Windows 缩放产生不足 1 CSS px 的取整差异, + 记录实际尺寸而非把目标尺寸当测量值。PNG 实际审阅包括浅色 1280、720 和深色 640; + 其余尺寸执行程序化检查,不将其描述为全部 PNG 人工审阅。 +- 临时驱动曾出现新浏览器 target 抢占自动化目标、隐藏窗口截图阻塞及严格像素比较 + 误判;修正驱动后重新取证,不把这些失败改写为产品修复。最终使用 pinned renderer、 + 独立测试窗口和 CDP PNG;原生视图状态单独从 Main 读取,避免网页截图漏掉原生视图。 +- 顺带复现已发布更新说明资源为 136,554 字节,超过原 128 KiB 读取上限。上限提高到 + 1 MiB,仍使用有界读取;回归直接读取实际打包资源并确认当前版本可展示、可确认。 + 完整 App 已显示并关闭 0.13.0 更新说明,重启后不再次显示。 +- 首轮 `npm test -- --maxWorkers=1`:4,108 通过、66 跳过,608.30 秒; + `npm run typecheck`、`npm run lint` 通过。补长名称修正后的全量执行为 4,105 通过、 + 66 跳过、3 失败,586.51 秒;失败全部位于并行修改中的子代理进度测试。 + 未修改这些文件,随后定向重跑 ACP 与 OpenCode 子代理共 66 项全部通过。 + 全仓类型与 lint 检查期间先后遇到并行子代理/存储升级改动的缺失字段和未使用导入, + 这些改动不纳入本次提交,不能把早先的通过结果当作当前并行工作区全绿。 +- 两组 Harness 单测补 `onTestFinished` 回收临时目录。最后定向四文件 73 项通过, + 运行前后检查新增残留目录为 0。测试清理不改变 deployed Runtime。 +- 本次修改文档的 66 个相对链接目标存在,暂存差异无空白错误;最终发布仍需对确定的、 + 不再变动的候选提交执行发布验证,不能将未提交的并行改动混入候选。 +- 临时脚本、截图和隔离构建按用户要求在提交后清理;这里只保留脱敏结论。知识库布局、 + 合成服务保存验证与调用计数见[知识库进度](../knowledge-base/progress.md#2026-09-13-知识库布局与自动名称修复)。 + ## 2026-09-13 OpenCode 事件流关闭顺序修复 候选检查中的 lifecycle 失败已稳定复现,4 路并行后 `openEventBodies` 为 3 而不是 0。 diff --git a/docs/features/knowledge-base/external-knowledge-logic-design.md b/docs/features/knowledge-base/external-knowledge-logic-design.md index b70b4c5c..3433e663 100644 --- a/docs/features/knowledge-base/external-knowledge-logic-design.md +++ b/docs/features/knowledge-base/external-knowledge-logic-design.md @@ -68,6 +68,10 @@ Renderer 丢弃已失效的目录、详情和测试响应,不把切换前的 ## 5. 创建与编辑 +新建绑定时,用户尚未编辑的显示名称跟随当前目录选择更新;更换实例或手工输入另一个 +目标 ID 时清除旧自动名称。用户手动填写过名称后保留该草稿。编辑或重新绑定已有库时, +始终保留已保存的本地名称,除非用户主动修改;此修正不自动重命名已有知识库。 + 流程为选择 `local | dify | fastgpt | ragflow`,外部类型再选择实例、目录项或手工 ID、 显示名称、配置与测试查询。表单测试成功后允许保存;服务保存前再次执行同一查询验证, 检查实例、绑定和唯一性是否在等待期间改变,再写入数据库。 diff --git a/docs/features/knowledge-base/external-knowledge-ui-design.md b/docs/features/knowledge-base/external-knowledge-ui-design.md index 4cb322cc..fc9cd84a 100644 --- a/docs/features/knowledge-base/external-knowledge-ui-design.md +++ b/docs/features/knowledge-base/external-knowledge-ui-design.md @@ -31,6 +31,12 @@ | 检索结果 | 外部组件显示总耗时、返回数量、片段、定位、实际 ID 和原始评分,包括 FastGPT 评分数组;不显示本地全文、向量、图谱通道或本地上下文操作 | | 聊天与引用 | 统一选择器显示 Provider、实例与状态;外部引用打开保存片段,显示实例 ID 和远端定位,不请求本地上下文或打开原始来源 | +创建类型分段控件复用全局不收缩规则,不能被目录和参数表单压扁;列表长名称的宽度约束见 +[知识库 UI 设计](./ui-design.md#2-信息架构)。新建时自动名称跟随目录选择,手动改名和已保存 +名称不被覆盖,具体规则见[逻辑设计](./external-knowledge-logic-design.md#5-创建与编辑)。 +实例 Modal 和其他浮层统一参与[原生浏览器遮挡处理](../assistant-workbar/browser-tabs-technical-design.md#4-renderer-与-viewport), +不再仅验证没有浏览器的独立 Portal。 + 原 `knowledge-field` 的控件样式依赖知识库工作区祖先,实例 Modal 经 `createPortal` 挂到 body 后丢失该作用域。当前 `Field` 使用全局 `.field` 和标签 span,密码也复用同一字段 结构,确保 Portal 内的字体、输入边框、背景和焦点样式与应用一致。弹窗宽度为 diff --git a/docs/features/knowledge-base/progress.md b/docs/features/knowledge-base/progress.md index 5cb7e560..757b4427 100644 --- a/docs/features/knowledge-base/progress.md +++ b/docs/features/knowledge-base/progress.md @@ -16,6 +16,26 @@ ## 验证证据 +### 2026-09-13 知识库布局与自动名称修复 + +- 创建类型的共享分段控件被长表单压缩,修复共享不收缩规则,不添加页面专属高度。 + 长名称卡片增加零最小宽度的 Grid 轨道,名称单行省略、图标保持 15px;详情名称允许换行。 + 合成长名称实测列表横向溢出为 0;详情修复前溢出 658px,修复后为 0。 +- 连续选择两个目录项时,未修改过的自动名称原先停留在第一个目标。回归先失败再修复, + 现在跟随选择,手动名称和已保存名称不自动覆盖。规则见 + [创建与编辑](./external-knowledge-logic-design.md#5-创建与编辑)。 +- 当前完整 App 的真实 UI → Preload → Main → SQLite 路径完成实例保存、两个目录项 + 切换、零结果检索后添加绑定、详情展示和配置草稿保留。上游是 loopback Dify 合成服务, + 不是真实知识平台;没有调用模型,也不能据此声称真实检索质量或远端验收。 +- 本轮合成服务共收到 8 次请求:1 次初始目录、2 次初始详情、2 次检索(测试与保存)、 + 2 次后续详情、1 次后续目录;真实模型和真实知识服务请求均为 0。 +- 创建类型在浅深主题、目标 1280×800、960×720、720×640、640×420 的八组尺寸中 + 检查控件高度至少 34px,并保存 PNG。实际审阅浅色 1280、深色 640 及最终添加后的 + 长名称详情 PNG,确认 Tab 不再成为窄缝,卡片边框和图标完整,详情无横向滚动条。 +- 同期修复和通用验证结果见[工作栏进度](../assistant-workbar/progress.md#2026-09-13-原生浏览器与应用浮层修复)。 + 临时脚本与合成 profile 在提交后清理,不把它们作为长期依赖;用户在另一测试窗口 + 手工创建的实例与绑定不属于本轮合成数据,不随清理删除。 + ### 2026-09-13 检索路径与绑定查询简化 对 `3b560c9..ca4a58c` 的复查发现三处与本功能相关的实现问题,已按 KISS 与性能优先修正: diff --git a/docs/features/knowledge-base/ui-design.md b/docs/features/knowledge-base/ui-design.md index 8fb1ad0d..8a3284e1 100644 --- a/docs/features/knowledge-base/ui-design.md +++ b/docs/features/knowledge-base/ui-design.md @@ -25,6 +25,11 @@ 标题区提供“用于当前对话”“检索测试”、编辑和删除。新建知识库是页面主操作。 “高级设置”承载问答效果入口、向量索引、分块与图谱配置,不把工程参数放在首次路径。 +本地与外部库共用的列表以 `minmax(0, 1fr)` 限制条目宽度,再对名称单行省略;选中卡片 +的边框必须完整留在列表内,Provider/实例等辅助文字允许换行。不得用列表外层裁切 +掩盖被长名称撑宽的卡片,库图标不能随名称一起收缩。外部详情的长名称和远端名称允许 +换行,不能撑出横向滚动条。创建类型分段控件与详情页签保持固定内容高度,不随长表单收缩。 + ## 3. 首次使用 没有知识库时,页面空状态显示“新建知识库”主操作。创建时先选择类型;本地类型默认 diff --git a/docs/quality/recurring-defects.md b/docs/quality/recurring-defects.md index 6632d964..063cdb9f 100644 --- a/docs/quality/recurring-defects.md +++ b/docs/quality/recurring-defects.md @@ -26,6 +26,37 @@ **历史**:`e9679fe`、`ca4a58c` 各修了一部分被人工点到的弹窗,同一批改动又新增了两个未覆盖 的遮罩;后续复查改为共享规则加自动发现测试。 +## 网页浮层遗漏原生浏览器视图 + +**症状**:知识库实例、项目设置等 Modal 的右侧被浏览器网页覆盖,通知或菜单也可能 +被压在原生视图下面。 + +**原因**:`WebContentsView` 不参与 Renderer 的 CSS 层叠。先前只在关闭终端确认时 +显式隐藏 viewport,没有覆盖其他页面的 Portal 和浮层。 + +**规则**:`BrowserViewport` 共用 `browser-viewport-occlusion.ts`,按 Modal 语义和 +浮层相交状态释放显示租约,不销毁网页会话。不得再增加逐个弹窗的特殊隐藏条件。 + +**防复发**:组件回归覆盖 body Modal、嵌套 Modal、已打开 Modal、隐藏或非相交浮层、 +通知、终端确认和非活动页签;实际 Electron 验证同时检查原生视图可见性,不能只看 DOM +的遮罩或截图。 + +**历史**:先前浏览器多实例修正只覆盖了终端关闭确认;2026-09-13 用户在知识库实例 +Modal 再次复现,同一检查确认其他应用级 Modal 均缺少处理。 + +## Flex 滚动内容压缩页签和分段控件 + +**症状**:知识库来源选择被长参数表单压成窄缝,标签和按钮文字被裁掉。 + +**规则**:`PageTabs` 与 `SegmentedControl` 在共享样式上固定 `flex: 0 0 auto`; +直接包装层也必须保留高度。不要按页面复制高度常量。 + +**防复发**:`WorkspacePrimitives.test.tsx` 检查共享分段控件不收缩,已有平台功能、 +能力和 MCP 页签回归继续保留;Electron 复验必须包含长表单与短窗口。 + +**历史**:设置页签曾补过包装层不收缩规则;2026-09-13 知识库创建类型的共享 +`SegmentedControl` 又出现同类问题。 + ## 逐行查询代替主键查询 **症状**:列表渲染或校验函数随数据量线性变慢,且慢在单次操作内重复发生。 diff --git a/src/main/agent/deepseek-harness-utility-launcher.test.ts b/src/main/agent/deepseek-harness-utility-launcher.test.ts index 51942cf7..146ef4a0 100644 --- a/src/main/agent/deepseek-harness-utility-launcher.test.ts +++ b/src/main/agent/deepseek-harness-utility-launcher.test.ts @@ -1,9 +1,9 @@ import { EventEmitter } from 'node:events' -import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { PassThrough } from 'node:stream' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' import { DEEPSEEK_HARNESS_CONTROL_PROTOCOL, DEEPSEEK_HARNESS_CONTROL_VERSION, @@ -33,6 +33,7 @@ async function fixture() { const root = await realpath( await mkdtemp(join(tmpdir(), 'goodbuddy-harness-launcher-')) ) + onTestFinished(() => rm(root, { recursive: true, force: true })) const workspace = join(root, 'workspace') const dshHome = join(root, 'home') const hostPath = join( diff --git a/src/main/deepseek-harness-host.test.ts b/src/main/deepseek-harness-host.test.ts index a9ae9ef0..abc47aae 100644 --- a/src/main/deepseek-harness-host.test.ts +++ b/src/main/deepseek-harness-host.test.ts @@ -1,11 +1,11 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, onTestFinished } from 'vitest' import { createBoundedNdJsonStream, installHarnessDiagnosticGuard, startControlledDeepSeekHarnessHost } from './deepseek-harness-host' import { vi } from 'vitest' -import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises' import type { Agent, CreateAgentOptions @@ -65,6 +65,7 @@ describe('controlled DeepSeek Harness host', () => { const root = await realpath( await mkdtemp(join(tmpdir(), 'goodbuddy-harness-host-')) ) + onTestFinished(() => rm(root, { recursive: true, force: true })) const inbound = new TransformStream< Record, Record @@ -125,6 +126,7 @@ describe('controlled DeepSeek Harness host', () => { const root = await realpath( await mkdtemp(join(tmpdir(), 'goodbuddy-harness-alias-')) ) + onTestFinished(() => rm(root, { recursive: true, force: true })) const alias = join(root, '..', basename(root)) const inbound = new TransformStream< Record, @@ -157,6 +159,7 @@ describe('controlled DeepSeek Harness host', () => { const root = await realpath( await mkdtemp(join(tmpdir(), 'goodbuddy-harness-extensions-')) ) + onTestFinished(() => rm(root, { recursive: true, force: true })) const brokenEntrypoint = join(root, 'broken.mjs') await writeFile( brokenEntrypoint, @@ -202,6 +205,7 @@ describe('controlled DeepSeek Harness host', () => { const root = await realpath( await mkdtemp(join(tmpdir(), 'goodbuddy-harness-skill-')) ) + onTestFinished(() => rm(root, { recursive: true, force: true })) const skillDirectory = join(root, 'web-3d-game') await mkdir(skillDirectory) await writeFile( diff --git a/src/main/release-notes-service.test.ts b/src/main/release-notes-service.test.ts index 2bddb08d..b232932a 100644 --- a/src/main/release-notes-service.test.ts +++ b/src/main/release-notes-service.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, rm, writeFile } from 'node:fs/promises' +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' @@ -70,6 +70,22 @@ afterEach(async () => { }) describe('ReleaseNotesService', () => { + it('reads and acknowledges the actual packaged release history', async () => { + const { version } = JSON.parse( + await readFile(join(process.cwd(), 'package.json'), 'utf8') + ) as { version: string } + const { filePath, service } = await createService(version) + await writeFile( + filePath, + await readFile(join(process.cwd(), 'resources', 'release-notes.json')) + ) + await expect(service.getPending()).resolves.toMatchObject({ + currentVersion: version, + releases: [{ version }] + }) + await service.acknowledge({ version }) + await expect(service.getPending()).resolves.toMatchObject({ releases: [] }) + }) it('shows only the current release on a fresh installation', async () => { const { service } = await createService('0.8.18') @@ -135,7 +151,7 @@ describe('ReleaseNotesService', () => { it('rejects an oversized release-notes resource with a bounded read', async () => { const { filePath, service } = await createService('0.8.18') - await writeFile(filePath, ' '.repeat(128 * 1024 + 1), 'utf8') + await writeFile(filePath, ' '.repeat(1024 * 1024 + 1), 'utf8') await expect(service.getPending()).rejects.toThrow( 'Release notes exceed the size limit' diff --git a/src/main/release-notes-service.ts b/src/main/release-notes-service.ts index f9c383fa..49f082f4 100644 --- a/src/main/release-notes-service.ts +++ b/src/main/release-notes-service.ts @@ -8,7 +8,7 @@ import { import type { ApplicationSettingsStore } from './application-settings-store' import { compareStrictSemVer } from './version-checker' -const maximumReleaseNotesBytes = 128 * 1024 +const maximumReleaseNotesBytes = 1024 * 1024 export class ReleaseNotesService { private releases?: ReleaseNote[] diff --git a/src/renderer/src/ExternalKnowledge.test.tsx b/src/renderer/src/ExternalKnowledge.test.tsx index 681b69c9..496c2ba5 100644 --- a/src/renderer/src/ExternalKnowledge.test.tsx +++ b/src/renderer/src/ExternalKnowledge.test.tsx @@ -32,6 +32,21 @@ function selectInstance(index = 0): void { } describe('external knowledge workflows', () => { + it('updates an automatic name when selecting another library but preserves an edited name', async () => { + bridge.externalCatalogList.mockResolvedValueOnce({ + items: [{ id: 'remote-1', name: 'Handbook' }, { id: 'remote-2', name: 'Second library' }], + hasMore: false + }) + render() + selectInstance() + fireEvent.click(await screen.findByRole('button', { name: /Handbook/ })) + expect(screen.getByLabelText('Name')).toHaveValue('Handbook') + fireEvent.click(screen.getByRole('button', { name: /Second library/ })) + expect(screen.getByLabelText('Name')).toHaveValue('Second library') + fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'My chosen name' } }) + fireEvent.click(screen.getByRole('button', { name: /Handbook/ })) + expect(screen.getByLabelText('Name')).toHaveValue('My chosen name') + }) it('navigates the rich instance menu, skips disabled items and restores focus', async () => { render() const trigger = screen.getByRole('button', { name: 'External instance' }) @@ -97,6 +112,7 @@ describe('external knowledge workflows', () => { it('keeps external tabs and filters from shrinking in the scrolling workspace', () => { const css = readFileSync('src/renderer/src/external-knowledge.css', 'utf8') expect(css).toMatch(/\.external-knowledge > \.page-tabs\s*\{[^}]*flex: 0 0 auto/) + expect(css).toMatch(/\.external-knowledge > header\s*\{[^}]*overflow-wrap: anywhere/) expect(css).toMatch(/\.external-knowledge__filters\s*\{[^}]*flex: 0 0 auto/) expect(css).toMatch(/\.external-knowledge-modal__surface\s*\{[^}]*width: min\(560px, 100%\)/) expect(css).toMatch(/\.external-knowledge__footer\s*\{[^}]*position: sticky/) diff --git a/src/renderer/src/ExternalKnowledge.tsx b/src/renderer/src/ExternalKnowledge.tsx index cc605028..015a59c5 100644 --- a/src/renderer/src/ExternalKnowledge.tsx +++ b/src/renderer/src/ExternalKnowledge.tsx @@ -187,6 +187,7 @@ export function ExternalBindingForm({ provider, instances, libraries, library, o const [instanceId, setInstanceId] = useState(binding?.instanceId ?? '') const [remoteId, setRemoteId] = useState(binding?.remoteKnowledgeBaseId ?? '') const [name, setName] = useState(library?.name ?? '') + const [nameEdited, setNameEdited] = useState(Boolean(library)) const [detail, setDetail] = useState() const [common, setCommon] = useState(binding?.commonConfig ?? commonDefaults) const [config, setConfig] = useState(binding?.providerConfig ?? externalProviderDefaults(provider)) @@ -265,10 +266,10 @@ export function ExternalBindingForm({ provider, instances, libraries, library, o

{t('external.source')}

- item.provider === provider)} value={instanceId} onChange={value => { invalidate(); setDetail(undefined); setInstanceId(value); setRemoteId(''); setConfig(externalProviderDefaults(provider)); setPage(1); setParentId(null); setSearch('') }} /> + item.provider === provider)} value={instanceId} onChange={value => { invalidate(); setDetail(undefined); setInstanceId(value); setRemoteId(''); if (!nameEdited) setName(''); setConfig(externalProviderDefaults(provider)); setPage(1); setParentId(null); setSearch('') }} /> setManual(value === 'manual')} /> - {manual ? { invalidate(); setDetail(undefined); setRemoteId(event.currentTarget.value) }} /> : <> + {manual ? { invalidate(); setDetail(undefined); setRemoteId(event.currentTarget.value); if (!nameEdited) setName('') }} /> : <> { setSearch(event.currentTarget.value); setPage(1) }} />
{parentId && }
@@ -280,7 +281,7 @@ export function ExternalBindingForm({ provider, instances, libraries, library, o const bound = libraries.some(candidate => candidate.id !== library?.id && candidate.external?.instanceId === instanceId && candidate.external.remoteKnowledgeBaseId === item.id) return })}
{page}
@@ -288,7 +289,7 @@ export function ExternalBindingForm({ provider, instances, libraries, library, o } {remoteId &&

{remoteName}
{remoteId}

} {duplicate && } - setName(event.currentTarget.value)} /> + { setName(event.currentTarget.value); setNameEdited(true) }} />