mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 04:50:14 +09:00
feat release: admin model market feature
This commit is contained in:
parent
b3d406858b
commit
6f6d818197
@ -33,6 +33,30 @@ type UpdateRootPasswordForm struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func UpdateMarketAPI(c *gin.Context) {
|
||||
var form MarketModelList
|
||||
if err := c.ShouldBindJSON(&form); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := MarketInstance.SetModels(form)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func InfoAPI(c *gin.Context) {
|
||||
db := utils.GetDBFromContext(c)
|
||||
cache := utils.GetCacheFromContext(c)
|
||||
|
7
admin/instance.go
Normal file
7
admin/instance.go
Normal file
@ -0,0 +1,7 @@
|
||||
package admin
|
||||
|
||||
var MarketInstance *Market
|
||||
|
||||
func InitInstance() {
|
||||
MarketInstance = NewMarket()
|
||||
}
|
57
admin/market.go
Normal file
57
admin/market.go
Normal file
@ -0,0 +1,57 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ModelTag []string
|
||||
type MarketModel struct {
|
||||
Id string `json:"id" mapstructure:"id" required:"true"`
|
||||
Name string `json:"name" mapstructure:"name" required:"true"`
|
||||
Description string `json:"description" mapstructure:"description"`
|
||||
Default bool `json:"default" mapstructure:"default"`
|
||||
HighContext bool `json:"high_context" mapstructure:"high_context"`
|
||||
Avatar string `json:"avatar" mapstructure:"avatar"`
|
||||
Tag ModelTag `json:"tag" mapstructure:"tag"`
|
||||
}
|
||||
type MarketModelList []MarketModel
|
||||
|
||||
type Market struct {
|
||||
Models MarketModelList `json:"models" mapstructure:"models"`
|
||||
}
|
||||
|
||||
func NewMarket() *Market {
|
||||
var models MarketModelList
|
||||
if err := viper.UnmarshalKey("market", &models); err != nil {
|
||||
fmt.Println(fmt.Sprintf("[market] read config error: %s, use default config", err.Error()))
|
||||
models = MarketModelList{}
|
||||
}
|
||||
|
||||
return &Market{
|
||||
Models: models,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Market) GetModels() MarketModelList {
|
||||
return m.Models
|
||||
}
|
||||
|
||||
func (m *Market) GetModel(id string) *MarketModel {
|
||||
for _, model := range m.Models {
|
||||
if model.Id == id {
|
||||
return &model
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Market) SaveConfig() error {
|
||||
viper.Set("market", m.Models)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func (m *Market) SetModels(models MarketModelList) error {
|
||||
m.Models = models
|
||||
return m.SaveConfig()
|
||||
}
|
@ -24,4 +24,6 @@ func Register(app *gin.RouterGroup) {
|
||||
app.POST("/admin/user/quota", UserQuotaAPI)
|
||||
app.POST("/admin/user/subscription", UserSubscriptionAPI)
|
||||
app.POST("/admin/user/root", UpdateRootPasswordAPI)
|
||||
|
||||
app.POST("/admin/market/update", UpdateMarketAPI)
|
||||
}
|
||||
|
33
app/src/api/v1.ts
Normal file
33
app/src/api/v1.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import axios from "axios";
|
||||
import { Model } from "@/api/types.ts";
|
||||
import { ChargeProps } from "@/admin/charge.ts";
|
||||
|
||||
export async function getApiModels(): Promise<string[]> {
|
||||
try {
|
||||
const res = await axios.get("/v1/models");
|
||||
return res.data as string[];
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApiMarket(): Promise<Model[]> {
|
||||
try {
|
||||
const res = await axios.get("/v1/market");
|
||||
return res.data as Model[];
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApiCharge(): Promise<ChargeProps[]> {
|
||||
try {
|
||||
const res = await axios.get("/v1/charge");
|
||||
return res.data as ChargeProps[];
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
padding: 1rem 2rem;
|
||||
padding: 1rem 2rem 0;
|
||||
|
||||
@media (max-width: 940px) {
|
||||
flex-direction: column;
|
||||
@ -102,9 +102,18 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
padding: 1rem 1.5rem;
|
||||
padding: 0 1.5rem 1rem;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 940px) {
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
|
||||
.chart-box {
|
||||
width: calc(100% - 1rem) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: calc(50% - 1rem);
|
||||
height: max-content;
|
||||
@ -116,13 +125,5 @@
|
||||
background: hsl(var(--background));
|
||||
box-shadow: 0.5rem 0.5rem 1rem 0 var(--shadow);
|
||||
user-select: none;
|
||||
|
||||
@media (max-width: 680px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,11 @@
|
||||
height: max-content;
|
||||
align-items: center;
|
||||
background: hsl(var(--card));
|
||||
transition: 0.25s;
|
||||
|
||||
&.error {
|
||||
border-color: hsl(var(--error));
|
||||
}
|
||||
|
||||
.market-tags {
|
||||
display: flex;
|
||||
|
@ -28,7 +28,7 @@
|
||||
height: max-content;
|
||||
padding: 0.75rem 1rem;
|
||||
align-items: center;
|
||||
margin: 0 0.75rem;
|
||||
margin: 0.15rem 0.75rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease-in-out;
|
||||
@ -36,35 +36,13 @@
|
||||
font-size: 16px;
|
||||
color: hsl(var(--text-secondary));
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 2rem;
|
||||
background: hsl(var(--text));
|
||||
border-radius: var(--radius);
|
||||
margin-right: 0;
|
||||
opacity: 0;
|
||||
transition: 0.25s;
|
||||
transition-property: opacity, margin-right;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: hsl(var(--card-hover));
|
||||
|
||||
&:before {
|
||||
opacity: .05;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: hsl(var(--text));
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
background: hsl(var(--card-hover));
|
||||
}
|
||||
|
||||
& > * {
|
||||
@ -78,7 +56,10 @@
|
||||
.menu-item-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
scale: 0.95;
|
||||
margin-right: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@
|
||||
|
||||
--gold: 45 100% 50%;
|
||||
--link: 210 100% 63%;
|
||||
--error: 20 80% 50%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
@ -3,9 +3,9 @@ import { closeMenu, selectMenu } from "@/store/menu.ts";
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
BookCopy,
|
||||
CandlestickChart,
|
||||
CloudCog,
|
||||
Gauge,
|
||||
GitFork,
|
||||
LayoutDashboard,
|
||||
Radio,
|
||||
Settings,
|
||||
Users,
|
||||
@ -49,11 +49,7 @@ function MenuBar() {
|
||||
const open = useSelector(selectMenu);
|
||||
return (
|
||||
<div className={`admin-menu ${open ? "open" : ""}`}>
|
||||
<MenuItem
|
||||
title={t("admin.dashboard")}
|
||||
icon={<LayoutDashboard />}
|
||||
path={"/"}
|
||||
/>
|
||||
<MenuItem title={t("admin.dashboard")} icon={<Gauge />} path={"/"} />
|
||||
<MenuItem title={t("admin.user")} icon={<Users />} path={"/users"} />
|
||||
<MenuItem
|
||||
title={t("admin.market.title")}
|
||||
@ -70,11 +66,7 @@ function MenuBar() {
|
||||
icon={<GitFork />}
|
||||
path={"/channel"}
|
||||
/>
|
||||
<MenuItem
|
||||
title={t("admin.prize")}
|
||||
icon={<CandlestickChart />}
|
||||
path={"/charge"}
|
||||
/>
|
||||
<MenuItem title={t("admin.prize")} icon={<CloudCog />} path={"/charge"} />
|
||||
<MenuItem
|
||||
title={t("admin.settings")}
|
||||
icon={<Settings />}
|
||||
|
@ -3,14 +3,40 @@ import { ThemeProvider } from "@/components/ThemeProvider.tsx";
|
||||
import DialogManager from "@/dialogs";
|
||||
import Broadcast from "@/components/Broadcast.tsx";
|
||||
import { useEffectAsync } from "@/utils/hook.ts";
|
||||
import { allModels } from "@/conf.ts";
|
||||
import axios from "axios";
|
||||
import { allModels, supportModels } from "@/conf.ts";
|
||||
import { channelModels } from "@/admin/channel.ts";
|
||||
import { getApiCharge, getApiMarket, getApiModels } from "@/api/v1.ts";
|
||||
import { loadPreferenceModels } from "@/utils/storage.ts";
|
||||
import { resetJsArray } from "@/utils/base.ts";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { initChatModels } from "@/store/chat.ts";
|
||||
import { Model } from "@/api/types.ts";
|
||||
import { ChargeProps, nonBilling } from "@/admin/charge.ts";
|
||||
|
||||
function AppProvider() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffectAsync(async () => {
|
||||
const res = await axios.get("/v1/models");
|
||||
res.data.forEach((model: string) => {
|
||||
const market = await getApiMarket();
|
||||
const charge = await getApiCharge();
|
||||
|
||||
market.forEach((item: Model) => {
|
||||
const obj = charge.find((i: ChargeProps) => i.models.includes(item.id));
|
||||
if (!obj) return;
|
||||
|
||||
item.free = obj.type === nonBilling;
|
||||
item.auth = item.free && !obj.anonymous;
|
||||
});
|
||||
|
||||
resetJsArray(supportModels, loadPreferenceModels(market));
|
||||
resetJsArray(
|
||||
allModels,
|
||||
supportModels.map((model) => model.id),
|
||||
);
|
||||
initChatModels(dispatch);
|
||||
|
||||
const models = await getApiModels();
|
||||
models.forEach((model: string) => {
|
||||
if (!allModels.includes(model)) allModels.push(model);
|
||||
if (!channelModels.includes(model)) channelModels.push(model);
|
||||
});
|
||||
|
@ -18,6 +18,7 @@ const TooltipContent = React.forwardRef<
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"max-w-[100vw]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
514
app/src/conf.ts
514
app/src/conf.ts
@ -12,7 +12,7 @@ import { getMemory } from "@/utils/memory.ts";
|
||||
import { Compass, Image, Newspaper } from "lucide-react";
|
||||
import React from "react";
|
||||
import { syncSiteInfo } from "@/admin/api/info.ts";
|
||||
import { loadPreferenceModels } from "@/utils/storage.ts";
|
||||
import { getOfflineModels, loadPreferenceModels } from "@/utils/storage.ts";
|
||||
|
||||
export const version = "3.8.1";
|
||||
export const dev: boolean = getDev();
|
||||
@ -21,517 +21,7 @@ export let rest_api: string = getRestApi(deploy);
|
||||
export let ws_api: string = getWebsocketApi(deploy);
|
||||
export const tokenField = getTokenField(deploy);
|
||||
|
||||
export let supportModels: Model[] = loadPreferenceModels([
|
||||
// openai models
|
||||
{
|
||||
id: "gpt-3.5-turbo-0613",
|
||||
name: "GPT-3.5",
|
||||
avatar: "gpt35turbo.png",
|
||||
free: true,
|
||||
auth: false,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-16k-0613",
|
||||
name: "GPT-3.5-16k",
|
||||
avatar: "gpt35turbo16k.webp",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "official", "high-context"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-1106",
|
||||
name: "GPT-3.5 1106",
|
||||
avatar: "gpt35turbo16k.webp",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-fast",
|
||||
name: "GPT-3.5 Fast",
|
||||
avatar: "gpt35turbo16k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-16k-fast",
|
||||
name: "GPT-3.5 16K Fast",
|
||||
avatar: "gpt35turbo16k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-0613",
|
||||
name: "GPT-4",
|
||||
avatar: "gpt4.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-1106-preview",
|
||||
name: "GPT-4 Turbo 128k",
|
||||
avatar: "gpt432k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-vision-preview",
|
||||
name: "GPT-4 Vision 128k",
|
||||
avatar: "gpt4v.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context", "multi-modal", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-v",
|
||||
name: "GPT-4 Vision",
|
||||
avatar: "gpt4v.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "unstable", "multi-modal"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-dalle",
|
||||
name: "GPT-4 DALLE",
|
||||
avatar: "gpt4dalle.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "unstable", "image-generation"],
|
||||
},
|
||||
|
||||
{
|
||||
id: "azure-gpt-3.5-turbo",
|
||||
name: "Azure GPT-3.5",
|
||||
avatar: "gpt35turbo.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
{
|
||||
id: "azure-gpt-3.5-turbo-16k",
|
||||
name: "Azure GPT-3.5 16K",
|
||||
avatar: "gpt35turbo16k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
{
|
||||
id: "azure-gpt-4",
|
||||
name: "Azure GPT-4",
|
||||
avatar: "gpt4.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "azure-gpt-4-1106-preview",
|
||||
name: "Azure GPT-4 Turbo 128k",
|
||||
avatar: "gpt432k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "azure-gpt-4-vision-preview",
|
||||
name: "Azure GPT-4 Vision 128k",
|
||||
avatar: "gpt4v.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context", "multi-modal"],
|
||||
},
|
||||
{
|
||||
id: "azure-gpt-4-32k",
|
||||
name: "Azure GPT-4 32k",
|
||||
avatar: "gpt432k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "multi-modal"],
|
||||
},
|
||||
|
||||
// spark desk
|
||||
{
|
||||
id: "spark-desk-v3",
|
||||
name: "讯飞星火 V3",
|
||||
avatar: "sparkdesk.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "spark-desk-v2",
|
||||
name: "讯飞星火 V2",
|
||||
avatar: "sparkdesk.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["official"],
|
||||
},
|
||||
{
|
||||
id: "spark-desk-v1.5",
|
||||
name: "讯飞星火 V1.5",
|
||||
avatar: "sparkdesk.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// dashscope models
|
||||
{
|
||||
id: "qwen-plus-net",
|
||||
name: "通义千问 Plus Net",
|
||||
avatar: "tongyi.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "high-quality", "web"],
|
||||
},
|
||||
{
|
||||
id: "qwen-plus",
|
||||
name: "通义千问 Plus",
|
||||
avatar: "tongyi.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "qwen-turbo-net",
|
||||
name: "通义千问 Turbo Net",
|
||||
avatar: "tongyi.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["official", "web"],
|
||||
},
|
||||
{
|
||||
id: "qwen-turbo",
|
||||
name: "通义千问 Turbo",
|
||||
avatar: "tongyi.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// huyuan models
|
||||
{
|
||||
id: "hunyuan",
|
||||
name: "腾讯混元 Pro",
|
||||
avatar: "hunyuan.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// zhipu models
|
||||
{
|
||||
id: "zhipu-chatglm-turbo",
|
||||
name: "ChatGLM Turbo",
|
||||
avatar: "chatglm.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "open-source", "high-context"],
|
||||
},
|
||||
|
||||
// baichuan models
|
||||
{
|
||||
id: "baichuan-53b",
|
||||
name: "百川 Baichuan 53B",
|
||||
avatar: "baichuan.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "open-source"],
|
||||
},
|
||||
|
||||
// skylark models
|
||||
{
|
||||
id: "skylark-chat",
|
||||
name: "抖音豆包 Skylark",
|
||||
avatar: "skylark.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// 360 models
|
||||
{
|
||||
id: "360-gpt-v9",
|
||||
name: "360 智脑",
|
||||
avatar: "360gpt.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
{
|
||||
id: "claude-1-100k",
|
||||
name: "Claude",
|
||||
avatar: "claude.png",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "claude-2",
|
||||
name: "Claude 100k",
|
||||
avatar: "claude100k.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context"],
|
||||
},
|
||||
{
|
||||
id: "claude-2.1",
|
||||
name: "Claude 200k",
|
||||
avatar: "claude100k.png",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["official", "high-context"],
|
||||
},
|
||||
|
||||
// llama models
|
||||
{
|
||||
id: "llama-2-70b",
|
||||
name: "LLaMa-2 70B",
|
||||
avatar: "llama2.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "llama-2-13b",
|
||||
name: "LLaMa-2 13B",
|
||||
avatar: "llama2.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "llama-2-7b",
|
||||
name: "LLaMa-2 7B",
|
||||
avatar: "llama2.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
|
||||
{
|
||||
id: "code-llama-34b",
|
||||
name: "Code LLaMa 34B",
|
||||
avatar: "llamacode.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "code-llama-13b",
|
||||
name: "Code LLaMa 13B",
|
||||
avatar: "llamacode.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "code-llama-7b",
|
||||
name: "Code LLaMa 7B",
|
||||
avatar: "llamacode.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
|
||||
// new bing
|
||||
{
|
||||
id: "bing-creative",
|
||||
name: "New Bing",
|
||||
avatar: "newbing.jpg",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "unstable", "web"],
|
||||
},
|
||||
|
||||
// google palm2
|
||||
{
|
||||
id: "chat-bison-001",
|
||||
name: "Google PaLM2",
|
||||
avatar: "palm2.webp",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["free", "english-model"],
|
||||
},
|
||||
|
||||
// gemini
|
||||
{
|
||||
id: "gemini-pro",
|
||||
name: "Gemini Pro",
|
||||
avatar: "gemini.jpeg",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "gemini-pro-vision",
|
||||
name: "Gemini Pro Vision",
|
||||
avatar: "gemini.jpeg",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: true,
|
||||
tag: ["free", "official", "multi-modal"],
|
||||
},
|
||||
|
||||
// drawing models
|
||||
{
|
||||
id: "midjourney",
|
||||
name: "Midjourney",
|
||||
avatar: "midjourney.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "midjourney-fast",
|
||||
name: "Midjourney Fast",
|
||||
avatar: "midjourney.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "fast", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "midjourney-turbo",
|
||||
name: "Midjourney Turbo",
|
||||
avatar: "midjourney.jpg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "fast", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "stable-diffusion",
|
||||
name: "Stable Diffusion XL",
|
||||
avatar: "stablediffusion.jpeg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: ["open-source", "unstable", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "dall-e-2",
|
||||
name: "DALLE 2",
|
||||
avatar: "dalle.jpeg",
|
||||
free: true,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["free", "official", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "dall-e-3",
|
||||
name: "DALLE 3",
|
||||
avatar: "dalle.jpeg",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: false,
|
||||
default: true,
|
||||
tag: ["official", "image-generation"],
|
||||
},
|
||||
|
||||
{
|
||||
id: "gpt-4-32k-0613",
|
||||
name: "GPT-4-32k",
|
||||
avatar: "gpt432k.webp",
|
||||
free: false,
|
||||
auth: true,
|
||||
high_context: true,
|
||||
default: false,
|
||||
tag: ["official", "high-quality", "high-price"],
|
||||
},
|
||||
]);
|
||||
export let supportModels: Model[] = loadPreferenceModels(getOfflineModels());
|
||||
|
||||
export let allModels: string[] = supportModels.map((model) => model.id);
|
||||
|
||||
|
@ -17,7 +17,7 @@ import { Model as RawModel } from "@/api/types.ts";
|
||||
import { supportModels } from "@/conf.ts";
|
||||
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { GripVertical, HelpCircle } from "lucide-react";
|
||||
import { GripVertical, HelpCircle, Plus, Trash2 } from "lucide-react";
|
||||
import { generateRandomChar, isUrl } from "@/utils/base.ts";
|
||||
import Require from "@/components/Require.tsx";
|
||||
import { Textarea } from "@/components/ui/textarea.tsx";
|
||||
@ -59,6 +59,22 @@ function reducer(state: MarketForm, action: any): MarketForm {
|
||||
seed: generateSeed(),
|
||||
},
|
||||
];
|
||||
case "new":
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
id: "",
|
||||
name: "",
|
||||
free: false,
|
||||
auth: false,
|
||||
description: "",
|
||||
high_context: false,
|
||||
default: false,
|
||||
tag: [],
|
||||
avatar: modelImages[0],
|
||||
seed: generateSeed(),
|
||||
},
|
||||
];
|
||||
case "remove":
|
||||
let { idx } = action.payload;
|
||||
return [...state.slice(0, idx), ...state.slice(idx + 1)];
|
||||
@ -342,6 +358,14 @@ function Market() {
|
||||
);
|
||||
}, [form]);
|
||||
|
||||
const doCheck = (index: number) => {
|
||||
return useMemo((): boolean => {
|
||||
const model = form[index];
|
||||
|
||||
return model.id.trim().length > 0 && model.name.trim().length > 0;
|
||||
}, [form, index]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`market`}>
|
||||
<Card className={`market-card`}>
|
||||
@ -349,7 +373,7 @@ function Market() {
|
||||
<CardTitle>{t("admin.market.title")}</CardTitle>
|
||||
<Button
|
||||
loading={true}
|
||||
className={`ml-auto mt-0`}
|
||||
className={`ml-auto mt-0 whitespace-nowrap`}
|
||||
size={`sm`}
|
||||
style={{ marginTop: 0 }}
|
||||
onClick={update}
|
||||
@ -389,7 +413,9 @@ function Market() {
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
className={`market-item`}
|
||||
className={`market-item ${
|
||||
doCheck(index) ? "" : "error"
|
||||
}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
@ -515,6 +541,29 @@ function Market() {
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
</div>
|
||||
<div className={`market-row`}>
|
||||
<div className={`grow`} />
|
||||
<Button
|
||||
variant={`outline`}
|
||||
size={`icon`}
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: "remove",
|
||||
payload: { idx: index },
|
||||
})
|
||||
}
|
||||
>
|
||||
<Trash2 className={`h-4 w-4`} />
|
||||
</Button>
|
||||
{index === form.length - 1 && (
|
||||
<Button
|
||||
size={`icon`}
|
||||
onClick={() => dispatch({ type: "new" })}
|
||||
>
|
||||
<Plus className={`h-4 w-4`} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -2,9 +2,16 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
import { ConversationInstance, Model } from "@/api/types.ts";
|
||||
import { Message } from "@/api/types.ts";
|
||||
import { insertStart } from "@/utils/base.ts";
|
||||
import { RootState } from "./index.ts";
|
||||
import { AppDispatch, RootState } from "./index.ts";
|
||||
import { planModels, supportModels } from "@/conf.ts";
|
||||
import { getBooleanMemory, getMemory, setMemory } from "@/utils/memory.ts";
|
||||
import {
|
||||
getArrayMemory,
|
||||
getBooleanMemory,
|
||||
getMemory,
|
||||
setArrayMemory,
|
||||
setMemory,
|
||||
} from "@/utils/memory.ts";
|
||||
import { setOfflineModels } from "@/utils/storage.ts";
|
||||
|
||||
type initialStateType = {
|
||||
history: ConversationInstance[];
|
||||
@ -31,17 +38,12 @@ export function getPlanModels(level: number): string[] {
|
||||
}
|
||||
|
||||
export function getModel(model: string | undefined | null): string {
|
||||
if (supportModels.length === 0) return "";
|
||||
return model && inModel(model) ? model : supportModels[0].id;
|
||||
}
|
||||
|
||||
export function getModelList(
|
||||
models: string | undefined | null,
|
||||
select: string | undefined | null,
|
||||
): string[] {
|
||||
const list =
|
||||
models && models.length
|
||||
? models.split(",").filter((item) => inModel(item))
|
||||
: [];
|
||||
export function getModelList(models: string[], select: string): string[] {
|
||||
const list = models.filter((item) => inModel(item));
|
||||
const target = list.length
|
||||
? list
|
||||
: supportModels.filter((item) => item.default).map((item) => item.id);
|
||||
@ -58,11 +60,20 @@ const chatSlice = createSlice({
|
||||
model: getModel(getMemory("model")),
|
||||
web: getBooleanMemory("web", false),
|
||||
current: -1,
|
||||
model_list: getModelList(getMemory("model_list"), getMemory("model")),
|
||||
model_list: getModelList(getArrayMemory("model_list"), getMemory("model")),
|
||||
market: false,
|
||||
mask: false,
|
||||
} as initialStateType,
|
||||
reducers: {
|
||||
doInit: (state) => {
|
||||
setOfflineModels(supportModels);
|
||||
|
||||
state.model = getModel(getMemory("model"));
|
||||
state.model_list = getModelList(
|
||||
getArrayMemory("model_list"),
|
||||
getMemory("model"),
|
||||
);
|
||||
},
|
||||
setHistory: (state, action) => {
|
||||
state.history = action.payload as ConversationInstance[];
|
||||
},
|
||||
@ -109,20 +120,20 @@ const chatSlice = createSlice({
|
||||
setModelList: (state, action) => {
|
||||
const models = action.payload as string[];
|
||||
state.model_list = models.filter((item) => inModel(item));
|
||||
setMemory("model_list", models.join(","));
|
||||
setArrayMemory("model_list", models);
|
||||
},
|
||||
addModelList: (state, action) => {
|
||||
const model = action.payload as string;
|
||||
if (inModel(model) && !state.model_list.includes(model)) {
|
||||
state.model_list.push(model);
|
||||
setMemory("model_list", state.model_list.join(","));
|
||||
setArrayMemory("model_list", state.model_list);
|
||||
}
|
||||
},
|
||||
removeModelList: (state, action) => {
|
||||
const model = action.payload as string;
|
||||
if (inModel(model) && state.model_list.includes(model)) {
|
||||
state.model_list = state.model_list.filter((item) => item !== model);
|
||||
setMemory("model_list", state.model_list.join(","));
|
||||
setArrayMemory("model_list", state.model_list);
|
||||
}
|
||||
},
|
||||
setMarket: (state, action) => {
|
||||
@ -147,6 +158,7 @@ const chatSlice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
doInit,
|
||||
setHistory,
|
||||
removeHistory,
|
||||
addHistory,
|
||||
@ -178,5 +190,6 @@ export const selectModelList = (state: RootState): string[] =>
|
||||
state.chat.model_list;
|
||||
export const selectMarket = (state: RootState): boolean => state.chat.market;
|
||||
export const selectMask = (state: RootState): boolean => state.chat.mask;
|
||||
export const initChatModels = (dispatch: AppDispatch) => dispatch(doInit());
|
||||
|
||||
export default chatSlice.reducer;
|
||||
|
@ -71,3 +71,12 @@ export function isUrl(value: string): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resetJsArray<T>(arr: T[], target: T[]): T[] {
|
||||
/**
|
||||
* this function is used to reset an array to another array without changing the *pointer
|
||||
*/
|
||||
|
||||
arr.splice(0, arr.length, ...target);
|
||||
return arr;
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ export function setNumberMemory(key: string, value: number) {
|
||||
setMemory(key, value.toString());
|
||||
}
|
||||
|
||||
export function setArrayMemory(key: string, value: string[]) {
|
||||
setMemory(key, value.join(","));
|
||||
}
|
||||
|
||||
export function getMemory(key: string): string {
|
||||
return (localStorage.getItem(key) || "").trim();
|
||||
}
|
||||
|
@ -25,3 +25,12 @@ export function loadPreferenceModels(models: Model[]): Model[] {
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
}
|
||||
|
||||
export function setOfflineModels(models: Model[]): void {
|
||||
setMemory("model_offline", JSON.stringify(models));
|
||||
}
|
||||
|
||||
export function getOfflineModels(): Model[] {
|
||||
const memory = getMemory("model_offline");
|
||||
return memory.length ? (JSON.parse(memory) as Model[]) : [];
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ func ConnectMySQL() *sql.DB {
|
||||
log.Println(fmt.Sprintf("[connection] connected to mysql server (host: %s)", viper.GetString("mysql.host")))
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(512)
|
||||
db.SetMaxIdleConns(64)
|
||||
|
||||
CreateUserTable(db)
|
||||
CreateConversationTable(db)
|
||||
CreateSharingTable(db)
|
||||
|
1
main.go
1
main.go
@ -36,6 +36,7 @@ func registerApiRouter(engine *gin.Engine) {
|
||||
|
||||
func main() {
|
||||
utils.ReadConf()
|
||||
admin.InitInstance()
|
||||
channel.InitManager()
|
||||
|
||||
if cli.Run() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"chat/admin"
|
||||
"chat/channel"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
@ -10,6 +11,10 @@ func ModelAPI(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, channel.ConduitInstance.GetModels())
|
||||
}
|
||||
|
||||
func MarketAPI(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, admin.MarketInstance.GetModels())
|
||||
}
|
||||
|
||||
func ChargeAPI(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, channel.ChargeInstance.ListRules())
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
func Register(app *gin.RouterGroup) {
|
||||
app.GET("/chat", ChatAPI)
|
||||
app.GET("/v1/models", ModelAPI)
|
||||
app.GET("/v1/market", MarketAPI)
|
||||
app.GET("/v1/charge", ChargeAPI)
|
||||
app.GET("/dashboard/billing/usage", GetBillingUsage)
|
||||
app.GET("/dashboard/billing/subscription", GetSubscription)
|
||||
|
Loading…
Reference in New Issue
Block a user