Some suggestions for improvement

This commit is contained in:
Mattias Granlund 2024-03-16 18:18:42 +01:00
parent 6c2d98d2f4
commit c90d887ab7
4 changed files with 113 additions and 84 deletions

View File

@ -20,11 +20,15 @@ const defaultGitConfig = Object.freeze({
class DummyGitConfigService implements GitConfigService {
constructor(private config: { [index: string]: string | undefined }) {}
async get<T extends string>(key: string): Promise<T | null> {
return (this.config[key] || null) as T | null;
async get<T extends string>(key: string): Promise<T | undefined> {
return (this.config[key] || undefined) as T | undefined;
}
async set<T extends string>(key: string, value: T): Promise<T | null> {
async getWithDefault<T extends string>(key: string): Promise<T> {
return this.config[key] as T;
}
async set<T extends string>(key: string, value: T): Promise<T | undefined> {
return (this.config[key] = value);
}
}
@ -42,14 +46,14 @@ class DummyAIClient implements AIClient {
const examplePatch = `
@@ -52,7 +52,8 @@
export enum AnthropicModelName {
Opus = 'claude-3-opus-20240229',
- Sonnet = 'claude-3-sonnet-20240229'
+ Sonnet = 'claude-3-sonnet-20240229',
+ Haiku = 'claude-3-haiku-20240307'
}
export const AI_SERVICE_CONTEXT = Symbol();
`;

View File

@ -56,6 +56,30 @@ export enum AnthropicModelName {
Haiku = 'claude-3-haiku-20240307'
}
export enum ConfigKeys {
ModelProvider = 'gitbutler.aiModelProvider',
OpenAIKeyOption = 'gitbutler.aiOpenAIKeyOption',
OpenAIModelName = 'gitbutler.aiOpenAIModelName',
OpenAIKey = 'gitbutler.aiOpenAIKey',
AnthropicKeyOption = 'gitbutler.aiAnthropicKeyOption',
AnthropicModelName = 'gitbutler.aiAnthropicModelName',
AnthropicKey = 'gitbutler.aiAnthropicKey'
}
type SummarizeCommitOpts = {
diff: string;
useEmojiStyle?: boolean;
useBriefStyle?: boolean;
commitTemplate?: string;
userToken?: string;
};
type SummarizeBranchOpts = {
diff: string;
branchTemplate?: string;
userToken?: string;
};
export class AIService {
constructor(
private gitConfig: GitConfigService,
@ -66,13 +90,18 @@ export class AIService {
// Firstly, if the user has opted to use the GB API and isn't logged in, it will return undefined
// Secondly, if the user has opted to bring their own key but hasn't provided one, it will return undefined
async buildClient(userToken?: string): Promise<undefined | AIClient> {
const modelKind =
(await this.gitConfig.get<ModelKind>('gitbutler.aiModelProvider')) || ModelKind.OpenAI;
const openAIKeyOption =
(await this.gitConfig.get<KeyOption>('gitbutler.aiOpenAIKeyOption')) || KeyOption.ButlerAPI;
const anthropicKeyOption =
(await this.gitConfig.get<KeyOption>('gitbutler.aiAnthropicKeyOption')) ||
KeyOption.ButlerAPI;
const modelKind = await this.gitConfig.getWithDefault<ModelKind>(
ConfigKeys.ModelProvider,
ModelKind.OpenAI
);
const openAIKeyOption = await this.gitConfig.getWithDefault<KeyOption>(
ConfigKeys.OpenAIKeyOption,
KeyOption.ButlerAPI
);
const anthropicKeyOption = await this.gitConfig.getWithDefault<KeyOption>(
ConfigKeys.AnthropicKeyOption,
KeyOption.ButlerAPI
);
if (
(modelKind == ModelKind.OpenAI && openAIKeyOption == KeyOption.ButlerAPI) ||
@ -80,24 +109,22 @@ export class AIService {
) {
if (!userToken) {
toasts.error("When using GitButler's API to summarize code, you must be logged in");
return;
}
return new ButlerAIClient(this.cloud, userToken, ModelKind.OpenAI);
}
if (modelKind == ModelKind.OpenAI) {
const openAIModelName =
(await this.gitConfig.get<OpenAIModelName>('gitbutler.aiOpenAIModelName')) ||
OpenAIModelName.GPT35Turbo;
const openAIKey = await this.gitConfig.get('gitbutler.aiOpenAIKey');
const openAIModelName = await this.gitConfig.getWithDefault<OpenAIModelName>(
ConfigKeys.OpenAIModelName,
OpenAIModelName.GPT35Turbo
);
const openAIKey = await this.gitConfig.get(ConfigKeys.OpenAIKey);
if (!openAIKey) {
toasts.error(
'When using OpenAI in a bring your own key configuration, you must provide a valid token'
);
return;
}
@ -105,17 +132,16 @@ export class AIService {
return new OpenAIClient(openAIModelName, openAI);
}
if (modelKind == ModelKind.Anthropic) {
const anthropicModelName =
(await this.gitConfig.get<AnthropicModelName>('gitbutler.aiAnthropicModelName')) ||
AnthropicModelName.Haiku;
const anthropicKey = await this.gitConfig.get('gitbutler.aiAnthropicKey');
const anthropicModelName = await this.gitConfig.getWithDefault<AnthropicModelName>(
ConfigKeys.AnthropicModelName,
AnthropicModelName.Haiku
);
const anthropicKey = await this.gitConfig.get(ConfigKeys.AnthropicKey);
// TODO: Provide feedback to user
if (!anthropicKey) {
toasts.error(
'When using Anthropic in a bring your own key configuration, you must provide a valid token'
);
return;
}
@ -129,31 +155,21 @@ export class AIService {
useBriefStyle = false,
commitTemplate = defaultCommitTemplate,
userToken
}: {
diff: string;
useEmojiStyle?: boolean;
useBriefStyle?: boolean;
commitTemplate?: string;
userToken?: string;
}) {
}: SummarizeCommitOpts) {
const aiClient = await this.buildClient(userToken);
if (!aiClient) return;
let prompt = commitTemplate.replaceAll('%{diff}', diff.slice(0, diffLengthLimit));
if (useBriefStyle) {
prompt = prompt.replaceAll(
'%{brief_style}',
'The commit message must be only one sentence and as short as possible.'
);
} else {
prompt = prompt.replaceAll('%{brief_style}', '');
}
if (useEmojiStyle) {
prompt = prompt.replaceAll('%{emoji_style}', 'Make use of GitMoji in the title prefix.');
} else {
prompt = prompt.replaceAll('%{emoji_style}', "Don't use any emoji.");
}
const briefPart = useBriefStyle
? 'The commit message must be only one sentence and as short as possible.'
: '';
prompt = prompt.replaceAll('%{brief_style}', briefPart);
const emojiPart = useEmojiStyle
? 'Make use of GitMoji in the title prefix.'
: "Don't use any emoji.";
prompt = prompt.replaceAll('%{emoji_style}', emojiPart);
let message = await aiClient.evaluate(prompt);
@ -161,9 +177,8 @@ export class AIService {
message = message.split('\n')[0];
}
const firstNewLine = message.indexOf('\n');
const summary = firstNewLine > -1 ? message.slice(0, firstNewLine).trim() : message;
const description = firstNewLine > -1 ? message.slice(firstNewLine + 1).trim() : '';
const parts = message.split(/\n+(.*?)\w*/s);
const [summary, description] = [parts[0] || '', parts[1] || ''];
return description.length > 0 ? `${summary}\n\n${description}` : summary;
}
@ -172,20 +187,12 @@ export class AIService {
diff,
branchTemplate = defaultBranchTemplate,
userToken = undefined
}: {
diff: string;
branchTemplate?: string;
userToken?: string;
}) {
}: SummarizeBranchOpts) {
const aiClient = await this.buildClient(userToken);
if (!aiClient) return;
const prompt = branchTemplate.replaceAll('%{diff}', diff.slice(0, diffLengthLimit));
let message = await aiClient.evaluate(prompt);
message = message.replaceAll(' ', '-');
message = message.replaceAll('\n', '-');
return message;
const message = await aiClient.evaluate(prompt);
return message.replaceAll(' ', '-').replaceAll('\n', '-');
}
}

View File

@ -1,11 +1,16 @@
import { invoke } from '@tauri-apps/api/tauri';
export class GitConfigService {
get<T extends string>(key: string): Promise<T | null> {
return invoke<T | null>('git_get_global_config', { key });
async get<T extends string>(key: string): Promise<T | undefined> {
return await invoke<T | undefined>('git_get_global_config', { key });
}
set<T extends string>(key: string, value: T) {
return invoke<T | null>('git_set_global_config', { key, value });
async getWithDefault<T extends string>(key: string, defaultValue: T): Promise<T> {
const value = await invoke<T | undefined>('git_get_global_config', { key });
return value || defaultValue;
}
async set<T extends string>(key: string, value: T) {
return invoke<T | undefined>('git_set_global_config', { key, value });
}
}

View File

@ -4,6 +4,7 @@
import TextBox from './TextBox.svelte';
import {
AnthropicModelName,
ConfigKeys,
KeyOption,
ModelKind,
OpenAIModelName
@ -17,36 +18,48 @@
const gitConfigService = getContextByClass(GitConfigService);
let modelKind: ModelKind;
$: gitConfigService.set('gitbutler.aiModelProvider', modelKind);
let openAIKeyOption: KeyOption;
$: gitConfigService.set('gitbutler.aiOpenAIKeyOption', openAIKeyOption);
let anthropicKeyOption: KeyOption;
$: gitConfigService.set('gitbutler.aiAnthropicKeyOption', anthropicKeyOption);
let openAIKey: string | undefined;
$: if (openAIKey) gitConfigService.set('gitbutler.aiOpenAIKey', openAIKey);
let openAIModelName: OpenAIModelName;
$: gitConfigService.set('gitbutler.aiOpenAIModelName', openAIModelName);
let anthropicKey: string | undefined;
$: if (anthropicKey) gitConfigService.set('gitbutler.aiAnthropicKey', anthropicKey);
let anthropicModelName: AnthropicModelName;
$: gitConfigService.set('gitbutler.aiModelProvider', modelKind);
$: gitConfigService.set('gitbutler.aiOpenAIKeyOption', openAIKeyOption);
$: gitConfigService.set('gitbutler.aiOpenAIModelName', openAIModelName);
$: if (openAIKey) gitConfigService.set('gitbutler.aiOpenAIKey', openAIKey);
$: gitConfigService.set('gitbutler.aiAnthropicKeyOption', anthropicKeyOption);
$: gitConfigService.set('gitbutler.aiAnthropicModelName', anthropicModelName);
$: if (anthropicKey) gitConfigService.set('gitbutler.aiAnthropicKey', anthropicKey);
onMount(async () => {
modelKind =
(await gitConfigService.get<ModelKind>('gitbutler.aiModelProvider')) || ModelKind.OpenAI;
openAIKeyOption =
(await gitConfigService.get<KeyOption>('gitbutler.aiOpenAIKeyOption')) || KeyOption.ButlerAPI;
anthropicKeyOption =
(await gitConfigService.get<KeyOption>('gitbutler.aiAnthropicKeyOption')) ||
KeyOption.ButlerAPI;
openAIModelName =
(await gitConfigService.get<OpenAIModelName>('gitbutler.aiOpenAIModelName')) ||
OpenAIModelName.GPT35Turbo;
openAIKey = (await gitConfigService.get('gitbutler.aiOpenAIKey')) || undefined;
anthropicModelName =
(await gitConfigService.get<AnthropicModelName>('gitbutler.aiAnthropicModelName')) ||
AnthropicModelName.Haiku;
anthropicKey = (await gitConfigService.get('gitbutler.aiAnthropicKey')) || undefined;
modelKind = await gitConfigService.getWithDefault<ModelKind>(
ConfigKeys.ModelProvider,
ModelKind.OpenAI
);
openAIKeyOption = await gitConfigService.getWithDefault<KeyOption>(
ConfigKeys.OpenAIKeyOption,
KeyOption.ButlerAPI
);
openAIModelName = await gitConfigService.getWithDefault<OpenAIModelName>(
ConfigKeys.OpenAIModelName,
OpenAIModelName.GPT35Turbo
);
openAIKey = await gitConfigService.get(ConfigKeys.OpenAIKey);
anthropicKeyOption = await gitConfigService.getWithDefault<KeyOption>(
ConfigKeys.AnthropicKeyOption,
KeyOption.ButlerAPI
);
anthropicModelName = await gitConfigService.getWithDefault<AnthropicModelName>(
ConfigKeys.AnthropicModelName,
AnthropicModelName.Haiku
);
anthropicKey = await gitConfigService.get(ConfigKeys.AnthropicKey);
});
$: if (form) form.modelKind.value = modelKind;