Skip to content

Commit 95cada3

Browse files
authored
Merge pull request #69 from beekeeper-studio/feat/new-onboarding
New onboarding ui
2 parents 7cbd93a + 534b7eb commit 95cada3

File tree

6 files changed

+149
-53
lines changed

6 files changed

+149
-53
lines changed

src/App.vue

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,10 @@
88
:anthropicApiKey="anthropicApiKey" :googleApiKey="googleApiKey" @manage-models="handleManageModels"
99
@open-configuration="handleOpenConfiguration" />
1010
<Configuration v-model:visible="showConfiguration" :reactivePage="configurationPage" @close="closeConfiguration" />
11-
<div class="onboarding-screen-popover-container" v-if="showOnboarding">
12-
<div class="onboarding-screen-popover" v-kbd-trap="true">
13-
<button class="btn close-btn" @click="closeOnboardingScreen">
14-
<span class="material-symbols-outlined">close</span>
15-
</button>
16-
<OnboardingScreen @submit="closeOnboardingScreen" @open-provider-config="closeOnboardingScreenAndOpenProviderConfig" />
17-
</div>
18-
</div>
11+
<Dialog :visible="showOnboarding" :closable="false" :draggable="false">
12+
<OnboardingScreen @submit="closeOnboardingScreen"
13+
@open-provider-config="closeOnboardingScreenAndOpenProviderConfig" />
14+
</Dialog>
1915
</div>
2016
</template>
2117

src/assets/styles/pages/_chat-interface.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@
7171

7272
.plugin-title {
7373
text-align: center;
74-
margin-bottom: 2rem;
74+
margin-bottom: 1rem;
75+
max-width: 32rem;
76+
77+
p {
78+
color: var(--text);
79+
}
7580
}
7681

7782
.chat-messages {

src/assets/styles/pages/_onboarding-screen.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
max-width: 24rem;
5555
text-align: center;
5656

57+
h1 {
58+
margin-top: 0;
59+
}
60+
5761
.api-info {
5862
margin-top: 1.5rem;
5963
margin-bottom: 1rem;

src/components/ApiKeyForm.vue

Lines changed: 99 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,114 @@
11
<template>
2-
<div class="api-key-form">
3-
<BaseInput
4-
type="password"
5-
placeholder="sk-proj-..."
6-
v-model="openaiApiKey"
7-
>
2+
<div class="api-key-form" :class="{ 'dropdown-based': dropdownBased }">
3+
<div class="input-container" v-if="dropdownBased">
4+
<label for="select-provider">Provider</label>
5+
<Select for="select-provider" v-model="selectedProvider" :options="dropdownOptions" optionLabel="label"
6+
placeholder="Select a provider" />
7+
</div>
8+
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'openai'" type="password" placeholder="sk-proj-..."
9+
v-model="openaiApiKey">
810
<template #label>
911
{{ providerConfigs["openai"].displayName }}
12+
{{ dropdownBased ? "API Key" : '' }}
1013
</template>
1114
<template #helper>
1215
Get your <ExternalLink href="https://platform.openai.com/account/api-keys">API Key here.</ExternalLink>
1316
</template>
1417
</BaseInput>
15-
<BaseInput
16-
type="password"
17-
placeholder="sk-ant-..."
18-
v-model="anthropicApiKey"
19-
>
18+
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'anthropic'" type="password" placeholder="sk-ant-..."
19+
v-model="anthropicApiKey">
2020
<template #label>
2121
{{ providerConfigs["anthropic"].displayName }}
22+
{{ dropdownBased ? "API Key" : '' }}
2223
</template>
2324
<template #helper>
2425
Get your <ExternalLink href="https://console.anthropic.com/settings/keys">API Key here.</ExternalLink>
2526
</template>
2627
</BaseInput>
27-
<BaseInput
28-
type="password"
29-
placeholder="AIzaSy..."
30-
v-model="googleApiKey"
31-
>
28+
<BaseInput v-if="!dropdownBased || selectedProvider.value === 'google'" type="password" placeholder="AIzaSy..."
29+
v-model="googleApiKey">
3230
<template #label>
3331
{{ providerConfigs["google"].displayName }}
32+
{{ dropdownBased ? "API Key" : '' }}
3433
</template>
3534
<template #helper>
3635
Get your <ExternalLink href="https://aistudio.google.com/app/apikey">API Key here.</ExternalLink>
3736
</template>
3837
</BaseInput>
38+
<template v-if="selectedProvider.value === 'openaiCompat'">
39+
<BaseInput v-model="openaiCompatApiKey">
40+
<template #label>OpenAI Compatible API Key</template>
41+
</BaseInput>
42+
<BaseInput v-model="openaiCompatBaseUrl">
43+
<template #label>Base URL</template>
44+
</BaseInput>
45+
<BaseInput v-model="openaiCompatHeaders" type="textarea">
46+
<template #label>Custom Headers</template>
47+
</BaseInput>
48+
</template>
49+
<template v-if="selectedProvider.value === 'ollama'">
50+
<BaseInput v-model="ollamaBaseUrl">
51+
<template #label>Ollama Base URL</template>
52+
</BaseInput>
53+
<BaseInput v-model="ollamaHeaders" type="textarea">
54+
<template #label>Custom Headers</template>
55+
</BaseInput>
56+
</template>
3957
</div>
4058
</template>
4159

4260
<script lang="ts">
43-
import { mapState, mapActions } from "pinia";
61+
import { mapActions } from "pinia";
4462
import { useConfigurationStore } from "@/stores/configuration";
45-
import { providerConfigs } from "@/config";
63+
import { AvailableProviders, providerConfigs } from "@/config";
4664
import BaseInput from "./common/BaseInput.vue";
4765
import ExternalLink from "./common/ExternalLink.vue";
66+
import Select from 'primevue/select';
4867
4968
export default {
5069
name: "ApiKeyForm",
5170
5271
components: {
5372
BaseInput,
5473
ExternalLink,
74+
Select,
5575
},
5676
57-
emits: ["change"],
77+
emits: ["change", "changeProvider"],
78+
79+
props: {
80+
dropdownBased: Boolean,
81+
},
5882
5983
data() {
84+
const config = useConfigurationStore();
85+
6086
return {
61-
openaiApiKey: "",
62-
anthropicApiKey: "",
63-
googleApiKey: "",
87+
selectedProvider: {
88+
label: "" as typeof providerConfigs[AvailableProviders]['displayName'],
89+
value: "" as AvailableProviders,
90+
},
91+
openaiApiKey: config['providers.openai.apiKey'],
92+
anthropicApiKey: config['providers.anthropic.apiKey'],
93+
googleApiKey: config['providers.google.apiKey'],
94+
ollamaBaseUrl: config.providers_ollama_baseUrl,
95+
ollamaHeaders: config.providers_ollama_headers,
96+
openaiCompatBaseUrl: config.providers_openaiCompat_baseUrl,
97+
openaiCompatApiKey: config.providers_openaiCompat_apiKey,
98+
openaiCompatHeaders: config.providers_openaiCompat_headers,
6499
};
65100
},
66101
67102
computed: {
68-
...mapState(useConfigurationStore, [
69-
"providers.openai.apiKey",
70-
"providers.anthropic.apiKey",
71-
"providers.google.apiKey",
72-
]),
73103
providerConfigs() {
74104
return providerConfigs;
75105
},
106+
dropdownOptions() {
107+
return Object.keys(providerConfigs).map((provider) => ({
108+
label: this.providerConfigs[provider].displayName,
109+
value: provider,
110+
}));
111+
},
76112
},
77113
78114
watch: {
@@ -88,16 +124,46 @@ export default {
88124
this.configure("providers.google.apiKey", this.googleApiKey);
89125
this.$emit("change");
90126
},
127+
ollamaBaseUrl() {
128+
this.configure("providers_ollama_baseUrl", this.ollamaBaseUrl);
129+
this.$emit("change");
130+
},
131+
ollamaHeaders() {
132+
this.configure("providers_ollama_headers", this.ollamaHeaders);
133+
this.$emit("change");
134+
},
135+
openaiCompatBaseUrl() {
136+
this.configure("providers_openaiCompat_baseUrl", this.openaiCompatBaseUrl);
137+
this.$emit("change");
138+
},
139+
openaiCompatApiKey() {
140+
this.configure("providers_openaiCompat_apiKey", this.openaiCompatApiKey);
141+
this.$emit("change");
142+
},
143+
openaiCompatHeaders() {
144+
this.configure("providers_openaiCompat_headers", this.openaiCompatHeaders);
145+
this.$emit("change");
146+
},
147+
selectedProvider() {
148+
this.$emit("changeProvider", this.selectedProvider.value);
149+
},
91150
},
92151
93152
methods: {
94153
...mapActions(useConfigurationStore, ["configure"]),
95154
},
96-
97-
mounted() {
98-
this.openaiApiKey = this["providers.openai.apiKey"];
99-
this.anthropicApiKey = this["providers.anthropic.apiKey"];
100-
this.googleApiKey = this["providers.google.apiKey"];
101-
},
102155
};
103156
</script>
157+
158+
<style scoped>
159+
.input-container {
160+
display: flex;
161+
flex-direction: column;
162+
gap: 0.5rem;
163+
justify-content: flex-start;
164+
165+
label {
166+
align-self: flex-start;
167+
}
168+
}
169+
</style>

src/components/ChatInterface.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
<span class="title-popup">Settings</span>
1212
</button>
1313
</div>
14-
<h1 class="plugin-title">AI Shell</h1>
14+
<div class="plugin-title">
15+
<h1>AI Shell</h1>
16+
<p>
17+
The AI Shell can see your table schemas, and (with your permission)
18+
run {{ sqlOrCode }} to answer your questions.
19+
<ExternalLink href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/">Learn more</ExternalLink>.
20+
</p>
21+
</div>
1522
<div class="chat-messages">
1623
<message
1724
v-for="(message, index) in messages"
@@ -88,6 +95,8 @@ import { RootBinding } from "@/plugins/appEvent";
8895
import { useInternalDataStore } from "@/stores/internalData";
8996
import BaseInput from "@/components/common/BaseInput.vue";
9097
import PromptInput from "@/components/common/PromptInput.vue";
98+
import { getConnectionInfo } from "@beekeeperstudio/plugin";
99+
import ExternalLink from "@/components/common/ExternalLink.vue";
91100
92101
export default {
93102
name: "ChatInterface",
@@ -97,6 +106,7 @@ export default {
97106
Markdown,
98107
BaseInput,
99108
PromptInput,
109+
ExternalLink,
100110
},
101111
102112
emits: ["manage-models", "open-configuration"],
@@ -133,6 +143,7 @@ export default {
133143
isAtBottom: true,
134144
showFullError: false,
135145
noModelError: false,
146+
sqlOrCode: "SQL",
136147
};
137148
},
138149
@@ -214,6 +225,14 @@ export default {
214225
},
215226
216227
async mounted() {
228+
getConnectionInfo().then((connection) => {
229+
if (connection.databaseType === "mongodb"
230+
|| connection.connectionType === "surrealdb"
231+
|| connection.connectionType === "redis") {
232+
this.sqlOrCode = "Code";
233+
}
234+
});
235+
217236
const scrollContainer = this.$refs.scrollContainerRef as HTMLElement;
218237
scrollContainer.addEventListener("scroll", () => {
219238
// Calculate if we're near bottom (within 50px of bottom)

src/components/OnboardingScreen.vue

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
<template>
22
<div class="onboarding-screen">
3-
<h1>Welcome to AI Shell</h1>
4-
<p>Before we start, please enter your API keys below.</p>
5-
<ApiInfo />
3+
<h1>Welcome to the AI Shell</h1>
4+
<p>
5+
Your AI agent can explore your database and (with your permission) write code to
6+
answer your questions. Enter the API key below for your preffered agent to get started.
7+
</p>
8+
<p>
9+
<ExternalLink href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/">Learn more</ExternalLink>
10+
-
11+
<ExternalLink href="https://www.youtube.com/watch?v=pAhQUFDeiwc">90s Walkthough Video</ExternalLink>
12+
</p>
613
<form @submit.prevent="$emit('submit')">
7-
<ApiKeyForm @change="changed = true" />
14+
<ApiKeyForm dropdown-based @change="changed = true" @change-provider="$event === 'ollama' && (changed = true)" />
815
<div class="actions">
9-
<button class="btn btn-flat" type="button" @click="$emit('open-provider-config')">
10-
<span class="material-symbols-outlined">settings</span>
11-
<span>View more providers</span>
12-
</button>
13-
<button class="btn btn-primary continue-btn" type="submit">
14-
{{ changed ? "Continue" : "Skip" }}
16+
<button class="btn btn-primary continue-btn" type="submit" :disabled="!changed">
17+
Get started
1518
</button>
1619
</div>
1720
</form>
@@ -21,12 +24,15 @@
2124
<script lang="ts">
2225
import ApiKeyForm from "@/components/ApiKeyForm.vue";
2326
import ApiInfo from "@/components/configuration/ApiInfo.vue";
27+
import ExternalLink from "@/components/common/ExternalLink.vue";
28+
2429
export default {
2530
emits: ["submit", "open-provider-config"],
2631
2732
components: {
2833
ApiKeyForm,
2934
ApiInfo,
35+
ExternalLink,
3036
},
3137
3238
data() {

0 commit comments

Comments
 (0)