feat(openai): add Azure OpenAI support with configuration options

This commit is contained in:
Gianluigi Conti
2025-09-09 10:14:34 +02:00
parent b995a6b6c0
commit 098d67cd8c
4 changed files with 143 additions and 12 deletions

View File

@ -1,2 +1,3 @@
export const DEFAULT_MODEL = "gpt-3.5-turbo";
export const DEFAULT_MAX_TOKENS = 1024;
export const DEFAULT_AZURE_API_VERSION = "2025-01-01-preview";

View File

@ -11,11 +11,12 @@ import {
FormLabel,
FormMessage,
Input,
Checkbox,
} from "@reactive-resume/ui";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL } from "@/client/constants/llm";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_AZURE_API_VERSION } from "@/client/constants/llm";
import { useOpenAiStore } from "@/client/stores/openai";
const formSchema = z.object({
@ -32,13 +33,21 @@ const formSchema = z.object({
.default(""),
model: z.string().default(DEFAULT_MODEL),
maxTokens: z.number().default(DEFAULT_MAX_TOKENS),
isAzure: z.boolean().default(false),
azureApiVersion: z.string().default(DEFAULT_AZURE_API_VERSION),
});
type FormValues = z.infer<typeof formSchema>;
export const OpenAISettings = () => {
const { apiKey, setApiKey, baseURL, setBaseURL, model, setModel, maxTokens, setMaxTokens } =
useOpenAiStore();
const {
apiKey, setApiKey,
baseURL, setBaseURL,
model, setModel,
maxTokens, setMaxTokens,
isAzure, setIsAzure,
azureApiVersion, setAzureApiVersion
} = useOpenAiStore();
const isEnabled = !!apiKey;
@ -49,11 +58,14 @@ export const OpenAISettings = () => {
baseURL: baseURL ?? "",
model: model ?? DEFAULT_MODEL,
maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS,
isAzure: isAzure ?? false,
azureApiVersion: azureApiVersion ?? DEFAULT_AZURE_API_VERSION,
},
});
const onSubmit = ({ apiKey, baseURL, model, maxTokens }: FormValues) => {
const onSubmit = ({ apiKey, baseURL, model, maxTokens, isAzure, azureApiVersion }: FormValues) => {
setApiKey(apiKey);
setIsAzure(isAzure);
if (baseURL) {
setBaseURL(baseURL);
}
@ -63,6 +75,9 @@ export const OpenAISettings = () => {
if (maxTokens) {
setMaxTokens(maxTokens);
}
if (azureApiVersion) {
setAzureApiVersion(azureApiVersion);
}
};
const onRemove = () => {
@ -70,15 +85,24 @@ export const OpenAISettings = () => {
setBaseURL(null);
setModel(DEFAULT_MODEL);
setMaxTokens(DEFAULT_MAX_TOKENS);
form.reset({ apiKey: "", baseURL: "", model: DEFAULT_MODEL, maxTokens: DEFAULT_MAX_TOKENS });
setIsAzure(false);
setAzureApiVersion(DEFAULT_AZURE_API_VERSION);
form.reset({
apiKey: "",
baseURL: "",
model: DEFAULT_MODEL,
maxTokens: DEFAULT_MAX_TOKENS,
isAzure: false,
azureApiVersion: DEFAULT_AZURE_API_VERSION
});
};
return (
<div className="space-y-6">
<div>
<h3 className="text-2xl font-bold leading-relaxed tracking-tight">{t`OpenAI/Ollama Integration`}</h3>
<h3 className="text-2xl font-bold leading-relaxed tracking-tight">{t`OpenAI/Azure OpenAI/Ollama Integration`}</h3>
<p className="leading-relaxed opacity-75">
{t`You can make use of the OpenAI API to help you generate content, or improve your writing while composing your resume.`}
{t`You can make use of the OpenAI API, Azure OpenAI, or Ollama to help you generate content, or improve your writing while composing your resume.`}
</p>
</div>
@ -99,6 +123,15 @@ export const OpenAISettings = () => {
</Trans>
</p>
<p>
<Trans>
You can also integrate with Azure OpenAI by enabling the "Use Azure OpenAI" checkbox
and setting the Resource URL to your Azure OpenAI resource (e.g.,
`https://your-resource.openai.azure.com`). Set the deployment name in the Model field
and specify the appropriate API version for your Azure deployment.
</Trans>
</p>
<p>
<Trans>
You can also integrate with Ollama simply by setting the API key to
@ -129,9 +162,22 @@ export const OpenAISettings = () => {
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>{t`Base URL`}</FormLabel>
<FormLabel>
{form.watch("isAzure")
? t`Azure OpenAI Resource URL`
: t`Base URL`
}
</FormLabel>
<FormControl>
<Input type="text" placeholder="http://localhost:11434/v1" {...field} />
<Input
type="text"
placeholder={
form.watch("isAzure")
? "https://your-resource.openai.azure.com"
: "http://localhost:11434/v1"
}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -142,7 +188,12 @@ export const OpenAISettings = () => {
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>{t`Model`}</FormLabel>
<FormLabel>
{form.watch("isAzure")
? t`Deployment Name`
: t`Model`
}
</FormLabel>
<FormControl>
<Input type="text" placeholder={DEFAULT_MODEL} {...field} />
</FormControl>
@ -170,6 +221,42 @@ export const OpenAISettings = () => {
</FormItem>
)}
/>
<FormField
name="isAzure"
control={form.control}
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>{t`Use Azure OpenAI`}</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="azureApiVersion"
control={form.control}
render={({ field }) => (
<FormItem className={form.watch("isAzure") ? "" : "opacity-50"}>
<FormLabel>{t`Azure API Version`}</FormLabel>
<FormControl>
<Input
type="text"
placeholder={DEFAULT_AZURE_API_VERSION}
disabled={!form.watch("isAzure")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center space-x-2 self-end sm:col-start-2">
<Button type="submit" disabled={!form.formState.isValid}>
{isEnabled && <FloppyDisk className="mr-2" />}

View File

@ -4,7 +4,7 @@ import { OpenAI } from "openai";
import { useOpenAiStore } from "@/client/stores/openai";
export const openai = () => {
const { apiKey, baseURL } = useOpenAiStore.getState();
const { apiKey, baseURL, isAzure, azureApiVersion, model } = useOpenAiStore.getState();
if (!apiKey) {
throw new Error(
@ -12,6 +12,37 @@ export const openai = () => {
);
}
if (isAzure) {
if (!baseURL) {
throw new Error(
t`Azure OpenAI Base URL is required when using Azure OpenAI.`,
);
}
if (!model) {
throw new Error(
t`Azure OpenAI deployment name (model) is required when using Azure OpenAI.`,
);
if (!azureApiVersion) {
throw new Error(
t`Azure OpenAI API version is required when using Azure OpenAI.`,
);
}
}
// Construct Azure OpenAI URL: https://your-resource.openai.azure.com/openai/deployments/your-deployment
const azureBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const constructedURL = `${azureBaseURL}/openai/deployments/${model}`;
return new OpenAI({
apiKey,
baseURL: constructedURL,
defaultQuery: { "api-version": azureApiVersion ?? undefined },
dangerouslyAllowBrowser: true,
});
}
if (baseURL) {
return new OpenAI({
apiKey,

View File

@ -1,7 +1,7 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL } from "../constants/llm";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_AZURE_API_VERSION } from "../constants/llm";
type OpenAIStore = {
baseURL: string | null;
@ -12,6 +12,10 @@ type OpenAIStore = {
setModel: (model: string | null) => void;
maxTokens: number | null;
setMaxTokens: (maxTokens: number | null) => void;
isAzure: boolean;
setIsAzure: (isAzure: boolean) => void;
azureApiVersion: string | null;
setAzureApiVersion: (apiVersion: string | null) => void;
};
export const useOpenAiStore = create<OpenAIStore>()(
@ -33,6 +37,14 @@ export const useOpenAiStore = create<OpenAIStore>()(
setMaxTokens: (maxTokens: number | null) => {
set({ maxTokens });
},
isAzure: false,
setIsAzure: (isAzure: boolean) => {
set({ isAzure });
},
azureApiVersion: DEFAULT_AZURE_API_VERSION,
setAzureApiVersion: (azureApiVersion: string | null) => {
set({ azureApiVersion });
},
}),
{ name: "openai" },
),