* feat(aichat): create reusable CustomAIPrompts component Extract custom AI prompts UI into a reusable component that can be used in both workspace settings and user settings. Component includes: - AI mode selector with visual indicators for set prompts - Textarea with character limit - Customizable title, description, and hint messages Co-authored-by: centdix <centdix@users.noreply.github.com> * refactor(aichat): update workspace AISettings to use reusable component Replace inline custom prompts UI with the reusable CustomAIPrompts component. Add hint about user-level custom prompts being available in account settings and how they combine with workspace prompts. Co-authored-by: centdix <centdix@users.noreply.github.com> * feat(aichat): add user-level custom AI prompts in account settings Add collapsible section in user settings for custom AI prompts: - Stored in localStorage (key: userCustomAIPrompts) - Collapsible UI to save space - Visual indicator when prompts are configured - Hint about prompt combination with workspace settings - Prompts apply across all workspaces for the user Co-authored-by: centdix <centdix@users.noreply.github.com> * feat(aichat): combine workspace and user custom prompts Update AIChatManager to combine workspace-level and user-level custom prompts. Prompts are combined in order: workspace first, then user. Add helper functions in aiStore.ts: - getUserCustomPrompts(): retrieves user prompts from localStorage - getCombinedCustomPrompt(mode): combines workspace + user prompts All AI modes (script, flow, navigator, ask, API) now use combined prompts, allowing users to append their own instructions to workspace settings across all workspaces. Co-authored-by: centdix <centdix@users.noreply.github.com> * fix: remove unused imports Remove unused imports to fix svelte-check errors: - Remove unused 'get' from svelte/store in AIChatManager - Remove unused 'copilotInfo' from aiStore in AIChatManager - Remove unused 'AIMode' from AISettings Co-authored-by: centdix <centdix@users.noreply.github.com> * simplify * nit * fix * fix --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: centdix <centdix@users.noreply.github.com>
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import { writable, get } from 'svelte/store'
|
|
import { workspaceAIClients } from './components/copilot/lib'
|
|
import { type AIProviderModel, type AIProvider, WorkspaceService, type AIConfig } from './gen'
|
|
import { COPILOT_SESSION_MODEL_SETTING_NAME, COPILOT_SESSION_PROVIDER_SETTING_NAME } from './stores'
|
|
import { getLocalSetting } from './utils'
|
|
|
|
const USER_CUSTOM_PROMPTS_KEY = 'userCustomAIPrompts'
|
|
|
|
const sessionModel = getLocalSetting(COPILOT_SESSION_MODEL_SETTING_NAME)
|
|
const sessionProvider = getLocalSetting(COPILOT_SESSION_PROVIDER_SETTING_NAME)
|
|
export const copilotSessionModel = writable<AIProviderModel | undefined>(
|
|
sessionModel && sessionProvider
|
|
? {
|
|
model: sessionModel,
|
|
provider: sessionProvider as AIProvider
|
|
}
|
|
: undefined
|
|
)
|
|
|
|
|
|
export const copilotInfo = writable<{
|
|
enabled: boolean
|
|
codeCompletionModel?: AIProviderModel
|
|
defaultModel?: AIProviderModel
|
|
aiModels: AIProviderModel[]
|
|
customPrompts?: Record<string, string>
|
|
maxTokensPerModel?: Record<string, number>
|
|
}>({
|
|
enabled: false,
|
|
codeCompletionModel: undefined,
|
|
defaultModel: undefined,
|
|
aiModels: [],
|
|
customPrompts: {},
|
|
maxTokensPerModel: {}
|
|
})
|
|
|
|
export async function loadCopilot(workspace: string) {
|
|
workspaceAIClients.init(workspace)
|
|
try {
|
|
const info = await WorkspaceService.getCopilotInfo({ workspace })
|
|
setCopilotInfo(info)
|
|
} catch (err) {
|
|
setCopilotInfo({})
|
|
console.error('Could not get copilot info', err)
|
|
}
|
|
}
|
|
|
|
export function setCopilotInfo(aiConfig: AIConfig) {
|
|
if (Object.keys(aiConfig.providers ?? {}).length > 0) {
|
|
const aiModels = Object.entries(aiConfig.providers ?? {}).flatMap(
|
|
([provider, providerConfig]) =>
|
|
providerConfig.models.map((m) => ({ model: m, provider: provider as AIProvider }))
|
|
)
|
|
|
|
copilotSessionModel.update((model) => {
|
|
if (
|
|
model &&
|
|
!aiModels.some((m) => m.model === model.model && m.provider === model.provider)
|
|
) {
|
|
return undefined
|
|
}
|
|
return model
|
|
})
|
|
|
|
copilotInfo.set({
|
|
enabled: true,
|
|
codeCompletionModel: aiConfig.code_completion_model,
|
|
defaultModel: aiConfig.default_model,
|
|
aiModels: aiModels,
|
|
customPrompts: aiConfig.custom_prompts ?? {},
|
|
maxTokensPerModel: aiConfig.max_tokens_per_model ?? {}
|
|
})
|
|
} else {
|
|
copilotSessionModel.set(undefined)
|
|
|
|
copilotInfo.set({
|
|
enabled: false,
|
|
codeCompletionModel: undefined,
|
|
defaultModel: undefined,
|
|
aiModels: [],
|
|
customPrompts: {},
|
|
maxTokensPerModel: {}
|
|
})
|
|
}
|
|
}
|
|
|
|
export function getCurrentModel() {
|
|
const model =
|
|
get(copilotSessionModel) ?? get(copilotInfo).defaultModel ?? get(copilotInfo).aiModels[0]
|
|
if (!model) {
|
|
throw new Error('No model selected')
|
|
}
|
|
return model
|
|
}
|
|
|
|
export function getUserCustomPrompts(): Record<string, string> {
|
|
const stored = getLocalSetting(USER_CUSTOM_PROMPTS_KEY)
|
|
if (stored) {
|
|
try {
|
|
return JSON.parse(stored)
|
|
} catch (e) {
|
|
console.error('Failed to parse user custom prompts', e)
|
|
return {}
|
|
}
|
|
}
|
|
return {}
|
|
}
|
|
|
|
export function getCombinedCustomPrompt(mode: string): string | undefined {
|
|
const workspacePrompt = get(copilotInfo).customPrompts?.[mode]
|
|
const userPrompts = getUserCustomPrompts()
|
|
const userPrompt = userPrompts[mode]
|
|
|
|
const prompts = [workspacePrompt, userPrompt].filter((p) => p?.trim())
|
|
|
|
if (prompts.length === 0) {
|
|
return undefined
|
|
}
|
|
|
|
return prompts.join('\n\n')
|
|
}
|