From 7f80a2f9e6899bc6de845fd4258b42613ab1d486 Mon Sep 17 00:00:00 2001 From: Mohammad Azmi Date: Mon, 26 Jan 2026 21:50:26 +0700 Subject: [PATCH 1/7] use zod for configuration schema --- src/stores/configuration.ts | 104 +++++------------------------- src/stores/configurationSchema.ts | 87 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 89 deletions(-) create mode 100644 src/stores/configurationSchema.ts diff --git a/src/stores/configuration.ts b/src/stores/configuration.ts index 2e3a894..17d2360 100644 --- a/src/stores/configuration.ts +++ b/src/stores/configuration.ts @@ -17,94 +17,16 @@ import { setData, setEncryptedData, } from "@beekeeperstudio/plugin"; -import { - AvailableProviders, - disabledModelsByDefault, - providerConfigs, -} from "@/config"; +import { AvailableProviders, providerConfigs } from "@/config"; import { useChatStore } from "./chat"; - -type Model = { - id: string; - displayName: string; -}; - -type Configurable = { - // ==== GENERAL ==== - /** Append custom instructions to the default system instructions. */ - customInstructions: string; - /** Append custom instructions to the default system instructions. - * It's applied based on the connection ID */ - customConnectionInstructions: { - workspaceId: number; - connectionId: number; - instructions: string; - }[]; - allowExecutionOfReadOnlyQueries: boolean; - enableAutoCompact: boolean; - - // ==== MODELS ==== - /** List of disabled models by id. */ - disabledModels: { providerId: AvailableProviders; modelId: string }[]; - /** Models that are removed are not shown in the UI and cannot be enabled. */ - removedModels: { providerId: AvailableProviders; modelId: string }[]; - providers_openaiCompat_baseUrl: string; - providers_openaiCompat_headers: string; - providers_ollama_baseUrl: string; - providers_ollama_headers: string; -} & { - // User defined models - [K in AvailableProviders as `providers_${K}_models`]: Model[]; -}; - -type EncryptedConfigurable = { - "providers.openai.apiKey": string; - "providers.anthropic.apiKey": string; - "providers.google.apiKey": string; - providers_openaiCompat_apiKey: string; -}; - -type ConfigurationState = Configurable & EncryptedConfigurable; - -export type ConfigurationKey = keyof ConfigurationState; - -const encryptedConfigKeys: (keyof EncryptedConfigurable)[] = [ - "providers.openai.apiKey", - "providers.anthropic.apiKey", - "providers.google.apiKey", - "providers_openaiCompat_apiKey", -]; - -const defaultConfiguration: ConfigurationState = { - // ==== GENERAL ==== - customInstructions: "", - customConnectionInstructions: [], - allowExecutionOfReadOnlyQueries: false, - enableAutoCompact: true, - - // ==== MODELS ==== - "providers.openai.apiKey": "", - "providers.anthropic.apiKey": "", - "providers.google.apiKey": "", - providers_openaiCompat_baseUrl: "", - providers_openaiCompat_apiKey: "", - providers_openaiCompat_headers: "", - providers_ollama_baseUrl: "http://localhost:11434", - providers_ollama_headers: "", - providers_openai_models: [], - providers_anthropic_models: [], - providers_google_models: [], - providers_openaiCompat_models: [], - providers_ollama_models: [], - disabledModels: disabledModelsByDefault, - removedModels: [], -}; - -function isEncryptedConfig( - config: string, -): config is keyof EncryptedConfigurable { - return encryptedConfigKeys.includes(config as keyof EncryptedConfigurable); -} +import { + Configurable, + ConfigurationState, + defaultConfiguration, + encryptedConfigurableSchema, + isEncryptedConfig, + Model, +} from "./configurationSchema"; export const useConfigurationStore = defineStore("configuration", { state: (): ConfigurationState => { @@ -113,7 +35,9 @@ export const useConfigurationStore = defineStore("configuration", { getters: { apiKeyExists(): boolean { - return encryptedConfigKeys.some((key) => this[key].trim() !== ""); + return Object.keys(encryptedConfigurableSchema.shape).some( + (key) => this[key].trim() !== "", + ); }, getModelsByProvider: (state) => { @@ -252,7 +176,9 @@ export const useConfigurationStore = defineStore("configuration", { const connection = useChatStore().connectionInfo; const connectionId = connection.id; const workspaceId = connection.workspaceId; - const connectionInstructions = _.cloneDeep(this.customConnectionInstructions); + const connectionInstructions = _.cloneDeep( + this.customConnectionInstructions, + ); const idx = connectionInstructions.findIndex( (i) => i.connectionId === connectionId && i.workspaceId === workspaceId, ); diff --git a/src/stores/configurationSchema.ts b/src/stores/configurationSchema.ts new file mode 100644 index 0000000..72aa40c --- /dev/null +++ b/src/stores/configurationSchema.ts @@ -0,0 +1,87 @@ +import { disabledModelsByDefault, providerConfigs } from "@/config"; +import z from "zod/v3"; + +function zodEnumFromObjKeys( + obj: Record, +): z.ZodEnum<[K, ...K[]]> { + const [firstKey, ...otherKeys] = Object.keys(obj) as K[]; + return z.enum([firstKey, ...otherKeys]); +} + +const ModelSchema = z.object({ + id: z.string(), + displayName: z.string(), +}); + +const configurableSchema = z.object({ + // ==== GENERAL ==== + /** Append custom instructions to the default system instructions. */ + customInstructions: z.string().default(""), + /** Append custom instructions to the default system instructions. + * It's applied based on the connection ID */ + customConnectionInstructions: z + .array( + z.object({ + workspaceId: z.number(), + connectionId: z.number(), + instructions: z.string(), + }), + ) + .default([]), + allowExecutionOfReadOnlyQueries: z.boolean().default(false), + enableAutoCompact: z.boolean().default(true), + + // ==== MODELS ==== + /** List of disabled models by id. */ + disabledModels: z + .array( + z.object({ + providerId: zodEnumFromObjKeys(providerConfigs), + modelId: z.string(), + }), + ) + .default(disabledModelsByDefault), + removedModels: z + .array( + z.object({ + providerId: zodEnumFromObjKeys(providerConfigs), + modelId: z.string(), + }), + ) + .default([]), + + providers_openaiCompat_baseUrl: z.string().default(""), + providers_openaiCompat_headers: z.string().default(""), + providers_ollama_baseUrl: z.string().default("http://localhost:11434"), + providers_ollama_headers: z.string().default(""), + + providers_mock_models: z.array(ModelSchema).default([]), + providers_openai_models: z.array(ModelSchema).default([]), + providers_anthropic_models: z.array(ModelSchema).default([]), + providers_google_models: z.array(ModelSchema).default([]), + providers_openaiCompat_models: z.array(ModelSchema).default([]), + providers_ollama_models: z.array(ModelSchema).default([]), +}); + +export const encryptedConfigurableSchema = z.object({ + "providers.openai.apiKey": z.string().default(""), + "providers.anthropic.apiKey": z.string().default(""), + "providers.google.apiKey": z.string().default(""), + providers_openaiCompat_apiKey: z.string().default(""), +}); + +export type Model = z.infer; +export type Configurable = z.infer; +export type ConfigurationState = z.infer & + z.infer; + +export function isEncryptedConfig( + config: string, +): config is keyof z.infer { + return config in encryptedConfigurableSchema.shape; +} + +export const defaultConfiguration = { + ...configurableSchema.parse({}), + ...encryptedConfigurableSchema.parse({}), +}; From 69cd8292b1f945dc9c8d03795a9206cb3ec771b5 Mon Sep 17 00:00:00 2001 From: Mohammad Azmi Date: Wed, 28 Jan 2026 21:58:34 +0700 Subject: [PATCH 2/7] add workspace connection instructions --- package.json | 2 +- src/App.vue | 6 +- src/assets/styles/components/_base-input.scss | 9 +- src/components/common/BaseInput.vue | 5 +- .../configuration/Configuration.vue | 9 +- .../configuration/GeneralConfiguration.vue | 40 +-------- .../InstructionsConfiguration.vue | 83 +++++++++++++++++++ src/stores/chat.ts | 26 +++++- src/stores/configuration.ts | 33 +++++--- src/stores/configurationSchema.ts | 18 +++- src/stores/internalData.ts | 17 +--- yarn.lock | 8 +- 12 files changed, 163 insertions(+), 93 deletions(-) create mode 100644 src/components/configuration/InstructionsConfiguration.vue diff --git a/package.json b/package.json index 2889356..6622f06 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@ai-sdk/openai": "^3.0.8", "@ai-sdk/openai-compatible": "^2.0.4", "@ai-sdk/vue": "^3.0.28", - "@beekeeperstudio/plugin": "^1.6.0", + "@beekeeperstudio/plugin": "^1.7.1-beta.0", "@beekeeperstudio/ui-kit": "0.3.1", "@langchain/core": "^0.3.61", "@material-symbols/font-400": "^0.31.2", diff --git a/src/App.vue b/src/App.vue index 79e3ae8..9006de8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -24,7 +24,7 @@ import Configuration, { PageId as ConfigurationPageId, } from "@/components/configuration/Configuration.vue"; import OnboardingScreen from "./components/OnboardingScreen.vue"; -import { getData, log } from "@beekeeperstudio/plugin"; +import { appStorage, log } from "@beekeeperstudio/plugin"; import { Dialog } from "primevue"; type Page = "starting" | "chat-interface"; @@ -129,10 +129,10 @@ export default { window.location.reload(); }, reloadDelay); try { - await getData(); + await appStorage.getItem("test"); } catch (e) { } finally { - // Cancel reload if getData() succeeds or fails quickly + // Cancel reload if it succeeds or fails quickly clearTimeout(reloadTimer); } }, diff --git a/src/assets/styles/components/_base-input.scss b/src/assets/styles/components/_base-input.scss index 4d1e806..f2903f4 100644 --- a/src/assets/styles/components/_base-input.scss +++ b/src/assets/styles/components/_base-input.scss @@ -24,7 +24,6 @@ align-items: stretch; display: grid; - &::after, textarea { width: auto; min-width: 1rem; @@ -33,15 +32,9 @@ font-size: 0.831rem; padding: 0.5rem 0.75rem; margin: 0; - resize: none; + resize: vertical; appearance: none; } - - &::after { - content: attr(data-value) " "; - visibility: hidden; - white-space: pre-wrap; - } } &.switch { diff --git a/src/components/common/BaseInput.vue b/src/components/common/BaseInput.vue index 5c155f9..d59a1d3 100644 --- a/src/components/common/BaseInput.vue +++ b/src/components/common/BaseInput.vue @@ -10,10 +10,7 @@ >

-
+