merge: isolate concurrent OpenCode chat runs

This commit is contained in:
2026-08-17 22:28:19 +08:00
34 changed files with 5159 additions and 434 deletions

View File

@@ -202,6 +202,7 @@ export function ProvidersSettings() {
const [editingProvider, setEditingProvider] = useState<string | null>(null);
const [syncingUserModels, setSyncingUserModels] = useState(false);
const [autoUserModelSyncAttempted, setAutoUserModelSyncAttempted] = useState(false);
const [runtimeRefreshRequired, setRuntimeRefreshRequired] = useState(false);
const vendorMap = new Map(vendors.map((vendor) => [vendor.id, vendor]));
const existingVendorIds = new Set(accounts.map((account) => account.vendorId));
const displayProviders = useMemo(
@@ -214,7 +215,10 @@ export function ProvidersSettings() {
refreshProviderSnapshot();
}, [refreshProviderSnapshot]);
const handleImportUserModelConfig = useCallback(async (options: { silent?: boolean } = {}) => {
const handleImportUserModelConfig = useCallback(async (options: {
silent?: boolean;
runtimeRefresh: 'apply' | 'defer';
}) => {
if (!accessToken) {
if (!options.silent) {
toast.error(t('aiProviders.toast.loginRequiredForSync', 'Sign in before syncing Makelore models.'));
@@ -224,7 +228,12 @@ export function ProvidersSettings() {
setSyncingUserModels(true);
try {
await importUserModelConfig(accessToken);
const result = await importUserModelConfig(accessToken, {
runtimeRefresh: options.runtimeRefresh,
});
setRuntimeRefreshRequired(
options.runtimeRefresh === 'defer' && result.runtimeRefreshRequired,
);
if (!options.silent) {
toast.success(t('aiProviders.toast.syncedUserModels', 'Synced Makelore models'));
}
@@ -244,7 +253,7 @@ export function ProvidersSettings() {
return;
}
setAutoUserModelSyncAttempted(true);
void handleImportUserModelConfig({ silent: true });
void handleImportUserModelConfig({ silent: true, runtimeRefresh: 'defer' });
}, [
accessToken,
autoUserModelSyncAttempted,
@@ -324,7 +333,7 @@ export function ProvidersSettings() {
<Button
data-testid="providers-sync-user-models-button"
variant="outline"
onClick={() => void handleImportUserModelConfig()}
onClick={() => void handleImportUserModelConfig({ runtimeRefresh: 'apply' })}
disabled={!accessToken || syncingUserModels}
className="rounded-full px-4 h-9 shadow-none font-medium text-meta"
>
@@ -333,7 +342,7 @@ export function ProvidersSettings() {
) : (
<RefreshCw className="h-4 w-4 mr-2" />
)}
{t('aiProviders.syncUserModels', 'Sync Makelore models')}
{t('aiProviders.syncUserModels', 'Sync and apply Makelore models')}
</Button>
<Button data-testid="providers-add-button" onClick={() => setShowAddDialog(true)} className="rounded-full px-5 h-9 shadow-none font-medium text-meta">
<Plus className="h-4 w-4 mr-2" />
@@ -342,6 +351,18 @@ export function ProvidersSettings() {
</div>
</div>
{runtimeRefreshRequired ? (
<p
data-testid="providers-runtime-refresh-pending"
className="rounded-xl border border-brand/20 bg-brand-soft px-4 py-3 text-sm text-foreground"
>
{t(
'aiProviders.runtimeRefreshPending',
'The model list is up to date. Use “Sync and apply Makelore models” when you are ready to restart the runtime.',
)}
</p>
) : null}
{loading ? (
<div className="flex items-center justify-center py-12 text-muted-foreground bg-surface-subtle rounded-3xl border border-transparent border-dashed">
<Loader2 className="h-6 w-6 animate-spin" />