Skip to content

Commit

Permalink
feat: support storage dalle images in backend (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Mar 12, 2024
1 parent 228fc78 commit 9ecaf9f
Show file tree
Hide file tree
Showing 24 changed files with 192 additions and 40 deletions.
3 changes: 2 additions & 1 deletion adapter/openai/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (c *ChatInstance) CreateImageRequest(props ImageProps) (string, error) {

// CreateImage will create a dalle image from prompt, return markdown of image
func (c *ChatInstance) CreateImage(props *adaptercommon.ChatProps) (string, error) {
url, err := c.CreateImageRequest(ImageProps{
original, err := c.CreateImageRequest(ImageProps{
Model: props.Model,
Prompt: c.GetLatestPrompt(props),
})
Expand All @@ -59,5 +59,6 @@ func (c *ChatInstance) CreateImage(props *adaptercommon.ChatProps) (string, erro
return "", err
}

url := utils.StoreImage(original)
return utils.GetImageMarkdown(url), nil
}
3 changes: 3 additions & 0 deletions app/src/admin/api/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type CommonState = {

article: string[];
generation: string[];

image_store: boolean;
};

export type SystemProps = {
Expand Down Expand Up @@ -152,5 +154,6 @@ export const initialSystemState: SystemProps = {
cache: [],
expire: 3600,
size: 1,
image_store: false,
},
};
15 changes: 10 additions & 5 deletions app/src/admin/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const ShortChannelTypes: Record<string, string> = {
groq: "Groq",
bing: "Bing",
slack: "Slack",
}
};

export const ChannelInfos: Record<string, ChannelInfo> = {
openai: {
Expand Down Expand Up @@ -148,7 +148,12 @@ export const ChannelInfos: Record<string, ChannelInfo> = {
sparkdesk: {
endpoint: "wss://spark-api.xf-yun.com",
format: "<app-id>|<api-secret>|<api-key>",
models: ["spark-desk-v1.5", "spark-desk-v2", "spark-desk-v3", "spark-desk-v3.5"],
models: [
"spark-desk-v1.5",
"spark-desk-v2",
"spark-desk-v3",
"spark-desk-v3.5",
],
},
chatglm: {
endpoint: "https://open.bigmodel.cn",
Expand Down Expand Up @@ -233,11 +238,11 @@ export const ChannelInfos: Record<string, ChannelInfo> = {
format: "<api-key>",
models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"],
},
groq: {
groq: {
endpoint: "https://api.groq.com/openai",
format: "<api-key>",
models: ["llama2-70b-4096", "mixtral-8x7b-32768", "gemma-7b-it"]
}
models: ["llama2-70b-4096", "mixtral-8x7b-32768", "gemma-7b-it"],
},
};

export const defaultChannelModels: string[] = getUniqueList(
Expand Down
2 changes: 1 addition & 1 deletion app/src/admin/datasets/charge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export const pricing: PricingDataset = [
models: ["llama2-70b-4096", "mixtral-8x7b-32768", "gemma-7b-it"],
output: 0.001, // free marked as $0.001
currency: Currency.USD,
}
},
];

const countPricing = (
Expand Down
4 changes: 3 additions & 1 deletion app/src/assets/admin/menu.less
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
background: hsl(var(--background));
transition: 0.225s ease-in-out;
min-height: calc(100% - 56px);
transition-property: width, background, box-shadow;
transition-property: width, background, box-shadow, opacity;
border-right: 0;
opacity: 0;
pointer-events: none;
overflow-x: hidden;

&.open {
width: 260px;
border-right: 1px solid hsl(var(--border));
opacity: 1;
pointer-events: all;
}

Expand Down
4 changes: 3 additions & 1 deletion app/src/assets/pages/home.less
Original file line number Diff line number Diff line change
Expand Up @@ -337,15 +337,17 @@
margin: 0;
background: hsl(var(--background));
transition: 0.2s ease-in-out;
transition-property: width, background, box-shadow;
transition-property: width, background, box-shadow, border-right, opacity;
border-right: 0;
pointer-events: none;
opacity: 0;
overflow-x: hidden;

&.open {
width: 260px;
border-right: 1px solid hsl(var(--border));
pointer-events: auto;
opacity: 1;
}

&.hidden {
Expand Down
20 changes: 11 additions & 9 deletions app/src/components/Broadcast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ function Broadcast() {
const content = await getBroadcast();
if (content.length === 0) return;

toast({
title: t("broadcast"),
description: (
<Markdown className={`text-common`} acceptHtml>
{content}
</Markdown>
),
duration: 30000,
});
toast(
{
title: t("broadcast"),
description: (
<Markdown className={`text-common`} acceptHtml>
{content}
</Markdown>
),
},
30000,
);
}, [init]);

return <div id={`broadcast`} />;
Expand Down
14 changes: 13 additions & 1 deletion app/src/components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ function MessageMenu({
{children}
</DropdownMenuTrigger>
<DropdownMenuContent align={align}>
{isAssistant && end && (
{isAssistant && end ? (
<DropdownMenuItem
onClick={() => {
onEvent && onEvent(message.end !== false ? "restart" : "stop");
Expand All @@ -169,6 +169,18 @@ function MessageMenu({
</>
)}
</DropdownMenuItem>
) : (
message.end !== false && (
<DropdownMenuItem
onClick={() => {
onEvent && onEvent("restart");
setDropdown(false);
}}
>
<RotateCcw className={`h-4 w-4 mr-1.5`} />
{t("message.restart")}
</DropdownMenuItem>
)
)}
<DropdownMenuItem
onClick={() => copyClipboard(filterMessage(message.content))}
Expand Down
6 changes: 5 additions & 1 deletion app/src/components/admin/ChargeWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,11 @@ function ChargeTable({ data, dispatch, onRefresh }: ChargeTableProps) {
dispatch({ type: "set", payload: props });

// scroll to top
scrollUp(getQuerySelector(".admin-content > .scrollarea-viewport")!);
scrollUp(
getQuerySelector(
".admin-content > .scrollarea-viewport",
)!,
);
}}
>
<Settings2 className={`h-4 w-4`} />
Expand Down
2 changes: 2 additions & 0 deletions app/src/components/ui/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ToasterToast = ToastProps & {
description?: React.ReactNode;
action?: ToastActionElement;
created?: number;
duration?: number;
};

const actionTypes = {
Expand Down Expand Up @@ -176,6 +177,7 @@ function toast({ ...props }: Toast, timeout?: number) {
id,
created: Date.now(),
open: true,
duration: timeout,
onOpenChange: (open) => {
if (!open) dismiss();
},
Expand Down
3 changes: 3 additions & 0 deletions app/src/resources/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,9 @@
"cache": "可缓存的模型",
"cacheTip": "可缓存的模型,勾选后当前模型可被缓存并击中缓存",
"cachePlaceholder": "已选 {{length}} 个模型",
"image_store": "图片存储",
"image_storeTip": "OpenAI 渠道 DALL-E 生成的图片将存储于服务端以防止图片失效",
"image_storeNoBackend": "未配置后端域名,无法启用图片存储",
"cacheAll": "设为全部可缓存",
"cacheFree": "设为免费模型可缓存",
"cacheNone": "设为全部不缓存",
Expand Down
5 changes: 4 additions & 1 deletion app/src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"relayPlan": "Subscription Quota Support Staging API",
"relayPlanTip": "Subscription quota supports the transit API, after opening the transit API billing will give priority to the use of user subscription quota\n(Tip: Subscription is a quota of times, the model of billing for tokens may affect the cost)",
"searchQueryTip": "Maximum number of search results, default is 5",
"searchPlaceholder": "DuckDuckGo Access Point (Format only https://example.com)"
"searchPlaceholder": "DuckDuckGo Access Point (Format only https://example.com)",
"image_store": "Picture storage",
"image_storeTip": "Images generated by the OpenAI channel DALL-E will be stored on the server to prevent invalidation of the images",
"image_storeNoBackend": "No backend domain configured, cannot enable image storage"
},
"user": "Users",
"invitation-code": "Invitation Code",
Expand Down
5 changes: 4 additions & 1 deletion app/src/resources/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"relayPlan": "サブスクリプションクォータサポートステージングAPI",
"relayPlanTip": "サブスクリプションクォータはトランジットAPIをサポートしています。トランジットAPI請求を開いた後、ユーザーサブスクリプションクォータの使用が優先されます\n(ヒント:サブスクリプションは時間のクォータであり、トークンの請求モデルはコストに影響する可能性があります)",
"searchQueryTip": "検索結果の最大数、デフォルトは5です",
"searchPlaceholder": "DuckDuckGoアクセスポイント(フォーマットのみhttps://example.com )"
"searchPlaceholder": "DuckDuckGoアクセスポイント(フォーマットのみhttps://example.com )",
"image_store": "画像ストレージ",
"image_storeTip": "OpenAIチャンネルDALL - Eによって生成された画像は、画像の無効化を防ぐためにサーバーに保存されます",
"image_storeNoBackend": "バックエンドドメインが設定されていません。画像ストレージを有効にできません"
},
"user": "ユーザー管理",
"invitation-code": "招待コード",
Expand Down
5 changes: 4 additions & 1 deletion app/src/resources/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"relayPlan": "API промежуточной поддержки квот подписки",
"relayPlanTip": "Квота подписки поддерживает транзитный API, после открытия транзитного API биллинг будет отдавать приоритет использованию пользовательской квоты подписки\n(Совет: Подписка - это квота раз, модель биллинга для токенов может повлиять на стоимость)",
"searchQueryTip": "Максимальное количество результатов поиска, по умолчанию 5",
"searchPlaceholder": "Точка доступа DuckDuckGo (только в формате https://example.com)"
"searchPlaceholder": "Точка доступа DuckDuckGo (только в формате https://example.com)",
"image_store": "Хранение изображений",
"image_storeTip": "Изображения, сгенерированные каналом OpenAI DALL-E, будут храниться на сервере, чтобы предотвратить недействительность изображений",
"image_storeNoBackend": "Нет настроенного внутреннего домена, невозможно включить хранение изображений"
},
"user": "Управление пользователями",
"invitation-code": "Код приглашения",
Expand Down
12 changes: 7 additions & 5 deletions app/src/routes/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,13 @@ function Login() {
form.username.trim() === "root" &&
form.password.trim() === "chatnio123456"
) {
toast({
title: t("admin.default-password"),
description: t("admin.default-password-prompt"),
duration: 30000,
});
toast(
{
title: t("admin.default-password"),
description: t("admin.default-password-prompt"),
},
15000,
);
}

validateToken(globalDispatch, resp.token);
Expand Down
56 changes: 50 additions & 6 deletions app/src/routes/admin/System.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { JSONEditorProvider } from "@/components/EditorProvider.tsx";

type CompProps<T> = {
data: T;
form: SystemProps;
dispatch: (action: any) => void;
onChange: (doToast?: boolean) => Promise<void>;
};
Expand Down Expand Up @@ -606,7 +607,7 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
);
}

function Common({ data, dispatch, onChange }: CompProps<CommonState>) {
function Common({ form, data, dispatch, onChange }: CompProps<CommonState>) {
const { t } = useTranslation();

const { channelModels } = useChannelModels();
Expand All @@ -618,6 +619,24 @@ function Common({ data, dispatch, onChange }: CompProps<CommonState>) {
configParagraph={true}
isCollapsed={true}
>
<ParagraphItem>
<Label className={`flex flex-row items-center`}>
{t("admin.system.image_store")}
<Tips content={t("admin.system.image_storeTip")} />
</Label>
<Switch
checked={data.image_store}
onCheckedChange={(value) => {
dispatch({ type: "update:common.image_store", value });
}}
/>
</ParagraphItem>
{data.image_store && form.general.backend.length === 0 && (
<ParagraphDescription border={true}>
{t("admin.system.image_storeNoBackend")}
</ParagraphDescription>
)}
<ParagraphSpace />
<ParagraphItem>
<Label className={`flex flex-row items-center`}>
{t("admin.system.cache")}
Expand Down Expand Up @@ -846,11 +865,36 @@ function System() {
</CardTitle>
</CardHeader>
<CardContent className={`flex flex-col gap-1`}>
<General data={data.general} dispatch={setData} onChange={doSaving} />
<Site data={data.site} dispatch={setData} onChange={doSaving} />
<Mail data={data.mail} dispatch={setData} onChange={doSaving} />
<Search data={data.search} dispatch={setData} onChange={doSaving} />
<Common data={data.common} dispatch={setData} onChange={doSaving} />
<General
form={data}
data={data.general}
dispatch={setData}
onChange={doSaving}
/>
<Site
form={data}
data={data.site}
dispatch={setData}
onChange={doSaving}
/>
<Mail
form={data}
data={data.mail}
dispatch={setData}
onChange={doSaving}
/>
<Search
form={data}
data={data.search}
dispatch={setData}
onChange={doSaving}
/>
<Common
form={data}
data={data.common}
dispatch={setData}
onChange={doSaving}
/>
</CardContent>
</Card>
</div>
Expand Down
4 changes: 0 additions & 4 deletions app/src/store/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,6 @@ const chatSlice = createSlice({
const conversation = state.conversations[id];
if (!conversation || conversation.messages.length === 0) return;

const last = conversation.messages[conversation.messages.length - 1];
if (last.role !== AssistantRole) return;
conversation.messages.pop();

conversation.messages.push({
role: AssistantRole,
content: "",
Expand Down
7 changes: 7 additions & 0 deletions channel/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package channel

import (
"chat/utils"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
Expand All @@ -15,6 +16,12 @@ func GetInfo(c *gin.Context) {
c.JSON(http.StatusOK, SystemInstance.AsInfo())
}

func AttachmentService(c *gin.Context) {
// /attachments/:hash -> ~/storage/attachments/:hash
hash := c.Param("hash")
c.File(fmt.Sprintf("storage/attachments/%s", hash))
}

func DeleteChannel(c *gin.Context) {
id := c.Param("id")
state := ConduitInstance.DeleteChannel(utils.ParseInt(id))
Expand Down
1 change: 1 addition & 0 deletions channel/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "github.com/gin-gonic/gin"

func Register(app *gin.RouterGroup) {
app.GET("/info", GetInfo)
app.GET("/attachments/:hash", AttachmentService)

app.GET("/admin/channel/list", GetChannelList)
app.POST("/admin/channel/create", CreateChannel)
Expand Down
Loading

0 comments on commit 9ecaf9f

Please sign in to comment.