feat(core): improve byok editing (#15427)

fix #14287
fix #15359
fix #15424

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Redesigned workspace AI provider settings with connection testing,
storage options, model selection, capability management, ordering, and
custom endpoints.
* AI chat model choices now adapt to the selected workspace and
conversation route.
  * Added support for image-based AI requests.
* **Bug Fixes**
  * Improved handling of unavailable or outdated model selections.
* App configuration updates now reject overlapping paths and load
deterministically.
* **Tests**
* Expanded coverage for provider models, AI chat scoping, image
requests, and configuration validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-08-05 19:26:19 +08:00
committed by GitHub
parent 965f4590ff
commit 543667d9b3
57 changed files with 2951 additions and 1472 deletions

View File

@@ -9,11 +9,34 @@ export class AppConfigModel extends BaseModel {
async load(excludedKeys: string[] = []) {
return this.db.appConfig.findMany({
where: excludedKeys.length ? { id: { notIn: excludedKeys } } : undefined,
orderBy: { id: 'asc' },
});
}
@Transactional()
async save(user: string, updates: Array<{ key: string; value: any }>) {
await this.db
.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${'app-config-paths'}, 0))`;
const existing = await this.db.appConfig.findMany({
select: { id: true },
});
const updateKeys = updates.map(update => update.key);
for (const [index, key] of updateKeys.entries()) {
const overlappingKey = [
...existing.map(config => config.id),
...updateKeys.slice(0, index),
].find(
candidate =>
candidate !== key &&
(candidate.startsWith(`${key}.`) || key.startsWith(`${candidate}.`))
);
if (overlappingKey) {
throw new Error(
`App config paths must not overlap: ${overlappingKey} and ${key}`
);
}
}
return await Promise.allSettled(
updates.map(async update => {
return this.db.appConfig.upsert({