mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 04:50:14 +09:00
feat: update /v1/models openai format
This commit is contained in:
parent
e8718d3386
commit
8079273428
@ -2,6 +2,7 @@ package admin
|
||||
|
||||
import (
|
||||
"chat/channel"
|
||||
"chat/globals"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
"github.com/go-redis/redis/v8"
|
||||
@ -52,7 +53,7 @@ func GetModelData(cache *redis.Client) ModelChartForm {
|
||||
|
||||
return ModelChartForm{
|
||||
Date: getDates(dates),
|
||||
Value: utils.EachNotNil[string, ModelData](channel.ConduitInstance.GetModels(), func(model string) *ModelData {
|
||||
Value: utils.EachNotNil[string, ModelData](globals.SupportModels, func(model string) *ModelData {
|
||||
data := ModelData{
|
||||
Model: model,
|
||||
Data: utils.Each[time.Time, int64](dates, func(date time.Time) int64 {
|
||||
|
@ -1,7 +1,8 @@
|
||||
export type CommonResponse = {
|
||||
status: boolean;
|
||||
error: string;
|
||||
error?: string;
|
||||
reason?: string;
|
||||
data?: any;
|
||||
};
|
||||
|
||||
export function toastState(
|
||||
@ -13,5 +14,9 @@ export function toastState(
|
||||
if (state.status)
|
||||
toastSuccess &&
|
||||
toast({ title: t("success"), description: t("request-success") });
|
||||
else toast({ title: t("error"), description: state.error ?? state.reason });
|
||||
else
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: state.error ?? state.reason ?? "error occurred",
|
||||
});
|
||||
}
|
||||
|
@ -1,11 +1,28 @@
|
||||
import axios from "axios";
|
||||
import { Model, Plan } from "@/api/types.ts";
|
||||
import { ChargeProps } from "@/admin/charge.ts";
|
||||
import { getErrorMessage } from "@/utils/base.ts";
|
||||
|
||||
type v1Options = {
|
||||
endpoint?: string;
|
||||
};
|
||||
|
||||
type v1Models = {
|
||||
object: string;
|
||||
data: {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
owned_by: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
type v1Resp<T> = {
|
||||
data: T;
|
||||
status: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export function getV1Path(path: string, options?: v1Options): string {
|
||||
let endpoint = options && options.endpoint ? options.endpoint : "";
|
||||
if (endpoint.endsWith("/")) endpoint = endpoint.slice(0, -1);
|
||||
@ -13,13 +30,31 @@ export function getV1Path(path: string, options?: v1Options): string {
|
||||
return endpoint + path;
|
||||
}
|
||||
|
||||
export async function getApiModels(options?: v1Options): Promise<string[]> {
|
||||
export async function getApiModels(
|
||||
secret?: string,
|
||||
options?: v1Options,
|
||||
): Promise<v1Resp<string[]>> {
|
||||
try {
|
||||
const res = await axios.get(getV1Path("/v1/models", options));
|
||||
return res.data as string[];
|
||||
const res = await axios.get(
|
||||
getV1Path("/v1/models", options),
|
||||
secret
|
||||
? {
|
||||
headers: {
|
||||
Authorization: `Bearer ${secret}`,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const data = res.data as v1Models;
|
||||
const models = data.data ? data.data.map((model) => model.id) : [];
|
||||
|
||||
return models.length > 0
|
||||
? { status: true, data: models }
|
||||
: { status: false, data: [], error: "No models found" };
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
return { status: false, data: [], error: getErrorMessage(e) };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,18 @@ import {
|
||||
} from "@/admin/api/channel.ts";
|
||||
import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { cn } from "@/components/ui/lib/utils.ts";
|
||||
import PopupDialog, { popupTypes } from "@/components/PopupDialog.tsx";
|
||||
import { getApiModels, getV1Path } from "@/api/v1.ts";
|
||||
import { getApiModels } from "@/api/v1.ts";
|
||||
import { getHostName } from "@/utils/base.ts";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog.tsx";
|
||||
import { Label } from "@/components/ui/label.tsx";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { DialogClose } from "@radix-ui/react-dialog";
|
||||
|
||||
type ChannelTableProps = {
|
||||
display: boolean;
|
||||
@ -65,27 +74,24 @@ function SyncDialog({ dispatch, open, setOpen }: SyncDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [endpoint, setEndpoint] = useState<string>("https://api.openai.com");
|
||||
const [secret, setSecret] = useState<string>("");
|
||||
|
||||
const submit = async (endpoint: string): Promise<boolean> => {
|
||||
endpoint = endpoint.trim();
|
||||
endpoint.endsWith("/") && (endpoint = endpoint.slice(0, -1));
|
||||
|
||||
const path = getV1Path("/v1/models", { endpoint });
|
||||
const models = await getApiModels({ endpoint });
|
||||
const resp = await getApiModels(secret, { endpoint });
|
||||
toastState(toast, t, resp, true);
|
||||
|
||||
if (models.length === 0) {
|
||||
toast({
|
||||
title: t("admin.channels.sync-failed"),
|
||||
description: t("admin.channels.sync-failed-prompt", { endpoint: path }),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!resp.status) return false;
|
||||
|
||||
const name = getHostName(endpoint).replace(/\./g, "-");
|
||||
const data: Channel = {
|
||||
id: -1,
|
||||
name,
|
||||
type: "openai",
|
||||
models,
|
||||
models: resp.data,
|
||||
priority: 0,
|
||||
weight: 1,
|
||||
retry: 3,
|
||||
@ -101,16 +107,51 @@ function SyncDialog({ dispatch, open, setOpen }: SyncDialogProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupDialog
|
||||
title={t("admin.channels.joint")}
|
||||
name={t("admin.channels.joint-endpoint")}
|
||||
placeholder={t("admin.channels.joint-endpoint-placeholder")}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
defaultValue={"https://api.chatnio.net"}
|
||||
type={popupTypes.Text}
|
||||
onSubmit={submit}
|
||||
/>
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("admin.channels.joint")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className={`pt-2 flex flex-col`}>
|
||||
<div className={`flex flex-row items-center mb-4`}>
|
||||
<Label className={`mr-2 whitespace-nowrap`}>
|
||||
{t("admin.channels.joint-endpoint")}
|
||||
</Label>
|
||||
<Input
|
||||
value={endpoint}
|
||||
onChange={(e) => setEndpoint(e.target.value)}
|
||||
placeholder={t("admin.channels.upstream-endpoint-placeholder")}
|
||||
/>
|
||||
</div>
|
||||
<div className={`flex flex-row items-center`}>
|
||||
<Label className={`mr-2 whitespace-nowrap`}>
|
||||
{t("admin.channels.secret")}
|
||||
</Label>
|
||||
<Input
|
||||
value={secret}
|
||||
onChange={(e) => setSecret(e.target.value)}
|
||||
placeholder={t("admin.channels.secret-placeholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant={`outline`}>{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
className={`mb-1`}
|
||||
onClick={async () => {
|
||||
const status = await submit(endpoint);
|
||||
status && setOpen(false);
|
||||
}}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ function AppProvider() {
|
||||
initChatModels(dispatch);
|
||||
|
||||
const models = await getApiModels();
|
||||
models.forEach((model: string) => {
|
||||
models.data.forEach((model: string) => {
|
||||
if (!allModels.includes(model)) allModels.push(model);
|
||||
if (!channelModels.includes(model)) channelModels.push(model);
|
||||
});
|
||||
|
@ -583,6 +583,9 @@
|
||||
"joint": "对接上游",
|
||||
"joint-endpoint": "上游地址",
|
||||
"joint-endpoint-placeholder": "请输入上游 Chat Nio 的 API 地址,如:https://api.chatnio.net",
|
||||
"upstream-endpoint-placeholder": "请输入上游 OpenAI 地址,如:https://api.openai.com",
|
||||
"secret": "密钥",
|
||||
"secret-placeholder": "请输入上游渠道的 API 密钥",
|
||||
"joint-secret": "API 秘钥",
|
||||
"joint-secret-placeholder": "请输入上游 Chat Nio 的 API 秘钥",
|
||||
"sync-failed": "同步失败",
|
||||
|
@ -447,7 +447,8 @@
|
||||
"sync-failed": "Sync Failed",
|
||||
"sync-failed-prompt": "Address could not be requested or model market model is empty\n(Endpoint: {{endpoint}})",
|
||||
"sync-success": "Sync successfully.",
|
||||
"sync-success-prompt": "{{length}} models were added from upstream synchronization."
|
||||
"sync-success-prompt": "{{length}} models were added from upstream synchronization.",
|
||||
"upstream-endpoint-placeholder": "Please enter the upstream OpenAI address, e.g. https://api.openai.com"
|
||||
},
|
||||
"charge": {
|
||||
"id": "ID",
|
||||
|
@ -447,7 +447,8 @@
|
||||
"sync-failed": "同期に失敗しました",
|
||||
"sync-failed-prompt": "住所をリクエストできなかったか、モデルマーケットモデルが空です\n(エンドポイント:{{ endpoint }})",
|
||||
"sync-success": "同期成功",
|
||||
"sync-success-prompt": "{{length}}モデルがアップストリーム同期から追加されました。"
|
||||
"sync-success-prompt": "{{length}}モデルがアップストリーム同期から追加されました。",
|
||||
"upstream-endpoint-placeholder": "上流のOpenAIアドレスを入力してください。例: https://api.openai.com"
|
||||
},
|
||||
"charge": {
|
||||
"id": "ID",
|
||||
|
@ -447,7 +447,8 @@
|
||||
"sync-failed": "Не удалось синхронизировать",
|
||||
"sync-failed-prompt": "Адрес не может быть запрошен или модель рынка пуста\n(Конечная точка: {{endpoint}})",
|
||||
"sync-success": "Успешная синхронизация",
|
||||
"sync-success-prompt": "{{length}} модели были добавлены из синхронизации восходящего потока."
|
||||
"sync-success-prompt": "{{length}} модели были добавлены из синхронизации восходящего потока.",
|
||||
"upstream-endpoint-placeholder": "Введите вышестоящий адрес OpenAI, например, https://api.openai.com"
|
||||
},
|
||||
"charge": {
|
||||
"id": "ID",
|
||||
|
@ -701,7 +701,7 @@ function Market() {
|
||||
globalDispatch(setModel(allModels[0]));
|
||||
|
||||
const models = await getApiModels();
|
||||
models.forEach((model: string) => {
|
||||
models.data.forEach((model: string) => {
|
||||
if (!allModels.includes(model)) allModels.push(model);
|
||||
if (!channelModels.includes(model)) channelModels.push(model);
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
package channel
|
||||
|
||||
import (
|
||||
"chat/globals"
|
||||
"chat/utils"
|
||||
"errors"
|
||||
"github.com/spf13/viper"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ConduitInstance *Manager
|
||||
@ -66,6 +68,21 @@ func (m *Manager) Load() {
|
||||
seq.Sort()
|
||||
m.PreflightSequence[model] = seq
|
||||
}
|
||||
|
||||
stamp := time.Now().Unix()
|
||||
|
||||
globals.SupportModels = m.Models
|
||||
globals.V1ListModels = globals.ListModels{
|
||||
Object: "list",
|
||||
Data: utils.Each(m.Models, func(model string) globals.ListModelsItem {
|
||||
return globals.ListModelsItem{
|
||||
Id: model,
|
||||
Object: "model",
|
||||
Created: stamp,
|
||||
OwnedBy: "system",
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) GetSequence() Sequence {
|
||||
|
4
globals/params.go
Normal file
4
globals/params.go
Normal file
@ -0,0 +1,4 @@
|
||||
package globals
|
||||
|
||||
var V1ListModels ListModels
|
||||
var SupportModels []string
|
@ -33,3 +33,15 @@ type GenerationSegmentResponse struct {
|
||||
End bool `json:"end"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type ListModels struct {
|
||||
Object string `json:"object"`
|
||||
Data []ListModelsItem `json:"data"`
|
||||
}
|
||||
|
||||
type ListModelsItem struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
OwnedBy string `json:"owned_by"`
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ package manager
|
||||
import (
|
||||
"chat/admin"
|
||||
"chat/channel"
|
||||
"chat/globals"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ModelAPI(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, channel.ConduitInstance.GetModels())
|
||||
c.JSON(http.StatusOK, globals.V1ListModels)
|
||||
}
|
||||
|
||||
func MarketAPI(c *gin.Context) {
|
||||
|
Loading…
Reference in New Issue
Block a user