mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 21:35:19 +00:00
feat: more llm provider options (google, mistral) (#28)
* feat: add google provider * fix: default for google model * feat: multiple providers * fix: defaults from env for login form * fix: add mistral to env files * chore: delete unused code * chore: revert database url to original * fix: render default value for api key from env on server * fix: type errors during compilation --------- Co-authored-by: Vasily Zubarev <me@vas3k.ru>
This commit is contained in:
@@ -31,20 +31,15 @@ export async function analyzeFileAction(
|
||||
const user = await getCurrentUser()
|
||||
|
||||
if (!file || file.userId !== user.id) {
|
||||
return { success: false, error: "File not found or does not belong to the user" }
|
||||
}
|
||||
|
||||
const apiKey = settings.openai_api_key || config.ai.openaiApiKey || ""
|
||||
if (!apiKey) {
|
||||
return { success: false, error: "OpenAI API key is not set" }
|
||||
}
|
||||
|
||||
if (isAiBalanceExhausted(user)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You used all of your pre-paid AI scans, please upgrade your account or buy new subscription plan",
|
||||
return { success: false, error: "File not found or does not belong to the user" }
|
||||
}
|
||||
|
||||
if (isAiBalanceExhausted(user)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You used all of your pre-paid AI scans, please upgrade your account or buy new subscription plan",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(user)) {
|
||||
return {
|
||||
@@ -70,7 +65,7 @@ export async function analyzeFileAction(
|
||||
|
||||
const schema = fieldsToJsonSchema(fields)
|
||||
|
||||
const results = await analyzeTransaction(prompt, schema, attachments, apiKey, file.id, user.id)
|
||||
const results = await analyzeTransaction(prompt, schema, attachments, file.id, user.id)
|
||||
|
||||
console.log("Analysis results:", results)
|
||||
|
||||
@@ -98,7 +93,7 @@ export async function saveFileAsTransactionAction(
|
||||
const file = await getFileById(fileId, user.id)
|
||||
if (!file) throw new Error("File not found")
|
||||
|
||||
// Create transaction
|
||||
// Create transaction
|
||||
const transaction = await createTransaction(user.id, validatedForm.data)
|
||||
|
||||
// Move file to processed location
|
||||
|
||||
@@ -38,14 +38,14 @@ export default async function UnsortedPage() {
|
||||
{files.length > 1 && <AnalyzeAllButton />}
|
||||
</header>
|
||||
|
||||
{config.selfHosted.isEnabled && !settings.openai_api_key && (
|
||||
{config.selfHosted.isEnabled && !settings.openai_api_key && !settings.google_api_key && !settings.mistral_api_key && (
|
||||
<Alert>
|
||||
<Settings className="h-4 w-4 mt-2" />
|
||||
<div className="flex flex-row justify-between pt-2">
|
||||
<div className="flex flex-col">
|
||||
<AlertTitle>ChatGPT API Key is required for analyzing files</AlertTitle>
|
||||
<AlertTitle>LLM provider API Key is required for analyzing files</AlertTitle>
|
||||
<AlertDescription>
|
||||
Please set your OpenAI API key in the settings to use the analyze form.
|
||||
Please set your LLM provider API key in the settings to use the analyze form.
|
||||
</AlertDescription>
|
||||
</div>
|
||||
<Link href="/settings/llm">
|
||||
|
||||
@@ -13,11 +13,20 @@ export async function selfHostedGetStartedAction(formData: FormData) {
|
||||
await createUserDefaults(user.id)
|
||||
}
|
||||
|
||||
const openaiApiKey = formData.get("openai_api_key")
|
||||
if (openaiApiKey) {
|
||||
await updateSettings(user.id, "openai_api_key", openaiApiKey as string)
|
||||
const apiKeys = [
|
||||
"openai_api_key",
|
||||
"google_api_key",
|
||||
"mistral_api_key"
|
||||
]
|
||||
|
||||
for (const key of apiKeys) {
|
||||
const value = formData.get(key)
|
||||
if (value) {
|
||||
await updateSettings(user.id, key, value as string)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const defaultCurrency = formData.get("default_currency")
|
||||
if (defaultCurrency) {
|
||||
await updateSettings(user.id, "default_currency", defaultCurrency as string)
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { FormSelectCurrency } from "@/components/forms/select-currency"
|
||||
import { FormInput } from "@/components/forms/simple"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardDescription, CardTitle } from "@/components/ui/card"
|
||||
import { ColoredText } from "@/components/ui/colored-text"
|
||||
import config from "@/lib/config"
|
||||
import { DEFAULT_CURRENCIES, DEFAULT_SETTINGS } from "@/models/defaults"
|
||||
import { getSelfHostedUser } from "@/models/users"
|
||||
import { ShieldAlert } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import { redirect } from "next/navigation"
|
||||
import { selfHostedGetStartedAction } from "../actions"
|
||||
import { PROVIDERS } from "@/lib/llm-providers"
|
||||
import SelfHostedSetupFormClient from "./setup-form-client"
|
||||
|
||||
export default async function SelfHostedWelcomePage() {
|
||||
if (!config.selfHosted.isEnabled) {
|
||||
@@ -35,6 +32,14 @@ export default async function SelfHostedWelcomePage() {
|
||||
redirect(config.selfHosted.redirectUrl)
|
||||
}
|
||||
|
||||
// Собираем дефолтные ключи для всех провайдеров
|
||||
const defaultProvider = PROVIDERS[0].key
|
||||
const defaultApiKeys: Record<string, string> = {
|
||||
openai: config.ai.openaiApiKey ?? "",
|
||||
google: config.ai.googleApiKey ?? "",
|
||||
mistral: config.ai.mistralApiKey ?? "",
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-xl mx-auto p-8 flex flex-col items-center justify-center gap-4">
|
||||
<Image src="/logo/512.png" alt="Logo" width={144} height={144} className="w-36 h-36" />
|
||||
@@ -43,36 +48,7 @@ export default async function SelfHostedWelcomePage() {
|
||||
</CardTitle>
|
||||
<CardDescription className="flex flex-col gap-4 text-center text-lg">
|
||||
<p>Welcome to your own instance of TaxHacker. Let's set up a couple of settings to get started.</p>
|
||||
|
||||
<form action={selfHostedGetStartedAction} className="flex flex-col gap-8 pt-8">
|
||||
<div>
|
||||
<FormInput title="OpenAI API Key" name="openai_api_key" defaultValue={config.ai.openaiApiKey} />
|
||||
|
||||
<small className="text-xs text-muted-foreground">
|
||||
Get your API key from{" "}
|
||||
<a
|
||||
href="https://platform.openai.com/settings/organization/api-keys"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
>
|
||||
OpenAI Platform Console
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-center gap-2">
|
||||
<FormSelectCurrency
|
||||
title="Default Currency"
|
||||
name="default_currency"
|
||||
defaultValue={DEFAULT_SETTINGS.find((s) => s.code === "default_currency")?.value ?? "EUR"}
|
||||
currencies={DEFAULT_CURRENCIES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-auto p-6">
|
||||
Get Started
|
||||
</Button>
|
||||
</form>
|
||||
<SelfHostedSetupFormClient defaultProvider={defaultProvider} defaultApiKeys={defaultApiKeys} />
|
||||
</CardDescription>
|
||||
</Card>
|
||||
)
|
||||
|
||||
76
app/(auth)/self-hosted/setup-form-client.tsx
Normal file
76
app/(auth)/self-hosted/setup-form-client.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
"use client"
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import { FormSelectCurrency } from "@/components/forms/select-currency"
|
||||
import { FormInput } from "@/components/forms/simple"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { DEFAULT_CURRENCIES, DEFAULT_SETTINGS } from "@/models/defaults"
|
||||
import { selfHostedGetStartedAction } from "../actions"
|
||||
import { FormSelect } from "@/components/forms/simple"
|
||||
import { PROVIDERS } from "@/lib/llm-providers"
|
||||
|
||||
type Props = {
|
||||
defaultProvider: string
|
||||
defaultApiKeys: Record<string, string>
|
||||
}
|
||||
|
||||
export default function SelfHostedSetupFormClient({ defaultProvider, defaultApiKeys }: Props) {
|
||||
const [provider, setProvider] = useState(defaultProvider)
|
||||
const selected = PROVIDERS.find(p => p.key === provider)!
|
||||
const getDefaultApiKey = useCallback((providerKey: string) => defaultApiKeys[providerKey] ?? "", [defaultApiKeys])
|
||||
|
||||
const [apiKey, setApiKey] = useState(getDefaultApiKey(provider))
|
||||
const userTyped = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!userTyped.current) {
|
||||
setApiKey(getDefaultApiKey(provider))
|
||||
}
|
||||
userTyped.current = false
|
||||
}, [provider, getDefaultApiKey])
|
||||
|
||||
return (
|
||||
<form action={selfHostedGetStartedAction} className="flex flex-col gap-8 pt-8">
|
||||
<div className="flex flex-row gap-4 items-center justify-center">
|
||||
<FormSelect
|
||||
title="LLM provider"
|
||||
name="provider"
|
||||
value={provider}
|
||||
onValueChange={setProvider}
|
||||
items={PROVIDERS.map(p => ({
|
||||
code: p.key,
|
||||
name: p.label,
|
||||
logo: p.logo
|
||||
}))}
|
||||
/>
|
||||
<FormSelectCurrency
|
||||
title="Default Currency"
|
||||
name="default_currency"
|
||||
defaultValue={DEFAULT_SETTINGS.find((s) => s.code === "default_currency")?.value ?? "EUR"}
|
||||
currencies={DEFAULT_CURRENCIES}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormInput
|
||||
title={`${selected.label} API Key`}
|
||||
name={selected.apiKeyName}
|
||||
value={apiKey ?? ""}
|
||||
onChange={e => {
|
||||
setApiKey(e.target.value)
|
||||
userTyped.current = true
|
||||
}}
|
||||
placeholder={selected.placeholder}
|
||||
/>
|
||||
<small className="text-xs text-muted-foreground flex justify-center mt-2">
|
||||
Get key from
|
||||
{"\u00A0"}
|
||||
<a href={selected.help.url} target="_blank" className="underline">
|
||||
{selected.help.label}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
<Button type="submit" className="w-auto p-6">
|
||||
Get Started
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user