✨ feat(vscode): 问 AI 输入框旁边能选 Claude profile,记住上次选的,问答灰字写用哪个 profile 答的
This commit is contained in:
1 parent
1eb42d8389
commit
9efae1f496
20 files changed
+397
-63
No files matched your search
@@ -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)) }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user