✨ feat(vscode): 问 AI 输入框旁边能选 Claude profile,记住上次选的,问答灰字写用哪个 profile 答的

This commit is contained in:
cruldra committed 2026-09-26 22:28:29 +08:00
1 parent 1eb42d8389
commit 9efae1f496
20 files changed
+397 -63

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
import type { Disposable, Memento } from 'vscode'
import type { ClaudeProfile } from '../../cc/profiles'
import { getDefaultProfilePath, listClaudeProfiles } from '../../cc/profiles'
/** globalState 里记的「问 AI」最近选的 profile 名(「默认」记成 '')。 */
export const ASK_PROFILE_KEY = 'superpowers.codeReader.askProfile'
/** 下拉里的一项:id 是 profile 名;「默认」(不带 `--settings`,claude 用 ~/.claude 的登录)的 id 是 ''。 */
export interface ProfileOption {
id: string
label: string
}
export interface ProfileChoice {
options: ProfileOption[]
selected: string
}
export const DEFAULT_OPTION: ProfileOption = { id: '', label: '默认' }
/**
* 下拉的选项和选中的那项。插件的默认 profile(getDefaultProfilePath)在列表里时,没选过就选它;
* 不在列表里(一个 profile 都没有,或没有 offical.json)时最前面加一项「默认」,没选过就选它。
* 记着的那项不在列表里了(profile 删了、目录换了)也回到这一项。
*/
export function profileChoice(profiles: readonly ClaudeProfile[], defaultPath: string, remembered: unknown): ProfileChoice {
const fallback = profiles.find(profile => profile.path === defaultPath)
const options = [...(fallback ? [] : [DEFAULT_OPTION]), ...profiles.map(({ name }) => ({ id: name, label: name }))]
const selected = options.find(option => option.id === remembered)?.id ?? fallback?.name ?? DEFAULT_OPTION.id
return { options, selected }
}
export interface ProfileSource {
list: () => Promise<ClaudeProfile[]>
defaultPath: () => string
}
const PROFILE_SOURCE: ProfileSource = { list: listClaudeProfiles, defaultPath: getDefaultProfilePath }
/**
* 「问 AI」用哪个 Claude Code profile:所有预览共用一份,记在 globalState 里,换个预览、重启 VS Code 都还是它;
* 一个预览改了,所有订阅的预览都收到(包括改的那个,webview 只按扩展端发来的显示)。
* profile 列表每次现读(同看板:cc/profiles 的目录),不依赖 vscode 运行时,便于单元测试。
*/
export class AskProfiles {
private readonly listeners = new Set<(choice: ProfileChoice) => void>()
constructor(private readonly state: Pick<Memento, 'get' | 'update'>, private readonly source: ProfileSource = PROFILE_SOURCE) {}
async choice(): Promise<ProfileChoice> {
return (await this.read()).choice
}
/** 下一个问题用的 profile;选的是「默认」时没有(不带 `--settings`)。 */
async current(): Promise<ClaudeProfile | undefined> {
const { profiles, choice } = await this.read()
return profiles.find(profile => profile.name === choice.selected)
}
/** 选了就调 listener,带上新的选项和选中的那项。 */
subscribe(listener: (choice: ProfileChoice) => void): Disposable {
this.listeners.add(listener)
return { dispose: () => this.listeners.delete(listener) }
}
async select(id: string): Promise<void> {
await this.state.update(ASK_PROFILE_KEY, id)
const choice = await this.choice()
for (const listener of [...this.listeners])
listener(choice)
}
private async read(): Promise<{ profiles: ClaudeProfile[], choice: ProfileChoice }> {
const profiles = await this.source.list()
return { profiles, choice: profileChoice(profiles, this.source.defaultPath(), this.state.get(ASK_PROFILE_KEY)) }
}
}