Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@
:anthropicApiKey="anthropicApiKey" :googleApiKey="googleApiKey" @manage-models="handleManageModels"
@open-configuration="handleOpenConfiguration" />
<Configuration v-model:visible="showConfiguration" :reactivePage="configurationPage" @close="closeConfiguration" />
<div class="onboarding-screen-popover-container" v-if="showOnboarding">
<div class="onboarding-screen-popover" v-kbd-trap="true">
<button class="btn close-btn" @click="closeOnboardingScreen">
<span class="material-symbols-outlined">close</span>
</button>
<OnboardingScreen @submit="closeOnboardingScreen" @open-provider-config="closeOnboardingScreenAndOpenProviderConfig" />
</div>
</div>
<Dialog :visible="showOnboarding" :closable="false" :draggable="false">
<OnboardingScreen @submit="closeOnboardingScreen"
@open-provider-config="closeOnboardingScreenAndOpenProviderConfig" />
</Dialog>
</div>
</template>

Expand Down
7 changes: 6 additions & 1 deletion src/assets/styles/pages/_chat-interface.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@

.plugin-title {
text-align: center;
margin-bottom: 2rem;
margin-bottom: 1rem;
max-width: 32rem;

p {
color: var(--text);
}
}

.chat-messages {
Expand Down
4 changes: 4 additions & 0 deletions src/assets/styles/pages/_onboarding-screen.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
max-width: 24rem;
text-align: center;

h1 {
margin-top: 0;
}

.api-info {
margin-top: 1.5rem;
margin-bottom: 1rem;
Expand Down
132 changes: 99 additions & 33 deletions src/components/ApiKeyForm.vue
Original file line number Diff line number Diff line change
@@ -1,78 +1,114 @@
<template>
<div class="api-key-form">
<BaseInput
type="password"
placeholder="sk-proj-..."
v-model="openaiApiKey"
>
<div class="api-key-form" :class="{ 'dropdown-based': dropdownBased }">
<div class="input-container" v-if="dropdownBased">
<label for="select-provider">Provider</label>
<Select for="select-provider" v-model="selectedProvider" :options="dropdownOptions" optionLabel="label"
placeholder="Select a provider" />
</div>
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'openai'" type="password" placeholder="sk-proj-..."
v-model="openaiApiKey">
<template #label>
{{ providerConfigs["openai"].displayName }}
{{ dropdownBased ? "API Key" : '' }}
</template>
<template #helper>
Get your <ExternalLink href="https://platform.openai.com/account/api-keys">API Key here.</ExternalLink>
</template>
</BaseInput>
<BaseInput
type="password"
placeholder="sk-ant-..."
v-model="anthropicApiKey"
>
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'anthropic'" type="password" placeholder="sk-ant-..."
v-model="anthropicApiKey">
<template #label>
{{ providerConfigs["anthropic"].displayName }}
{{ dropdownBased ? "API Key" : '' }}
</template>
<template #helper>
Get your <ExternalLink href="https://console.anthropic.com/settings/keys">API Key here.</ExternalLink>
</template>
</BaseInput>
<BaseInput
type="password"
placeholder="AIzaSy..."
v-model="googleApiKey"
>
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'google'" type="password" placeholder="AIzaSy..."
v-model="googleApiKey">
<template #label>
{{ providerConfigs["google"].displayName }}
{{ dropdownBased ? "API Key" : '' }}
</template>
<template #helper>
Get your <ExternalLink href="https://aistudio.google.com/app/apikey">API Key here.</ExternalLink>
</template>
</BaseInput>
<template v-if="selectedProvider.value === 'openaiCompat'">
<BaseInput v-model="openaiCompatApiKey">
<template #label>OpenAI Compatible API Key</template>
</BaseInput>
<BaseInput v-model="openaiCompatBaseUrl">
<template #label>Base URL</template>
</BaseInput>
<BaseInput v-model="openaiCompatHeaders" type="textarea">
<template #label>Custom Headers</template>
</BaseInput>
</template>
<template v-if="selectedProvider.value === 'ollama'">
<BaseInput v-model="ollamaBaseUrl">
<template #label>Ollama Base URL</template>
</BaseInput>
<BaseInput v-model="ollamaHeaders" type="textarea">
<template #label>Custom Headers</template>
</BaseInput>
</template>
</div>
</template>

<script lang="ts">
import { mapState, mapActions } from "pinia";
import { mapActions } from "pinia";
import { useConfigurationStore } from "@/stores/configuration";
import { providerConfigs } from "@/config";
import { AvailableProviders, providerConfigs } from "@/config";
import BaseInput from "./common/BaseInput.vue";
import ExternalLink from "./common/ExternalLink.vue";
import Select from 'primevue/select';

export default {
name: "ApiKeyForm",

components: {
BaseInput,
ExternalLink,
Select,
},

emits: ["change"],
emits: ["change", "changeProvider"],

props: {
dropdownBased: Boolean,
},

data() {
const config = useConfigurationStore();

return {
openaiApiKey: "",
anthropicApiKey: "",
googleApiKey: "",
selectedProvider: {
label: "" as typeof providerConfigs[AvailableProviders]['displayName'],
value: "" as AvailableProviders,
},
openaiApiKey: config['providers.openai.apiKey'],
anthropicApiKey: config['providers.anthropic.apiKey'],
googleApiKey: config['providers.google.apiKey'],
ollamaBaseUrl: config.providers_ollama_baseUrl,
ollamaHeaders: config.providers_ollama_headers,
openaiCompatBaseUrl: config.providers_openaiCompat_baseUrl,
openaiCompatApiKey: config.providers_openaiCompat_apiKey,
openaiCompatHeaders: config.providers_openaiCompat_headers,
};
},

computed: {
...mapState(useConfigurationStore, [
"providers.openai.apiKey",
"providers.anthropic.apiKey",
"providers.google.apiKey",
]),
providerConfigs() {
return providerConfigs;
},
dropdownOptions() {
return Object.keys(providerConfigs).map((provider) => ({
label: this.providerConfigs[provider].displayName,
value: provider,
}));
},
},

watch: {
Expand All @@ -88,16 +124,46 @@ export default {
this.configure("providers.google.apiKey", this.googleApiKey);
this.$emit("change");
},
ollamaBaseUrl() {
this.configure("providers_ollama_baseUrl", this.ollamaBaseUrl);
this.$emit("change");
},
ollamaHeaders() {
this.configure("providers_ollama_headers", this.ollamaHeaders);
this.$emit("change");
},
openaiCompatBaseUrl() {
this.configure("providers_openaiCompat_baseUrl", this.openaiCompatBaseUrl);
this.$emit("change");
},
openaiCompatApiKey() {
this.configure("providers_openaiCompat_apiKey", this.openaiCompatApiKey);
this.$emit("change");
},
openaiCompatHeaders() {
this.configure("providers_openaiCompat_headers", this.openaiCompatHeaders);
this.$emit("change");
},
selectedProvider() {
this.$emit("changeProvider", this.selectedProvider.value);
},
},

methods: {
...mapActions(useConfigurationStore, ["configure"]),
},

mounted() {
this.openaiApiKey = this["providers.openai.apiKey"];
this.anthropicApiKey = this["providers.anthropic.apiKey"];
this.googleApiKey = this["providers.google.apiKey"];
},
};
</script>

<style scoped>
.input-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
justify-content: flex-start;

label {
align-self: flex-start;
}
}
</style>
21 changes: 20 additions & 1 deletion src/components/ChatInterface.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
<span class="title-popup">Settings</span>
</button>
</div>
<h1 class="plugin-title">AI Shell</h1>
<div class="plugin-title">
<h1>AI Shell</h1>
<p>
The AI Shell can see your table schemas, and (with your permission)
run {{ sqlOrCode }} to answer your questions.
<ExternalLink href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/">Learn more</ExternalLink>.
</p>
</div>
<div class="chat-messages">
<message
v-for="(message, index) in messages"
Expand Down Expand Up @@ -88,6 +95,8 @@ import { RootBinding } from "@/plugins/appEvent";
import { useInternalDataStore } from "@/stores/internalData";
import BaseInput from "@/components/common/BaseInput.vue";
import PromptInput from "@/components/common/PromptInput.vue";
import { getConnectionInfo } from "@beekeeperstudio/plugin";
import ExternalLink from "@/components/common/ExternalLink.vue";

export default {
name: "ChatInterface",
Expand All @@ -97,6 +106,7 @@ export default {
Markdown,
BaseInput,
PromptInput,
ExternalLink,
},

emits: ["manage-models", "open-configuration"],
Expand Down Expand Up @@ -133,6 +143,7 @@ export default {
isAtBottom: true,
showFullError: false,
noModelError: false,
sqlOrCode: "SQL",
};
},

Expand Down Expand Up @@ -214,6 +225,14 @@ export default {
},

async mounted() {
getConnectionInfo().then((connection) => {
if (connection.databaseType === "mongodb"
|| connection.connectionType === "surrealdb"
|| connection.connectionType === "redis") {
this.sqlOrCode = "Code";
}
});

const scrollContainer = this.$refs.scrollContainerRef as HTMLElement;
scrollContainer.addEventListener("scroll", () => {
// Calculate if we're near bottom (within 50px of bottom)
Expand Down
26 changes: 16 additions & 10 deletions src/components/OnboardingScreen.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
<template>
<div class="onboarding-screen">
<h1>Welcome to AI Shell</h1>
<p>Before we start, please enter your API keys below.</p>
<ApiInfo />
<h1>Welcome to the AI Shell</h1>
<p>
Your AI agent can explore your database and (with your permission) write code to
answer your questions. Enter the API key below for your preffered agent to get started.
</p>
<p>
<ExternalLink href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/">Learn more</ExternalLink>
-
<ExternalLink href="https://www.youtube.com/watch?v=pAhQUFDeiwc">90s Walkthough Video</ExternalLink>
</p>
<form @submit.prevent="$emit('submit')">
<ApiKeyForm @change="changed = true" />
<ApiKeyForm dropdown-based @change="changed = true" @change-provider="$event === 'ollama' && (changed = true)" />
<div class="actions">
<button class="btn btn-flat" type="button" @click="$emit('open-provider-config')">
<span class="material-symbols-outlined">settings</span>
<span>View more providers</span>
</button>
<button class="btn btn-primary continue-btn" type="submit">
{{ changed ? "Continue" : "Skip" }}
<button class="btn btn-primary continue-btn" type="submit" :disabled="!changed">
Get started
</button>
</div>
</form>
Expand All @@ -21,12 +24,15 @@
<script lang="ts">
import ApiKeyForm from "@/components/ApiKeyForm.vue";
import ApiInfo from "@/components/configuration/ApiInfo.vue";
import ExternalLink from "@/components/common/ExternalLink.vue";

export default {
emits: ["submit", "open-provider-config"],

components: {
ApiKeyForm,
ApiInfo,
ExternalLink,
},

data() {
Expand Down