mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Merge pull request #2382 from glconti/main
feat(openai): add Azure OpenAI support with configuration options
This commit is contained in:
@ -1,2 +1,3 @@
|
|||||||
export const DEFAULT_MODEL = "gpt-3.5-turbo";
|
export const DEFAULT_MODEL = "gpt-3.5-turbo";
|
||||||
export const DEFAULT_MAX_TOKENS = 1024;
|
export const DEFAULT_MAX_TOKENS = 1024;
|
||||||
|
export const DEFAULT_AZURE_API_VERSION = "2024-10-21";
|
||||||
|
|||||||
@ -11,11 +11,12 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
Input,
|
Input,
|
||||||
|
Checkbox,
|
||||||
} from "@reactive-resume/ui";
|
} from "@reactive-resume/ui";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
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";
|
import { useOpenAiStore } from "@/client/stores/openai";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
@ -32,13 +33,21 @@ const formSchema = z.object({
|
|||||||
.default(""),
|
.default(""),
|
||||||
model: z.string().default(DEFAULT_MODEL),
|
model: z.string().default(DEFAULT_MODEL),
|
||||||
maxTokens: z.number().default(DEFAULT_MAX_TOKENS),
|
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>;
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
export const OpenAISettings = () => {
|
export const OpenAISettings = () => {
|
||||||
const { apiKey, setApiKey, baseURL, setBaseURL, model, setModel, maxTokens, setMaxTokens } =
|
const {
|
||||||
useOpenAiStore();
|
apiKey, setApiKey,
|
||||||
|
baseURL, setBaseURL,
|
||||||
|
model, setModel,
|
||||||
|
maxTokens, setMaxTokens,
|
||||||
|
isAzure, setIsAzure,
|
||||||
|
azureApiVersion, setAzureApiVersion
|
||||||
|
} = useOpenAiStore();
|
||||||
|
|
||||||
const isEnabled = !!apiKey;
|
const isEnabled = !!apiKey;
|
||||||
|
|
||||||
@ -49,11 +58,14 @@ export const OpenAISettings = () => {
|
|||||||
baseURL: baseURL ?? "",
|
baseURL: baseURL ?? "",
|
||||||
model: model ?? DEFAULT_MODEL,
|
model: model ?? DEFAULT_MODEL,
|
||||||
maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS,
|
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);
|
setApiKey(apiKey);
|
||||||
|
setIsAzure(isAzure);
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
setBaseURL(baseURL);
|
setBaseURL(baseURL);
|
||||||
}
|
}
|
||||||
@ -63,6 +75,9 @@ export const OpenAISettings = () => {
|
|||||||
if (maxTokens) {
|
if (maxTokens) {
|
||||||
setMaxTokens(maxTokens);
|
setMaxTokens(maxTokens);
|
||||||
}
|
}
|
||||||
|
if (azureApiVersion) {
|
||||||
|
setAzureApiVersion(azureApiVersion);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemove = () => {
|
const onRemove = () => {
|
||||||
@ -70,15 +85,24 @@ export const OpenAISettings = () => {
|
|||||||
setBaseURL(null);
|
setBaseURL(null);
|
||||||
setModel(DEFAULT_MODEL);
|
setModel(DEFAULT_MODEL);
|
||||||
setMaxTokens(DEFAULT_MAX_TOKENS);
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -99,11 +123,20 @@ export const OpenAISettings = () => {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</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.,
|
||||||
|
<code>https://your-resource.openai.azure.com</code>). Set the deployment name in the Model field
|
||||||
|
and specify the appropriate API version for your Azure deployment.
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<Trans>
|
<Trans>
|
||||||
You can also integrate with Ollama simply by setting the API key to
|
You can also integrate with Ollama simply by setting the API key to
|
||||||
`sk-1234567890abcdef` and the Base URL to your Ollama URL, i.e.
|
<code>sk-1234567890abcdef</code> and the Base URL to your Ollama URL, i.e.
|
||||||
`http://localhost:11434/v1`. You can also pick and choose models and set the max tokens
|
<code>http://localhost:11434/v1</code>. You can also pick and choose models and set the max tokens
|
||||||
as per your preference.
|
as per your preference.
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
@ -129,9 +162,22 @@ export const OpenAISettings = () => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t`Base URL`}</FormLabel>
|
<FormLabel>
|
||||||
|
{form.watch("isAzure")
|
||||||
|
? t`Azure OpenAI Resource URL`
|
||||||
|
: t`Base URL`
|
||||||
|
}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -142,7 +188,12 @@ export const OpenAISettings = () => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t`Model`}</FormLabel>
|
<FormLabel>
|
||||||
|
{form.watch("isAzure")
|
||||||
|
? t`Deployment Name`
|
||||||
|
: t`Model`
|
||||||
|
}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="text" placeholder={DEFAULT_MODEL} {...field} />
|
<Input type="text" placeholder={DEFAULT_MODEL} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -170,6 +221,44 @@ export const OpenAISettings = () => {
|
|||||||
</FormItem>
|
</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={(value) => {
|
||||||
|
field.onChange(Boolean(value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</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">
|
<div className="flex items-center space-x-2 self-end sm:col-start-2">
|
||||||
<Button type="submit" disabled={!form.formState.isValid}>
|
<Button type="submit" disabled={!form.formState.isValid}>
|
||||||
{isEnabled && <FloppyDiskIcon className="mr-2" />}
|
{isEnabled && <FloppyDiskIcon className="mr-2" />}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { OpenAI } from "openai";
|
|||||||
import { useOpenAiStore } from "@/client/stores/openai";
|
import { useOpenAiStore } from "@/client/stores/openai";
|
||||||
|
|
||||||
export const openai = () => {
|
export const openai = () => {
|
||||||
const { apiKey, baseURL } = useOpenAiStore.getState();
|
const { apiKey, baseURL, isAzure, azureApiVersion, model } = useOpenAiStore.getState();
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -12,6 +12,31 @@ 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) {
|
if (baseURL) {
|
||||||
return new OpenAI({
|
return new OpenAI({
|
||||||
apiKey,
|
apiKey,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
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 = {
|
type OpenAIStore = {
|
||||||
baseURL: string | null;
|
baseURL: string | null;
|
||||||
@ -12,6 +12,10 @@ type OpenAIStore = {
|
|||||||
setModel: (model: string | null) => void;
|
setModel: (model: string | null) => void;
|
||||||
maxTokens: number | null;
|
maxTokens: number | null;
|
||||||
setMaxTokens: (maxTokens: number | null) => void;
|
setMaxTokens: (maxTokens: number | null) => void;
|
||||||
|
isAzure: boolean;
|
||||||
|
setIsAzure: (isAzure: boolean) => void;
|
||||||
|
azureApiVersion: string | null;
|
||||||
|
setAzureApiVersion: (apiVersion: string | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useOpenAiStore = create<OpenAIStore>()(
|
export const useOpenAiStore = create<OpenAIStore>()(
|
||||||
@ -33,6 +37,14 @@ export const useOpenAiStore = create<OpenAIStore>()(
|
|||||||
setMaxTokens: (maxTokens: number | null) => {
|
setMaxTokens: (maxTokens: number | null) => {
|
||||||
set({ maxTokens });
|
set({ maxTokens });
|
||||||
},
|
},
|
||||||
|
isAzure: false,
|
||||||
|
setIsAzure: (isAzure: boolean) => {
|
||||||
|
set({ isAzure });
|
||||||
|
},
|
||||||
|
azureApiVersion: DEFAULT_AZURE_API_VERSION,
|
||||||
|
setAzureApiVersion: (azureApiVersion: string | null) => {
|
||||||
|
set({ azureApiVersion });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{ name: "openai" },
|
{ name: "openai" },
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user