perf: binary avatar
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 8.3 KiB |
BIN
client/public/icon/logo2.png
Normal file
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -5,3 +5,5 @@ import type { InitDateResponse } from '@/pages/api/system/getInitData';
|
|||||||
export const getInitData = () => GET<InitDateResponse>('/system/getInitData');
|
export const getInitData = () => GET<InitDateResponse>('/system/getInitData');
|
||||||
|
|
||||||
export const getSystemModelList = () => GET<ChatModelItemType[]>('/system/getModels');
|
export const getSystemModelList = () => GET<ChatModelItemType[]>('/system/getModels');
|
||||||
|
|
||||||
|
export const uploadImg = (base64Img: string) => POST<string>('/system/uploadImage', { base64Img });
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useMemo, useEffect } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { formatLinkText } from '@/utils/tools';
|
import { formatLinkText } from '@/utils/tools';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
25
client/src/pages/api/system/img/[id].ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Image } from '@/service/mongo';
|
||||||
|
|
||||||
|
// get the models available to the system
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
const { id } = req.query;
|
||||||
|
|
||||||
|
const data = await Image.findById(id);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new Error('no image');
|
||||||
|
}
|
||||||
|
res.setHeader('Content-Type', 'image/jpeg');
|
||||||
|
|
||||||
|
res.send(data.binary);
|
||||||
|
} catch (error) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
26
client/src/pages/api/system/uploadImage.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Image } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
|
||||||
|
// get the models available to the system
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
const { base64Img } = req.body;
|
||||||
|
const base64Data = base64Img.split(',')[1];
|
||||||
|
|
||||||
|
const { _id } = await Image.create({
|
||||||
|
userId,
|
||||||
|
binary: Buffer.from(base64Data, 'base64')
|
||||||
|
});
|
||||||
|
|
||||||
|
jsonRes(res, { data: `/api/system/img/${_id}` });
|
||||||
|
} catch (error) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -163,7 +163,7 @@ const Home = () => {
|
|||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
>
|
>
|
||||||
<Image src="/icon/logo.png" w={['70px', '120px']} h={['70px', '120px']} alt={''}></Image>
|
<Image src="/icon/logo2.png" w={['70px', '120px']} h={['70px', '120px']} alt={''}></Image>
|
||||||
<Box
|
<Box
|
||||||
className={styles.textlg}
|
className={styles.textlg}
|
||||||
fontWeight={'bold'}
|
fontWeight={'bold'}
|
||||||
|
@@ -114,12 +114,14 @@ const Info = (
|
|||||||
const file = e[0];
|
const file = e[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
try {
|
try {
|
||||||
const base64 = await compressImg({
|
const src = await compressImg({
|
||||||
file,
|
file,
|
||||||
maxW: 100,
|
maxW: 100,
|
||||||
maxH: 100
|
maxH: 100
|
||||||
});
|
});
|
||||||
setValue('avatar', base64);
|
|
||||||
|
setValue('avatar', src);
|
||||||
|
|
||||||
setRefresh((state) => !state);
|
setRefresh((state) => !state);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
|
@@ -104,12 +104,12 @@ const ModelEditForm = ({
|
|||||||
const file = e[0];
|
const file = e[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
try {
|
try {
|
||||||
const base64 = await compressImg({
|
const src = await compressImg({
|
||||||
file,
|
file,
|
||||||
maxW: 100,
|
maxW: 100,
|
||||||
maxH: 100
|
maxH: 100
|
||||||
});
|
});
|
||||||
setValue('avatar', base64);
|
setValue('avatar', src);
|
||||||
setRefresh((state) => !state);
|
setRefresh((state) => !state);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
|
@@ -14,6 +14,7 @@ import dynamic from 'next/dynamic';
|
|||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
import { compressImg } from '@/utils/file';
|
import { compressImg } from '@/utils/file';
|
||||||
import { useCopyData } from '@/utils/tools';
|
import { useCopyData } from '@/utils/tools';
|
||||||
|
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
@@ -112,14 +113,15 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
const file = e[0];
|
const file = e[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
try {
|
try {
|
||||||
const base64 = await compressImg({
|
const src = await compressImg({
|
||||||
file,
|
file,
|
||||||
maxW: 100,
|
maxW: 100,
|
||||||
maxH: 100
|
maxH: 100
|
||||||
});
|
});
|
||||||
|
|
||||||
onclickSave({
|
onclickSave({
|
||||||
...userInfo,
|
...userInfo,
|
||||||
avatar: base64
|
avatar: src
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
|
15
client/src/service/models/image.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Schema, model, models, Model } from 'mongoose';
|
||||||
|
|
||||||
|
const ImageSchema = new Schema({
|
||||||
|
userId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'user',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
binary: {
|
||||||
|
type: Buffer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Image: Model<{ userId: string; binary: Buffer }> =
|
||||||
|
models['image'] || model('image', ImageSchema);
|
@@ -68,3 +68,4 @@ export * from './models/shareChat';
|
|||||||
export * from './models/kb';
|
export * from './models/kb';
|
||||||
export * from './models/inform';
|
export * from './models/inform';
|
||||||
export * from './models/system';
|
export * from './models/system';
|
||||||
|
export * from './models/image';
|
||||||
|
@@ -3,6 +3,7 @@ import Papa from 'papaparse';
|
|||||||
import { getOpenAiEncMap } from './plugin/openai';
|
import { getOpenAiEncMap } from './plugin/openai';
|
||||||
import { getErrText } from './tools';
|
import { getErrText } from './tools';
|
||||||
import { OpenAiChatEnum } from '@/constants/model';
|
import { OpenAiChatEnum } from '@/constants/model';
|
||||||
|
import { uploadImg } from '@/api/system';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取 txt 文件内容
|
* 读取 txt 文件内容
|
||||||
@@ -218,11 +219,11 @@ export const compressImg = ({
|
|||||||
new Promise<string>((resolve, reject) => {
|
new Promise<string>((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
reader.onload = () => {
|
reader.onload = async () => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
img.src = reader.result;
|
img.src = reader.result;
|
||||||
img.onload = () => {
|
img.onload = async () => {
|
||||||
let width = img.width;
|
let width = img.width;
|
||||||
let height = img.height;
|
let height = img.height;
|
||||||
|
|
||||||
@@ -248,14 +249,24 @@ export const compressImg = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.drawImage(img, 0, 0, width, height);
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
const compressedDataUrl = canvas.toDataURL(file.type, 1);
|
const compressedDataUrl = canvas.toDataURL(file.type, 0.8);
|
||||||
// 移除 canvas 元素
|
// 移除 canvas 元素
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
|
|
||||||
if (compressedDataUrl.length > maxSize) {
|
if (compressedDataUrl.length > maxSize) {
|
||||||
return reject('图片太大了');
|
return reject('图片太大了');
|
||||||
}
|
}
|
||||||
resolve(compressedDataUrl);
|
|
||||||
|
const src = await (async () => {
|
||||||
|
try {
|
||||||
|
const src = await uploadImg(compressedDataUrl);
|
||||||
|
return src;
|
||||||
|
} catch (error) {
|
||||||
|
return compressedDataUrl;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
resolve(src);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
reader.onerror = (err) => {
|
reader.onerror = (err) => {
|
||||||
|