mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-01 14:19:40 +08:00
fix(server): index generation error handle (#15532)
<!-- Thank you for contributing to AFFiNE! The PR title must follow Conventional Commits (enforced by CI): type(scope): description e.g. fix(editor): keep selection after paste Types: feat fix docs style refactor perf test build ci chore revert --> ## Description <!-- What does this PR do? Link related issues, e.g. "Closes #1234". Screenshots or recordings are welcome for UI changes. --> ## Checklist - [ ] I have signed the [AFFiNE Contributor License Agreement](https://cla-assistant.io/toeverything/AFFiNE) — required before merge; the `license/cla` check must be green ([how it works](https://github.com/toeverything/AFFiNE/blob/canary/docs/BUILDING.md#sign-the-cla-first)) - [ ] The PR targets the `canary` branch and its title follows [Conventional Commits](https://www.conventionalcommits.org/) - [ ] Tests are added or updated where it makes sense - [ ] `yarn lint` and `yarn typecheck` pass locally #### PR Dependency Tree * **PR #15532** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved search reconciliation and publication status handling when workspace reconciliation fails. * Prevented failed workspace reconciliation from incorrectly blocking generation completion. * Preserved active generation state so reconciliation can retry and complete pending publications. * Improved managed provider profile migration, including legacy configurations, unavailable models, conflicting assignments, and missing defaults. * **Tests** * Expanded coverage for workspace recovery and managed provider profile migration scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -143,6 +143,24 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
models: ['@cf/baai/bge-reranker-base'],
|
||||
config: { apiKey: 'profile-key' },
|
||||
},
|
||||
{
|
||||
id: 'fal-default',
|
||||
type: 'fal',
|
||||
priority: 5,
|
||||
config: { apiKey: 'existing-fal-key' },
|
||||
},
|
||||
{
|
||||
id: 'anthropic-default',
|
||||
type: 'anthropic',
|
||||
priority: 2,
|
||||
config: { apiKey: 'existing-anthropic-key' },
|
||||
},
|
||||
{
|
||||
id: 'anthropicVertex-default',
|
||||
type: 'anthropicVertex',
|
||||
priority: 1,
|
||||
config: { projectId: 'existing-anthropic-vertex-project' },
|
||||
},
|
||||
];
|
||||
await t.context.db.appConfig.createMany({
|
||||
data: [
|
||||
@@ -151,6 +169,10 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
id: 'copilot.providers.openai',
|
||||
value: { apiKey: 'openai-key' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.cloudflareWorkersAi',
|
||||
value: { apiKey: 'legacy-cloudflare-key' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.gemini',
|
||||
value: { apiKey: 'gemini-key' },
|
||||
@@ -159,6 +181,18 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
id: 'copilot.providers.geminiVertex',
|
||||
value: { projectId: 'gemini-vertex-project' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.fal',
|
||||
value: { apiKey: 'legacy-fal-key' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.anthropic',
|
||||
value: { apiKey: 'legacy-anthropic-key' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.anthropicVertex',
|
||||
value: { projectId: 'legacy-anthropic-vertex-project' },
|
||||
},
|
||||
{
|
||||
id: 'copilot.providers.defaults',
|
||||
value: { fallback: 'openai-default' },
|
||||
@@ -173,7 +207,20 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
where: { id: 'copilot.providers.profiles' },
|
||||
});
|
||||
t.deepEqual(migrated.value, [
|
||||
...profiles,
|
||||
profiles[0],
|
||||
{
|
||||
...profiles[1],
|
||||
models: ['lora/image-to-image', 'workflowutils/teed'],
|
||||
},
|
||||
{
|
||||
...profiles[2],
|
||||
models: ['claude-sonnet-4-6'],
|
||||
},
|
||||
{
|
||||
...profiles[3],
|
||||
models: ['claude-sonnet-4-6'],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'openai-default',
|
||||
type: 'openai',
|
||||
@@ -181,6 +228,14 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
models: ['gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-image-1', 'gpt-4o-mini'],
|
||||
config: { apiKey: 'openai-key' },
|
||||
},
|
||||
{
|
||||
id: 'cloudflareWorkersAi-default',
|
||||
type: 'cloudflareWorkersAi',
|
||||
priority: 6,
|
||||
models: ['@cf/baai/bge-reranker-base'],
|
||||
config: { apiKey: 'legacy-cloudflare-key' },
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'gemini-default',
|
||||
type: 'gemini',
|
||||
@@ -203,8 +258,12 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
id: {
|
||||
in: [
|
||||
'copilot.providers.openai',
|
||||
'copilot.providers.cloudflareWorkersAi',
|
||||
'copilot.providers.gemini',
|
||||
'copilot.providers.geminiVertex',
|
||||
'copilot.providers.fal',
|
||||
'copilot.providers.anthropic',
|
||||
'copilot.providers.anthropicVertex',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -216,6 +275,38 @@ test('managed provider migration preserves explicit profiles and converts legacy
|
||||
where: { id: 'copilot.providers.defaults' },
|
||||
})
|
||||
);
|
||||
await t.context.db.appConfig.delete({
|
||||
where: { id: 'copilot.providers.defaults' },
|
||||
});
|
||||
|
||||
const defaultOnlyProfiles = profiles.slice(1);
|
||||
await t.context.db.appConfig.update({
|
||||
where: { id: 'copilot.providers.profiles' },
|
||||
data: { value: defaultOnlyProfiles },
|
||||
});
|
||||
await ConvergeManagedProviderProfiles1786810000000.up(t.context.db);
|
||||
t.deepEqual(
|
||||
(
|
||||
await t.context.db.appConfig.findUniqueOrThrow({
|
||||
where: { id: 'copilot.providers.profiles' },
|
||||
})
|
||||
).value,
|
||||
[
|
||||
{
|
||||
...defaultOnlyProfiles[0],
|
||||
models: ['lora/image-to-image', 'workflowutils/teed'],
|
||||
},
|
||||
{
|
||||
...defaultOnlyProfiles[1],
|
||||
models: ['claude-sonnet-4-6'],
|
||||
},
|
||||
{
|
||||
...defaultOnlyProfiles[2],
|
||||
models: ['claude-sonnet-4-6'],
|
||||
enabled: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
await t.context.db.appConfig.update({
|
||||
where: { id: 'copilot.providers.profiles' },
|
||||
|
||||
+58
-24
@@ -13,6 +13,9 @@ const PROVIDERS = [
|
||||
] as const;
|
||||
|
||||
const PROVIDER_IDS = PROVIDERS.map(provider => `copilot.providers.${provider}`);
|
||||
const DEFAULT_PROFILE_IDS = new Set(
|
||||
PROVIDERS.map(provider => `${provider}-default`)
|
||||
);
|
||||
const PROVIDER_MODELS: Record<(typeof PROVIDERS)[number], string[]> = {
|
||||
openai: ['gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-image-1', 'gpt-4o-mini'],
|
||||
cloudflareWorkersAi: ['@cf/baai/bge-reranker-base'],
|
||||
@@ -60,16 +63,11 @@ export class ConvergeManagedProviderProfiles1786810000000 {
|
||||
const byId = new Map(rows.map(row => [row.id, row]));
|
||||
const profileRow = byId.get(PROFILE_KEY);
|
||||
const profiles = readProfiles(profileRow?.value);
|
||||
const profileIds = new Set(
|
||||
profiles.flatMap(profile =>
|
||||
isRecord(profile) && typeof profile.id === 'string'
|
||||
? [profile.id]
|
||||
: []
|
||||
)
|
||||
);
|
||||
const assignedModels = new Set(
|
||||
profiles.flatMap(profile =>
|
||||
isRecord(profile) &&
|
||||
typeof profile.id === 'string' &&
|
||||
!DEFAULT_PROFILE_IDS.has(profile.id) &&
|
||||
profile.enabled !== false &&
|
||||
Array.isArray(profile.models)
|
||||
? profile.models.filter(
|
||||
@@ -78,33 +76,69 @@ export class ConvergeManagedProviderProfiles1786810000000 {
|
||||
: []
|
||||
)
|
||||
);
|
||||
let converged = false;
|
||||
|
||||
for (const [index, provider] of PROVIDERS.entries()) {
|
||||
const legacy = byId.get(`copilot.providers.${provider}`);
|
||||
if (!legacy) continue;
|
||||
if (!isRecord(legacy.value)) {
|
||||
if (legacy && !isRecord(legacy.value)) {
|
||||
throw new Error(`copilot.providers.${provider} must be an object`);
|
||||
}
|
||||
const id = `${provider}-default`;
|
||||
if (!profileIds.has(id)) {
|
||||
const models = PROVIDER_MODELS[provider].filter(
|
||||
model => !assignedModels.has(model)
|
||||
);
|
||||
const enabled = models.length > 0;
|
||||
profiles.push({
|
||||
id,
|
||||
type: provider,
|
||||
priority: PROVIDERS.length - index,
|
||||
models: enabled ? models : PROVIDER_MODELS[provider],
|
||||
config: legacy.value,
|
||||
...(enabled ? {} : { enabled: false }),
|
||||
const profileIndex = profiles.findIndex(
|
||||
profile => isRecord(profile) && profile.id === id
|
||||
);
|
||||
const existing =
|
||||
profileIndex === -1 ? undefined : profiles[profileIndex];
|
||||
if (!legacy && !existing) continue;
|
||||
converged = true;
|
||||
|
||||
const configuredModels =
|
||||
isRecord(existing) &&
|
||||
Array.isArray(existing.models) &&
|
||||
existing.models.length > 0
|
||||
? existing.models
|
||||
: PROVIDER_MODELS[provider];
|
||||
const canEnable =
|
||||
!isRecord(existing) ||
|
||||
existing.enabled === undefined ||
|
||||
existing.enabled === true;
|
||||
const modelsAreValid = configuredModels.every(
|
||||
model => typeof model === 'string'
|
||||
);
|
||||
const availableModels =
|
||||
canEnable && modelsAreValid
|
||||
? configuredModels.filter(model => !assignedModels.has(model))
|
||||
: configuredModels;
|
||||
const hasConflict = canEnable && availableModels.length === 0;
|
||||
const models = hasConflict ? configuredModels : availableModels;
|
||||
const profile: Prisma.JsonObject = {
|
||||
...(legacy
|
||||
? {
|
||||
id,
|
||||
type: provider,
|
||||
priority: PROVIDERS.length - index,
|
||||
config: legacy.value,
|
||||
}
|
||||
: {}),
|
||||
...(isRecord(existing) ? existing : {}),
|
||||
id,
|
||||
type: provider,
|
||||
models,
|
||||
...(hasConflict ? { enabled: false } : {}),
|
||||
};
|
||||
if (profileIndex === -1) {
|
||||
profiles.push(profile);
|
||||
} else {
|
||||
profiles[profileIndex] = profile;
|
||||
}
|
||||
if (canEnable && !hasConflict) {
|
||||
models.forEach(model => {
|
||||
if (typeof model === 'string') assignedModels.add(model);
|
||||
});
|
||||
models.forEach(model => assignedModels.add(model));
|
||||
profileIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (PROVIDER_IDS.some(id => byId.has(id))) {
|
||||
if (converged) {
|
||||
validateProfiles(profiles);
|
||||
await tx.appConfig.upsert({
|
||||
where: { id: PROFILE_KEY },
|
||||
|
||||
Reference in New Issue
Block a user