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