mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-24 18:12:48 +03:00
feat: add model and key configuration options
Add UI components for configuring the model kind (OpenAI or Anthropic), key option (Butler API or bring your own key), and associated settings. The model kind can be set to either OpenAI or Anthropic. The key option allows using the Butler API proxy or providing your own API key. When using your own key, the API key and model version can be specified for the selected model kind (OpenAI or Anthropic). The selected options are persisted and loaded from the backend settings.
This commit is contained in:
parent
15956c0729
commit
12aefe73a3
@ -1,8 +1,8 @@
|
|||||||
import { getAnthropicKey, getAnthropicModel, getModelKind, getOpenAIKey, getOpenAIModel, getTokenOption, KeyOption, ModelKind } from './summarizer_settings';
|
import { getAnthropicKey, getAnthropicModel, getModelKind, getOpenAIKey, getOpenAIModel, getKeyOption, KeyOption, ModelKind } from './summarizer_settings';
|
||||||
import {
|
import {
|
||||||
type AIProvider,
|
type AIProvider,
|
||||||
ButlerAIProvider,
|
ButlerAIProvider,
|
||||||
OpenAIProvider as AnthropicAIProvider,
|
AnthropicAIProvider,
|
||||||
OpenAIProvider
|
OpenAIProvider
|
||||||
} from '$lib/backend/aiProviders';
|
} from '$lib/backend/aiProviders';
|
||||||
import { getCloudApiClient, type User } from '$lib/backend/cloud';
|
import { getCloudApiClient, type User } from '$lib/backend/cloud';
|
||||||
@ -99,9 +99,9 @@ interface SummarizerContext {
|
|||||||
// Secondly, if the user has opted to bring their own key but hasn't provided one, it will return undefined
|
// Secondly, if the user has opted to bring their own key but hasn't provided one, it will return undefined
|
||||||
export async function buildSummarizer(context: SummarizerContext): Promise<Summarizer | undefined> {
|
export async function buildSummarizer(context: SummarizerContext): Promise<Summarizer | undefined> {
|
||||||
const modelKind = await getModelKind();
|
const modelKind = await getModelKind();
|
||||||
const tokenOption = await getTokenOption();
|
const keyOption = await getKeyOption();
|
||||||
|
|
||||||
if (tokenOption === KeyOption.ButlerAPI) {
|
if (keyOption === KeyOption.ButlerAPI) {
|
||||||
if (!context.user) return;
|
if (!context.user) return;
|
||||||
|
|
||||||
const aiProvider = new ButlerAIProvider(getCloudApiClient(), context.user, modelKind);
|
const aiProvider = new ButlerAIProvider(getCloudApiClient(), context.user, modelKind);
|
||||||
|
@ -42,12 +42,12 @@ export function setModelKind(modelKind: ModelKind) {
|
|||||||
|
|
||||||
const tokenOptionConfigKey = 'tokenOption';
|
const tokenOptionConfigKey = 'tokenOption';
|
||||||
|
|
||||||
export async function getTokenOption(): Promise<KeyOption> {
|
export async function getKeyOption(): Promise<KeyOption> {
|
||||||
const tokenKind = (await gitGetConfig(tokenOptionConfigKey)) as KeyOption | undefined;
|
const tokenKind = (await gitGetConfig(tokenOptionConfigKey)) as KeyOption | undefined;
|
||||||
return tokenKind || KeyOption.ButlerAPI;
|
return tokenKind || KeyOption.ButlerAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTokenOption(tokenOption: KeyOption) {
|
export function setKeyOption(tokenOption: KeyOption) {
|
||||||
return gitSetConfig(tokenOptionConfigKey, tokenOption);
|
return gitSetConfig(tokenOptionConfigKey, tokenOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export async function getAnthropicKey(): Promise<string | undefined> {
|
|||||||
return key || undefined;
|
return key || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAnthropicToken(token: string) {
|
export function setAnthropicKey(token: string) {
|
||||||
return gitSetConfig(anthropicKeyConfigKey, token);
|
return gitSetConfig(anthropicKeyConfigKey, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
125
gitbutler-ui/src/lib/components/AISettings.svelte
Normal file
125
gitbutler-ui/src/lib/components/AISettings.svelte
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SectionCard from "$lib/components/SectionCard.svelte";
|
||||||
|
import { KeyOption, ModelKind, getModelKind, getKeyOption, setModelKind, setKeyOption, getAnthropicKey, setAnthropicKey, setOpenAIKey, getOpenAIKey, AnthropicModel, getAnthropicModel, OpenAIModel, getOpenAIModel, setAnthropicModel, setOpenAIModel } from "$lib/backend/summarizer_settings";
|
||||||
|
import Select from "./Select.svelte";
|
||||||
|
import SelectItem from "./SelectItem.svelte";
|
||||||
|
import TextBox from "./TextBox.svelte";
|
||||||
|
|
||||||
|
let modelKind: { name: string, value: ModelKind } | undefined;
|
||||||
|
getModelKind().then((kind) => modelKind = modelKinds.find((option) => option.value == kind))
|
||||||
|
$: if (modelKind) setModelKind(modelKind.value)
|
||||||
|
|
||||||
|
const modelKinds = [
|
||||||
|
{
|
||||||
|
name: "Open AI",
|
||||||
|
value: ModelKind.OpenAI
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Anthropic",
|
||||||
|
value: ModelKind.Anthropic
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let keyOption: { name: string, value: KeyOption } | undefined;
|
||||||
|
getKeyOption().then((persistedKeyOption) => keyOption = keyOptions.find((option) => option.value == persistedKeyOption))
|
||||||
|
$: if (keyOption) setKeyOption(keyOption.value)
|
||||||
|
|
||||||
|
const keyOptions = [
|
||||||
|
{
|
||||||
|
name: "Butler API",
|
||||||
|
value: KeyOption.ButlerAPI
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bring your own key",
|
||||||
|
value: KeyOption.BringYourOwn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let openAIKey: string | undefined;
|
||||||
|
getOpenAIKey().then((persistedOpenAIKey) => openAIKey = persistedOpenAIKey)
|
||||||
|
$: if (openAIKey) setOpenAIKey(openAIKey)
|
||||||
|
|
||||||
|
let openAIModel: { name: string, value: OpenAIModel } | undefined;
|
||||||
|
getOpenAIModel().then((persistedOpenAIModel) => openAIModel = openAIModelOptions.find((option) => option.value == persistedOpenAIModel))
|
||||||
|
$: if (openAIModel) setOpenAIModel(openAIModel.value)
|
||||||
|
|
||||||
|
const openAIModelOptions = [
|
||||||
|
{
|
||||||
|
name: "GPT 3.5 Turbo",
|
||||||
|
value: OpenAIModel.GPT35Turbo
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GPT 4",
|
||||||
|
value: OpenAIModel.GPT4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GPT 4 Turbo",
|
||||||
|
value: OpenAIModel.GPT4Turbo
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let anthropicKey: string | undefined;
|
||||||
|
getAnthropicKey().then((persistedAnthropicKey) => anthropicKey = persistedAnthropicKey)
|
||||||
|
$: if (anthropicKey) setAnthropicKey(anthropicKey)
|
||||||
|
|
||||||
|
let anthropicModel: { name: string, value: AnthropicModel } | undefined;
|
||||||
|
getAnthropicModel().then((persistedAnthropicModel) => anthropicModel = anthropicModelOptions.find((option) => option.value == persistedAnthropicModel))
|
||||||
|
$: if (anthropicModel) setAnthropicModel(anthropicModel.value)
|
||||||
|
|
||||||
|
const anthropicModelOptions = [
|
||||||
|
{
|
||||||
|
name: "Sonnet",
|
||||||
|
value: AnthropicModel.Sonnet
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Opus",
|
||||||
|
value: AnthropicModel.Opus
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SectionCard>
|
||||||
|
<svelte:fragment slot="title">Model Kind</svelte:fragment>
|
||||||
|
<svelte:fragment slot="body">
|
||||||
|
GitButler supports OpenAI and Anthropic for various summerization tasks, either proxied via the GitButler servers or in a bring your own key configuration.
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<Select items={modelKinds} bind:value={modelKind} itemId="value" labelId="name">
|
||||||
|
<SelectItem slot="template" let:item>
|
||||||
|
{item.name}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</SectionCard>
|
||||||
|
|
||||||
|
<SectionCard>
|
||||||
|
<svelte:fragment slot="title">Key Configuration</svelte:fragment>
|
||||||
|
<svelte:fragment slot="body">
|
||||||
|
GitButler can either be configured to be proxied via the GitButler servers or to use your own key.
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<Select items={keyOptions} bind:value={keyOption} itemId="value" labelId="name">
|
||||||
|
<SelectItem slot="template" let:item>
|
||||||
|
{item.name}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{#if keyOption?.value === KeyOption.BringYourOwn}
|
||||||
|
{#if modelKind?.value == ModelKind.Anthropic}
|
||||||
|
<TextBox label="Anthropic API Key" bind:value={anthropicKey}/>
|
||||||
|
|
||||||
|
<Select items={anthropicModelOptions} bind:value={anthropicModel} itemId="value" labelId="name" label="Model Version">
|
||||||
|
<SelectItem slot="template" let:item>
|
||||||
|
{item.name}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
{:else if modelKind?.value == ModelKind.OpenAI}
|
||||||
|
<TextBox label="OpenAI API Key" bind:value={openAIKey}/>
|
||||||
|
|
||||||
|
<Select items={openAIModelOptions} bind:value={openAIModel} itemId="value" labelId="name" label="Model Version">
|
||||||
|
<SelectItem slot="template" let:item>
|
||||||
|
{item.name}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</SectionCard>
|
@ -25,6 +25,7 @@
|
|||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import AiSettings from '$lib/components/AISettings.svelte';
|
||||||
|
|
||||||
const userSettings = getContext(SETTINGS_CONTEXT) as SettingsStore;
|
const userSettings = getContext(SETTINGS_CONTEXT) as SettingsStore;
|
||||||
|
|
||||||
@ -335,35 +336,7 @@
|
|||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
{:else if currentSection === 'ai'}
|
{:else if currentSection === 'ai'}
|
||||||
<ContentWrapper title="AI Options">
|
<ContentWrapper title="AI Options">
|
||||||
<SectionCard>
|
<AiSettings />
|
||||||
<svelte:fragment slot="title">AI Provider</svelte:fragment>
|
|
||||||
<svelte:fragment slot="caption">
|
|
||||||
GitButler uses a a connection to its API to provide AI functionality, but supports
|
|
||||||
additional providers
|
|
||||||
</svelte:fragment>
|
|
||||||
|
|
||||||
<TextBox readonly selectall bind:value={sshKey} />
|
|
||||||
<div class="row-buttons">
|
|
||||||
<Button
|
|
||||||
kind="filled"
|
|
||||||
color="primary"
|
|
||||||
icon="copy"
|
|
||||||
on:click={() => copyToClipboard(sshKey)}
|
|
||||||
>
|
|
||||||
Copy to Clipboard
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
kind="outlined"
|
|
||||||
color="neutral"
|
|
||||||
icon="open-link"
|
|
||||||
on:mousedown={() => {
|
|
||||||
openExternalUrl('https://github.com/settings/ssh/new');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add key to GitHub
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SectionCard>
|
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
Loading…
Reference in New Issue
Block a user