mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 00:02:41 +00:00
Let users reset saved Pi model choices on the current host
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { PiConfiguredModelReset } from './PiConfiguredModelReset'
|
||||
import { getConnectionIdFromState } from '@/lib/connection-context'
|
||||
import { useRef } from 'react'
|
||||
import type React from 'react'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
@@ -103,6 +105,11 @@ export function CommitMessageAiPane({
|
||||
const storeSearchQuery = useAppStore((s) => s.settingsSearchQuery)
|
||||
const searchQuery = settingsSearchQuery ?? storeSearchQuery
|
||||
const config = readSettings(settings)
|
||||
const piHostKey = useAppStore((s) =>
|
||||
getCommitMessageModelDiscoveryHostKeyForScope(
|
||||
getRuntimeGitScope(settings, getConnectionIdFromState(s, s.activeWorktreeId))
|
||||
)
|
||||
)
|
||||
const ownership = getSettingOwnershipSummary('sourceControlAiDefaults')
|
||||
const settingsWriteQueueRef = useRef<Promise<void>>(undefined!)
|
||||
settingsWriteQueueRef.current ??= Promise.resolve()
|
||||
@@ -214,6 +221,24 @@ export function CommitMessageAiPane({
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
config.enabled &&
|
||||
matchesSettingsSearch(searchQuery, {
|
||||
title: translate('settings.piConfiguredModelReset.title', 'Saved Pi model choices'),
|
||||
description: translate('settings.piConfiguredModelReset.action', 'Use Pi’s configured model'),
|
||||
keywords: ['pi', 'model', 'copilot']
|
||||
})
|
||||
) {
|
||||
sections.push(
|
||||
<PiConfiguredModelReset
|
||||
key="pi-model-reset"
|
||||
config={config}
|
||||
hostKey={piHostKey}
|
||||
writeConfig={writeConfig}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
sections.push(
|
||||
<SourceControlAiActionRecipeDefaults
|
||||
key="action-recipes"
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import type {
|
||||
SourceControlAiSettings,
|
||||
SourceControlAiSettingsPatch
|
||||
} from '../../../../shared/source-control-ai-types'
|
||||
import {
|
||||
hasSavedPiSourceControlModel,
|
||||
resetPiSourceControlModelsForHost
|
||||
} from '../../../../shared/pi-source-control-model-reset'
|
||||
import { Button } from '../ui/button'
|
||||
import { Label } from '../ui/label'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
export function PiConfiguredModelReset({
|
||||
config,
|
||||
hostKey,
|
||||
writeConfig
|
||||
}: {
|
||||
config: SourceControlAiSettings
|
||||
hostKey: string
|
||||
writeConfig: (patch: SourceControlAiSettingsPatch) => Promise<void>
|
||||
}): React.JSX.Element | null {
|
||||
const [saving, setSaving] = useState(false)
|
||||
if (!hasSavedPiSourceControlModel(config, hostKey)) {
|
||||
return null
|
||||
}
|
||||
const reset = async (): Promise<void> => {
|
||||
setSaving(true)
|
||||
try {
|
||||
await writeConfig((current) => resetPiSourceControlModelsForHost(current, hostKey))
|
||||
toast.success(
|
||||
translate(
|
||||
'settings.piConfiguredModelReset.success',
|
||||
'Pi now uses its configured model on this host.'
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
toast.error(
|
||||
translate(
|
||||
'settings.piConfiguredModelReset.error',
|
||||
'Could not reset saved Pi model choices.'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 py-2">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<Label>
|
||||
{translate('settings.piConfiguredModelReset.title', 'Saved Pi model choices')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'settings.piConfiguredModelReset.description',
|
||||
'Reset saved Pi model choices for commit messages, pull requests, and branch names on this host. Models in recipe CLI arguments still apply.'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate('settings.piConfiguredModelReset.host', 'Host: {{host}}', {
|
||||
host: hostKey === 'local' ? 'This computer' : 'Current workspace host'
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" disabled={saving} onClick={() => void reset()}>
|
||||
{translate('settings.piConfiguredModelReset.action', 'Use Pi’s configured model')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,11 @@ import { translateSearchKeyword } from './settings-search-keywords'
|
||||
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
|
||||
|
||||
export const getCommitMessageAiPaneSearchEntries = createLocalizedCatalog(() => [
|
||||
{
|
||||
title: translate('settings.piConfiguredModelReset.title', 'Saved Pi model choices'),
|
||||
description: translate('settings.piConfiguredModelReset.action', 'Use Pi’s configured model'),
|
||||
keywords: ['pi', 'model', 'copilot', 'reset']
|
||||
},
|
||||
{
|
||||
title: translate(
|
||||
'auto.components.settings.commit.message.ai.search.24dbdfca78',
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultSettings } from './constants'
|
||||
import {
|
||||
hasSavedPiSourceControlModel,
|
||||
resetPiSourceControlModelsForHost
|
||||
} from './pi-source-control-model-reset'
|
||||
import {
|
||||
resolveSourceControlAiForOperation,
|
||||
projectSourceControlAiToLegacyCommitMessageAi,
|
||||
mergeLegacyCommitMessageAiIntoSourceControlAi
|
||||
} from './source-control-ai'
|
||||
|
||||
const copilot = 'github-copilot/gpt-5.4-mini'
|
||||
describe('Pi model reset', () => {
|
||||
it.each(['local', 'ssh:fixture', 'wsl:Ubuntu', 'runtime:fixture'])(
|
||||
'resets only Pi choices for %s through save/reload',
|
||||
(hostKey) => {
|
||||
const settings = getDefaultSettings('/tmp')
|
||||
const config = settings.sourceControlAi!
|
||||
config.agentId = 'pi'
|
||||
config.selectedModelByAgent = { pi: copilot, codex: 'gpt-5.5' }
|
||||
config.selectedModelByAgentByHost = {
|
||||
[hostKey]: { pi: copilot, codex: 'gpt-5.5' },
|
||||
'ssh:other': { pi: 'other/model' }
|
||||
}
|
||||
config.modelOverridesByOperation = {
|
||||
branchName: {
|
||||
selectedModelByAgentByHost: {
|
||||
[hostKey]: { pi: copilot },
|
||||
'ssh:other': { pi: 'other/branch' }
|
||||
}
|
||||
}
|
||||
}
|
||||
config.modelOverridesByOperation.branchName!.selectedThinkingByModel = { 'gpt-5.5': 'high' }
|
||||
config.actions = { branchName: { agentId: 'pi', agentArgs: '--thinking low' } }
|
||||
expect(hasSavedPiSourceControlModel(config, hostKey)).toBe(true)
|
||||
const reset = resetPiSourceControlModelsForHost(config, hostKey)
|
||||
expect(hasSavedPiSourceControlModel(reset, hostKey)).toBe(false)
|
||||
expect(reset.actions).toEqual(config.actions)
|
||||
expect(reset.modelOverridesByOperation?.branchName?.selectedThinkingByModel).toEqual({
|
||||
'gpt-5.5': 'high'
|
||||
})
|
||||
expect(reset.selectedModelByAgentByHost?.['ssh:other']).toEqual({ pi: 'other/model' })
|
||||
expect(reset.selectedModelByAgentByHost?.[hostKey]?.codex).toBe('gpt-5.5')
|
||||
expect(
|
||||
reset.modelOverridesByOperation?.branchName?.selectedModelByAgentByHost?.['ssh:other']
|
||||
).toEqual({ pi: 'other/branch' })
|
||||
expect(config.selectedModelByAgentByHost?.[hostKey]?.pi).toBe(copilot)
|
||||
if (hostKey !== 'local') {
|
||||
expect(reset.selectedModelByAgent.pi).toBe(copilot)
|
||||
}
|
||||
settings.commitMessageAi = projectSourceControlAiToLegacyCommitMessageAi(reset)
|
||||
settings.sourceControlAi = mergeLegacyCommitMessageAiIntoSourceControlAi(
|
||||
reset,
|
||||
settings.commitMessageAi
|
||||
)
|
||||
for (const operation of ['commitMessage', 'pullRequest', 'branchName'] as const) {
|
||||
const resolved = resolveSourceControlAiForOperation({
|
||||
settings,
|
||||
operation,
|
||||
discoveryHostKey: hostKey
|
||||
})
|
||||
expect(resolved.ok && resolved.value.params.useConfiguredDefaultModel).toBe(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
it('does not reset an unresolved host', () => {
|
||||
const config = getDefaultSettings('/tmp').sourceControlAi!
|
||||
config.selectedModelByAgent.pi = copilot
|
||||
expect(hasSavedPiSourceControlModel(config, 'unknown')).toBe(false)
|
||||
expect(resetPiSourceControlModelsForHost(config, 'unknown')).toBe(config)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
clearSourceControlAiModelChoiceForHost,
|
||||
readSourceControlAiModelChoiceForHost
|
||||
} from './source-control-ai-model-selection'
|
||||
import { PI_DEFAULT_MODEL_ID } from './pi-configured-default-model'
|
||||
import { UNKNOWN_COMMIT_MESSAGE_HOST_KEY } from './commit-message-host-key'
|
||||
import type { SourceControlAiSettings } from './source-control-ai-types'
|
||||
|
||||
export function hasSavedPiSourceControlModel(
|
||||
settings: SourceControlAiSettings,
|
||||
hostKey: string
|
||||
): boolean {
|
||||
if (hostKey === UNKNOWN_COMMIT_MESSAGE_HOST_KEY) {
|
||||
return false
|
||||
}
|
||||
return [settings, ...Object.values(settings.modelOverridesByOperation ?? {})].some((choice) => {
|
||||
const model = readSourceControlAiModelChoiceForHost(choice, hostKey, 'pi')
|
||||
return model !== undefined && model !== PI_DEFAULT_MODEL_ID
|
||||
})
|
||||
}
|
||||
|
||||
export function resetPiSourceControlModelsForHost(
|
||||
settings: SourceControlAiSettings,
|
||||
hostKey: string
|
||||
): SourceControlAiSettings {
|
||||
if (hostKey === UNKNOWN_COMMIT_MESSAGE_HOST_KEY) {
|
||||
return settings
|
||||
}
|
||||
const choice = clearSourceControlAiModelChoiceForHost(settings, hostKey, 'pi')
|
||||
const overrides = { ...settings.modelOverridesByOperation }
|
||||
for (const operation of Object.keys(overrides) as (keyof typeof overrides)[]) {
|
||||
const previous = overrides[operation]
|
||||
const cleared = clearSourceControlAiModelChoiceForHost(previous, hostKey, 'pi')
|
||||
overrides[operation] = previous?.selectedThinkingByModel
|
||||
? { ...cleared, selectedThinkingByModel: previous.selectedThinkingByModel }
|
||||
: cleared
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
selectedModelByAgent: choice?.selectedModelByAgent ?? {},
|
||||
selectedModelByAgentByHost: choice?.selectedModelByAgentByHost ?? {},
|
||||
modelOverridesByOperation: overrides
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { once } from 'node:events'
|
||||
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
|
||||
import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { createRequire } from 'node:module'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -88,7 +88,9 @@ try {
|
||||
join(dir, 'settings.json'),
|
||||
JSON.stringify({ defaultProvider: 'orca-proof', defaultModel: 'local' })
|
||||
)
|
||||
const settings = getDefaultSettings(scratch)
|
||||
const settings = process.argv[3]
|
||||
? JSON.parse(await readFile(resolve(process.argv[3]), 'utf8')).settings
|
||||
: getDefaultSettings(scratch)
|
||||
settings.defaultTuiAgent = 'pi'
|
||||
settings.sourceControlAi.agentId = 'pi'
|
||||
settings.commitMessageAi.agentId = 'pi'
|
||||
|
||||
Reference in New Issue
Block a user