feat: app detail
@@ -25,6 +25,7 @@
|
|||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"echarts": "^5.4.1",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^9.0.6",
|
"framer-motion": "^9.0.6",
|
||||||
|
|||||||
26
client/pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ dependencies:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
version: registry.npmmirror.com/dayjs@1.11.7
|
version: registry.npmmirror.com/dayjs@1.11.7
|
||||||
|
echarts:
|
||||||
|
specifier: ^5.4.1
|
||||||
|
version: registry.npmmirror.com/echarts@5.4.1
|
||||||
formidable:
|
formidable:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: registry.npmmirror.com/formidable@2.1.1
|
version: registry.npmmirror.com/formidable@2.1.1
|
||||||
@@ -7341,6 +7344,15 @@ packages:
|
|||||||
safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1
|
safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/echarts@5.4.1:
|
||||||
|
resolution: {integrity: sha512-9ltS3M2JB0w2EhcYjCdmtrJ+6haZcW6acBolMGIuf01Hql1yrIV01L1aRj7jsaaIULJslEP9Z3vKlEmnJaWJVQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/echarts/-/echarts-5.4.1.tgz}
|
||||||
|
name: echarts
|
||||||
|
version: 5.4.1
|
||||||
|
dependencies:
|
||||||
|
tslib: registry.npmmirror.com/tslib@2.3.0
|
||||||
|
zrender: registry.npmmirror.com/zrender@5.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/ee-first@1.1.1:
|
registry.npmmirror.com/ee-first@1.1.1:
|
||||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz}
|
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz}
|
||||||
name: ee-first
|
name: ee-first
|
||||||
@@ -11983,6 +11995,12 @@ packages:
|
|||||||
name: tslib
|
name: tslib
|
||||||
version: 1.14.1
|
version: 1.14.1
|
||||||
|
|
||||||
|
registry.npmmirror.com/tslib@2.3.0:
|
||||||
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz}
|
||||||
|
name: tslib
|
||||||
|
version: 2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/tslib@2.4.0:
|
registry.npmmirror.com/tslib@2.4.0:
|
||||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz}
|
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz}
|
||||||
name: tslib
|
name: tslib
|
||||||
@@ -12580,6 +12598,14 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/zrender@5.4.1:
|
||||||
|
resolution: {integrity: sha512-M4Z05BHWtajY2241EmMPHglDQAJ1UyHQcYsxDNzD9XLSkPDqMq4bB28v9Pb4mvHnVQ0GxyTklZ/69xCFP6RXBA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zrender/-/zrender-5.4.1.tgz}
|
||||||
|
name: zrender
|
||||||
|
version: 5.4.1
|
||||||
|
dependencies:
|
||||||
|
tslib: registry.npmmirror.com/tslib@2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0):
|
registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-2iPUzfwx+g3f0PagOMz2vDO9mZzEp2puFpNe7vrAymVPOEIEUjCPkC4/zy84eAscxIWmTU4j9g6upXYkJdzEFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zustand/-/zustand-4.3.5.tgz}
|
resolution: {integrity: sha512-2iPUzfwx+g3f0PagOMz2vDO9mZzEp2puFpNe7vrAymVPOEIEUjCPkC4/zy84eAscxIWmTU4j9g6upXYkJdzEFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zustand/-/zustand-4.3.5.tgz}
|
||||||
id: registry.npmmirror.com/zustand/4.3.5
|
id: registry.npmmirror.com/zustand/4.3.5
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { AppSchema } from '@/types/mongoSchema';
|
|||||||
import type { AppListItemType, AppUpdateParams } from '@/types/app';
|
import type { AppListItemType, AppUpdateParams } from '@/types/app';
|
||||||
import { RequestPaging } from '../types/index';
|
import { RequestPaging } from '../types/index';
|
||||||
import type { Props as CreateAppProps } from '@/pages/api/app/create';
|
import type { Props as CreateAppProps } from '@/pages/api/app/create';
|
||||||
|
import { addDays } from 'date-fns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取模型列表
|
* 获取模型列表
|
||||||
@@ -42,3 +43,11 @@ export const getShareModelList = (data: { searchText?: string } & RequestPaging)
|
|||||||
*/
|
*/
|
||||||
export const triggerModelCollection = (appId: string) =>
|
export const triggerModelCollection = (appId: string) =>
|
||||||
POST<number>(`/app/share/collection?appId=${appId}`);
|
POST<number>(`/app/share/collection?appId=${appId}`);
|
||||||
|
|
||||||
|
// ====================== data
|
||||||
|
export const getTokenUsage = (data: { appId: string }) =>
|
||||||
|
POST<{ tokenLen: number; date: Date }[]>(`/app/data/tokenUsage`, {
|
||||||
|
...data,
|
||||||
|
start: addDays(new Date(), -7),
|
||||||
|
end: new Date()
|
||||||
|
});
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${
|
|||||||
/**
|
/**
|
||||||
* get history quotes
|
* get history quotes
|
||||||
*/
|
*/
|
||||||
export const getHistoryQuote = (params: { chatId: string; historyId: string }) =>
|
export const getHistoryQuote = (params: { historyId: string; contentId: string }) =>
|
||||||
GET<(QuoteItemType & { _id: string })[]>(`/chat/history/getHistoryQuote`, params);
|
GET<(QuoteItemType & { _id: string })[]>(`/chat/history/getHistoryQuote`, params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update history quote status
|
* update history quote status
|
||||||
*/
|
*/
|
||||||
export const updateHistoryQuote = (params: {
|
export const updateHistoryQuote = (params: {
|
||||||
chatId: string;
|
|
||||||
historyId: string;
|
historyId: string;
|
||||||
|
contentId: string;
|
||||||
quoteId: string;
|
quoteId: string;
|
||||||
sourceText: string;
|
sourceText: string;
|
||||||
}) => GET(`/chat/history/updateHistoryQuote`, params);
|
}) => GET(`/chat/history/updateHistoryQuote`, params);
|
||||||
|
|||||||
199
client/src/components/Charts/Line.tsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
|
||||||
|
const LineChart = ({
|
||||||
|
type,
|
||||||
|
limit = 1000000,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
type: 'blue' | 'deepBlue' | 'green' | 'purple';
|
||||||
|
limit: number;
|
||||||
|
data: number[];
|
||||||
|
}) => {
|
||||||
|
const { screenWidth } = useGlobalStore();
|
||||||
|
|
||||||
|
const Dom = useRef<HTMLDivElement>(null);
|
||||||
|
const myChart = useRef<echarts.ECharts>();
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
blue: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 182, 240, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#36ADEF'
|
||||||
|
},
|
||||||
|
deepBlue: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(94, 159, 235, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false
|
||||||
|
},
|
||||||
|
lineColor: '#3293EC'
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(52, 60, 255, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#8172D8'
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(19, 217, 181, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#00A9A6',
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const option = useMemo(
|
||||||
|
() => ({
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
show: false,
|
||||||
|
boundaryGap: false,
|
||||||
|
data: data.map((_, i) => i)
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
boundaryGap: false,
|
||||||
|
splitNumber: 2,
|
||||||
|
max: 100,
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 2
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line'
|
||||||
|
},
|
||||||
|
formatter: (e: any[]) => `${e[0]?.value || 0}%`
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: new Array(data.length).fill(0),
|
||||||
|
type: 'line',
|
||||||
|
showSymbol: false,
|
||||||
|
smooth: true,
|
||||||
|
animationDuration: 300,
|
||||||
|
animationEasingUpdate: 'linear',
|
||||||
|
areaStyle: {
|
||||||
|
color: map[type].backgroundColor
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: '1',
|
||||||
|
color: map[type].lineColor
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
width: 1.5,
|
||||||
|
color: map[type].lineColor
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
// highlight
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
[limit, type]
|
||||||
|
);
|
||||||
|
|
||||||
|
// init chart
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Dom.current || myChart?.current?.getOption()) return;
|
||||||
|
myChart.current = echarts.init(Dom.current);
|
||||||
|
myChart.current && myChart.current.setOption(option);
|
||||||
|
}, [Dom]);
|
||||||
|
|
||||||
|
// data changed, update
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart?.current?.getOption()) return;
|
||||||
|
|
||||||
|
const uniData = data.map((item) => ((item / limit) * 100).toFixed(2));
|
||||||
|
|
||||||
|
const x = option.xAxis.data;
|
||||||
|
option.xAxis.data = [...x.slice(1), x[x.length - 1] + 1];
|
||||||
|
option.series[0].data = uniData;
|
||||||
|
myChart.current.setOption(option);
|
||||||
|
}, [data, limit]);
|
||||||
|
|
||||||
|
// limit changed, update
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart?.current?.getOption()) return;
|
||||||
|
myChart.current.setOption(option);
|
||||||
|
}, [limit, option, type]);
|
||||||
|
|
||||||
|
// resize chart
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart.current.getOption()) return;
|
||||||
|
myChart.current.resize();
|
||||||
|
}, [screenWidth]);
|
||||||
|
|
||||||
|
return <div ref={Dom} style={{ width: '100%', height: '100%' }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(LineChart);
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688888958304" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2590" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667a424.96 424.96 0 0 1-198.144-48.725334l-183.04 47.658667a36.266667 36.266667 0 0 1-44.245333-44.202667l47.616-182.997333A424.917333 424.917333 0 0 1 85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333z m53.418667 469.333334H373.333333l-4.352 0.298666a32 32 0 0 0 0 63.402667l4.352 0.298667h192.085334l4.309333-0.298667a32 32 0 0 0 0-63.402667L565.418667 554.666667z m85.248-149.333334h-277.333334l-4.352 0.298667a32 32 0 0 0 0 63.402667L373.333333 469.333333h277.333334l4.352-0.298666a32 32 0 0 0 0-63.402667L650.666667 405.333333z" p-id="2591"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689170427347" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1982" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 26.54814777c268.11505778 0 485.45185223 217.33679445 485.45185223 485.45185223s-217.33679445 485.45185223-485.45185223 485.45185223a483.51004445 483.51004445 0 0 1-225.44384-55.43860224l-208.25884445 54.22497223a41.26340779 41.26340779 0 0 1-50.34135665-50.29281223l54.17642667-208.21029888A483.46149888 483.46149888 0 0 1 26.54814777 512C26.54814777 243.88494222 243.88494222 26.54814777 512 26.54814777z m60.77857223 533.9970378H354.22814777l-4.95160889 0.33981553a36.40888889 36.40888889 0 0 0 0 72.13814557l4.95160889 0.33981667h218.55042446l4.90306332-0.33981667a36.40888889 36.40888889 0 0 0 0-72.13814557L572.77857223 560.54518557z m96.99328-169.90814891h-315.54370446l-4.95160889 0.33981667a36.40888889 36.40888889 0 0 0 0 72.13814557L354.22814777 463.45481443h315.54370446l4.95160889-0.33981553a36.40888889 36.40888889 0 0 0 0-72.13814557L669.77185223 390.63703666z" p-id="1983"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1.2 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688888938208" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2436" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667a424.96 424.96 0 0 1-195.712-47.445334l-163.242667 45.525334a53.333333 53.333333 0 0 1-65.706666-65.706667l45.568-163.114667A424.96 424.96 0 0 1 85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333z m0 64A362.666667 362.666667 0 0 0 149.333333 512c0 62.72 15.914667 123.008 45.781334 176.512l6.4 11.52-47.445334 169.984 170.112-47.445333 11.52 6.4A362.666667 362.666667 0 1 0 512 149.333333zM373.333333 554.666667h191.914667a32 32 0 0 1 4.352 63.701333l-4.352 0.298667H373.333333a32 32 0 0 1-4.352-63.701334L373.333333 554.666667h191.914667H373.333333z m0-149.333334h277.546667a32 32 0 0 1 4.309333 63.701334l-4.309333 0.298666H373.333333a32 32 0 0 1-4.352-63.701333L373.333333 405.333333h277.546667H373.333333z" p-id="2437"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689170412707" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1828" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 26.54814777c268.11505778 0 485.45185223 217.33679445 485.45185223 485.45185223s-217.33679445 485.45185223-485.45185223 485.45185223a483.51004445 483.51004445 0 0 1-222.67676445-53.98224668l-185.73387889 51.79771335a60.6814811 60.6814811 0 0 1-74.75958443-74.75958557l51.84625778-185.58824334A483.51004445 483.51004445 0 0 1 26.54814777 512C26.54814777 243.88494222 243.88494222 26.54814777 512 26.54814777z m0 72.81777778A412.63407445 412.63407445 0 0 0 99.36592555 512c0 71.36142222 18.10735445 139.95576889 52.08898446 200.83143111l7.28177778 13.1072-53.98224669 193.40401778 193.54965333-53.98224555 13.1072 7.28177778A412.63407445 412.63407445 0 1 0 512 99.36592555zM354.22814777 560.54518557h218.35624334a36.40888889 36.40888889 0 0 1 4.95160889 72.4779611l-4.95160889 0.33981667H354.22814777a36.40888889 36.40888889 0 0 1-4.95160889-72.47796224L354.22814777 560.54518557h218.35624334H354.22814777z m0-169.90814891h315.78643001a36.40888889 36.40888889 0 0 1 4.90306332 72.47796224l-4.90306332 0.33981553H354.22814777a36.40888889 36.40888889 0 0 1-4.95160889-72.4779611L354.22814777 390.63703666h315.78643001H354.22814777z" p-id="1829"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.5 KiB |
1
client/src/components/Icon/icons/light/fullScreen.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689170193460" class="icon" viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2859" data-spm-anchor-id="a313x.7781069.0.i1" xmlns:xlink="http://www.w3.org/1999/xlink" width="64.1875" height="64"><path d="M733.549304 0l116.434359 116.23452-226.402521 226.40252 57.053835 57.068109 226.459617-226.445342 120.616689 120.41685V0H733.549304zM689.513507 619.855586l-57.068108 57.068109 224.232847 224.232847-122.64362 122.843458h293.676657V729.838022l-114.007751 114.207588-224.190025-224.190024zM338.197775 404.144414l57.068109-57.068109L171.033037 122.843458 293.676657 0H0v294.161978l114.022025-114.207588 224.17575 224.190024zM347.076305 624.294851L120.616689 850.754468 0 730.323343v293.676657h294.161978l-116.420084-116.23452 226.40252-226.40252-57.068109-57.068109z" p-id="2860"></path></svg>
|
||||||
|
After Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688217440856" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2309" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M819.823 83.694H206.991c-67.703 0-122.588 54.885-122.588 122.588v612.833c0 67.703 54.885 122.588 122.588 122.588h612.833c67.703 0 122.588-54.885 122.588-122.588V206.282c-0.001-67.703-54.885-122.588-122.589-122.588z m-124.435 63.313v241.142H331.772V147.007h363.616z m185.787 672.274c0.027 33.765-27.323 61.158-61.088 61.185H207.133c-16.389 0-31.864-6.297-43.454-17.887s-18.039-26.91-18.039-43.298v-612.94c0.061-33.923 27.57-61.395 61.493-61.41h61.327v245.294c-0.05 33.771 27.286 61.187 61.057 61.237h367.888c33.853 0 61.299-27.387 61.299-61.237V144.931h61.206c33.872 0.036 61.301 27.524 61.265 61.396V819.281z" fill="" p-id="2310"></path><path d="M574.817 329.936c17.483 0 31.656-14.173 31.656-31.656v-61.292c0-17.483-14.173-31.656-31.656-31.656s-31.656 14.173-31.656 31.656v61.292c0 17.483 14.173 31.656 31.656 31.656z" fill="" p-id="2311"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689170210122" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3013" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M862.23416889 24.68295111H164.96753778c-77.03096889 0-139.47790222 62.44693333-139.47790223 139.47790222v697.26776889c0 77.03096889 62.44693333 139.47790222 139.47790223 139.47790223h697.26776889c77.03096889 0 139.47790222-62.44693333 139.47790222-139.47790223V164.16085333c-0.00113778-77.03096889-62.44693333-139.47790222-139.47904-139.47790222z m-141.57937778 72.03612444v274.3660089H306.94058667V96.71907555h413.71420444z m211.38432 764.89841778c0.03072 38.41706667-31.08750222 69.58421333-69.50456889 69.61493334H165.12910222c-18.64704 0-36.25415111-7.16458667-49.44099555-20.35143112s-20.52437333-30.6176-20.52437334-49.26350222v-697.38951111c0.06940445-38.59683555 31.36853333-69.85386667 69.96536889-69.87093333h69.77649778v279.09006222c-0.05688889 38.42389333 31.04540445 69.61720889 69.46929778 69.67409778h418.57479111c38.51719111 0 69.74464-31.16032 69.74464-69.67409778V94.35704889h69.63882666c38.53880889 0.04096 69.74691555 31.31619555 69.70595556 69.85500444V861.61749333z" p-id="3014"></path><path d="M583.47178667 304.85162667c19.89176889 0 36.01749333-16.12572445 36.01749333-36.01749334v-69.73667555c0-19.89176889-16.12572445-36.01749333-36.01749333-36.01749333s-36.01749333 16.12572445-36.01749334 36.01749333v69.73667555c0 19.89176889 16.12572445 36.01749333 36.01749334 36.01749334z" p-id="3015"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -3,7 +3,7 @@ import type { IconProps } from '@chakra-ui/react';
|
|||||||
import { Icon } from '@chakra-ui/react';
|
import { Icon } from '@chakra-ui/react';
|
||||||
|
|
||||||
const map = {
|
const map = {
|
||||||
model: require('./icons/model.svg').default,
|
app: require('./icons/app.svg').default,
|
||||||
copy: require('./icons/copy.svg').default,
|
copy: require('./icons/copy.svg').default,
|
||||||
chatSend: require('./icons/chatSend.svg').default,
|
chatSend: require('./icons/chatSend.svg').default,
|
||||||
delete: require('./icons/delete.svg').default,
|
delete: require('./icons/delete.svg').default,
|
||||||
@@ -16,7 +16,7 @@ const map = {
|
|||||||
backFill: require('./icons/fill/back.svg').default,
|
backFill: require('./icons/fill/back.svg').default,
|
||||||
more: require('./icons/more.svg').default,
|
more: require('./icons/more.svg').default,
|
||||||
tabbarChat: require('./icons/phoneTabbar/chat.svg').default,
|
tabbarChat: require('./icons/phoneTabbar/chat.svg').default,
|
||||||
tabbarModel: require('./icons/phoneTabbar/model.svg').default,
|
tabbarModel: require('./icons/phoneTabbar/app.svg').default,
|
||||||
tabbarMore: require('./icons/phoneTabbar/more.svg').default,
|
tabbarMore: require('./icons/phoneTabbar/more.svg').default,
|
||||||
tabbarMe: require('./icons/phoneTabbar/me.svg').default,
|
tabbarMe: require('./icons/phoneTabbar/me.svg').default,
|
||||||
closeSolid: require('./icons/closeSolid.svg').default,
|
closeSolid: require('./icons/closeSolid.svg').default,
|
||||||
@@ -50,6 +50,7 @@ const map = {
|
|||||||
welcomeText: require('./icons/modules/welcomeText.svg').default,
|
welcomeText: require('./icons/modules/welcomeText.svg').default,
|
||||||
variable: require('./icons/modules/variable.svg').default,
|
variable: require('./icons/modules/variable.svg').default,
|
||||||
setTop: require('./icons/light/setTop.svg').default,
|
setTop: require('./icons/light/setTop.svg').default,
|
||||||
|
fullScreenLight: require('./icons/light/fullScreen.svg').default,
|
||||||
voice: require('./icons/voice.svg').default
|
voice: require('./icons/voice.svg').default
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ export enum NavbarTypeEnum {
|
|||||||
|
|
||||||
const Navbar = ({ unread }: { unread: number }) => {
|
const Navbar = ({ unread }: { unread: number }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { userInfo, lastModelId } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { lastChatAppId, lastChatId } = useChatStore();
|
const { lastChatAppId, lastHistoryId } = useChatStore();
|
||||||
const navbarList = useMemo(
|
const navbarList = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
label: '聊天',
|
label: '聊天',
|
||||||
icon: 'chatLight',
|
icon: 'chatLight',
|
||||||
activeIcon: 'chatFill',
|
activeIcon: 'chatFill',
|
||||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
link: `/chat?appId=${lastChatAppId}&historyId=${lastHistoryId}`,
|
||||||
activeLink: ['/chat']
|
activeLink: ['/chat']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '应用',
|
label: '应用',
|
||||||
icon: 'tabbarModel',
|
icon: 'tabbarModel',
|
||||||
activeIcon: 'model',
|
activeIcon: 'app',
|
||||||
link: `/app/list`,
|
link: `/app/list`,
|
||||||
activeLink: ['/app/list', '/app/detail']
|
activeLink: ['/app/list', '/app/detail']
|
||||||
},
|
},
|
||||||
@@ -56,7 +56,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
activeLink: ['/number']
|
activeLink: ['/number']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[lastChatId, lastChatAppId]
|
[lastHistoryId, lastChatAppId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemStyles: any = {
|
const itemStyles: any = {
|
||||||
@@ -99,10 +99,8 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
{/* 导航列表 */}
|
{/* 导航列表 */}
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
{navbarList.map((item) => (
|
{navbarList.map((item) => (
|
||||||
<Link
|
<Box
|
||||||
key={item.link}
|
key={item.link}
|
||||||
as={NextLink}
|
|
||||||
href={item.link}
|
|
||||||
{...itemStyles}
|
{...itemStyles}
|
||||||
{...(item.activeLink.includes(router.pathname)
|
{...(item.activeLink.includes(router.pathname)
|
||||||
? {
|
? {
|
||||||
@@ -114,6 +112,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
color: 'myGray.500',
|
color: 'myGray.500',
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
})}
|
})}
|
||||||
|
onClick={() => router.push(item.link)}
|
||||||
>
|
>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={
|
name={
|
||||||
@@ -127,7 +126,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
<Box fontSize={'12px'} transform={'scale(0.9)'} mt={'5px'} lineHeight={1}>
|
<Box fontSize={'12px'} transform={'scale(0.9)'} mt={'5px'} lineHeight={1}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
{unread > 0 && (
|
{unread > 0 && (
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import Badge from '../Badge';
|
|||||||
|
|
||||||
const NavbarPhone = ({ unread }: { unread: number }) => {
|
const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { lastChatAppId, lastChatId } = useChatStore();
|
const { lastChatAppId, lastHistoryId } = useChatStore();
|
||||||
const navbarList = useMemo(
|
const navbarList = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
label: '聊天',
|
label: '聊天',
|
||||||
icon: 'tabbarChat',
|
icon: 'tabbarChat',
|
||||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
link: `/chat?appId=${lastChatAppId}&historyId=${lastHistoryId}`,
|
||||||
activeLink: ['/chat'],
|
activeLink: ['/chat'],
|
||||||
unread: 0
|
unread: 0
|
||||||
},
|
},
|
||||||
@@ -39,7 +39,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
|
|||||||
unread
|
unread
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[lastChatId, lastChatAppId, unread]
|
[lastHistoryId, lastChatAppId, unread]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1127,563 +1127,3 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// export const classifyQuestionDemo: AppItemType = {
|
|
||||||
// id: 'classifyQuestionDemo',
|
|
||||||
// // 标记字段
|
|
||||||
// modules: [
|
|
||||||
// {
|
|
||||||
// moduleId: '1',
|
|
||||||
// type: AppModuleItemTypeEnum.http,
|
|
||||||
// url: '/openapi/modules/agent/classifyQuestion',
|
|
||||||
// body: {
|
|
||||||
// systemPrompt:
|
|
||||||
// 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。',
|
|
||||||
// agents: [
|
|
||||||
// {
|
|
||||||
// desc: '打招呼、问候、身份询问等问题',
|
|
||||||
// key: 'a'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "询问 'laf 使用和介绍的问题'",
|
|
||||||
// key: 'b'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "询问 'laf 代码问题'",
|
|
||||||
// key: 'c'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: '其他问题',
|
|
||||||
// key: 'd'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'a',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'a',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'b',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'b',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'c',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'c',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'd',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'd',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'a',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '你好,我是 Laf 助手,有什么可以帮助你的?'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// // laf 知识库
|
|
||||||
// {
|
|
||||||
// moduleId: 'b',
|
|
||||||
// type: 'http',
|
|
||||||
// url: '/openapi/modules/kb/search',
|
|
||||||
// body: {
|
|
||||||
// kb_ids: ['646627f4f7b896cfd8910e24'],
|
|
||||||
// similarity: 0.82,
|
|
||||||
// limit: 4,
|
|
||||||
// maxToken: 2500
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'rawSearch',
|
|
||||||
// value: undefined,
|
|
||||||
// response: true,
|
|
||||||
// targets: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'quotePrompt',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'lafchat',
|
|
||||||
// key: 'quotePrompt'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// // laf 对话
|
|
||||||
// {
|
|
||||||
// moduleId: 'lafchat',
|
|
||||||
// type: 'http',
|
|
||||||
// url: '/openapi/modules/chat/gpt',
|
|
||||||
// body: {
|
|
||||||
// model: 'gpt-3.5-turbo-16k',
|
|
||||||
// temperature: 5,
|
|
||||||
// maxToken: 4000,
|
|
||||||
// systemPrompt: '知识库是关于 Laf 的内容。',
|
|
||||||
// limitPrompt: '你仅能参考知识库的内容回答问题,不能超出知识库范围。'
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: 'quotePrompt',
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'answer',
|
|
||||||
// answer: true,
|
|
||||||
// value: undefined,
|
|
||||||
// targets: []
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// // laf 代码知识库
|
|
||||||
// {
|
|
||||||
// moduleId: 'c',
|
|
||||||
// type: 'http',
|
|
||||||
// url: '/openapi/modules/kb/search',
|
|
||||||
// body: {
|
|
||||||
// kb_ids: ['646627f4f7b896cfd8910e26'],
|
|
||||||
// similarity: 0.8,
|
|
||||||
// limit: 4,
|
|
||||||
// maxToken: 2500
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'rawSearch',
|
|
||||||
// value: undefined,
|
|
||||||
// response: true,
|
|
||||||
// targets: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'quotePrompt',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'lafcodechat',
|
|
||||||
// key: 'quotePrompt'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// // laf代码对话
|
|
||||||
// {
|
|
||||||
// moduleId: 'lafcodechat',
|
|
||||||
// type: 'http',
|
|
||||||
// url: '/openapi/modules/chat/gpt',
|
|
||||||
// body: {
|
|
||||||
// model: 'gpt-3.5-turbo-16k',
|
|
||||||
// temperature: 5,
|
|
||||||
// maxToken: 4000,
|
|
||||||
// systemPrompt: `下例是laf结构\n~~~ts\nimport cloud from '@lafjs/cloud'\nexport default async function(ctx: FunctionContext){\nreturn \"success\"\n};\n~~~\n下例是@lafjs/cloud的api\n~~~\ncloud.fetch//完全等同axios\ncloud.database()// 获取操作数据库实例,和mongo语法相似.\ncloud.getToken(payload)//获取token\ncloud.parseToken(token)//解析token\n// 下面是持久化缓存Api\ncloud.shared.set(key,val); //设置缓存,仅能设置值,无法设置过期时间\ncloud.shared.get(key);\ncloud.shared.has(key); \ncloud.shared.delete(key); \ncloud.shared.clear(); \n~~~\n下例是ctx对象\n~~~\nctx.requestId\nctx.method\nctx.headers//请求的 headers, ctx.headers.get('Content-Type')获取Content-Type的值\nctx.user//Http Bearer Token 认证时,获取token值\nctx.query\nctx.body\nctx.request//同express的Request\nctx.response//同express的Response\nctx.socket/WebSocket 实例\nctx.files//上传的文件 (File对象数组)\nctx.env//自定义的环境变量\n~~~\n下例是数据库获取数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {minMemory} = ctx.query\nconst _ = db.command;\nconst {data: users,total} = collection(\"users\")\n .where({//条件查询\n category: \"computer\",\n type: {\n memory: _gt(minMemory), \n }\n }) \n .skip(10)//跳过10条-分页时使用\n .limit(10)//仅返回10条\n .orderBy(\"name\", \"asc\") \n .orderBy(\"age\", \"desc\")\n .field({age:true,name: false})//返回age不返回name\n}\nconst {data:user} = db.where({phone:req.body.phone}).getOne()//获取一个满足条件的用户\nreturn {users,total}\n~~~\n下例是数据库添加数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext) {\n const {username} = ctx.body\n const {id:userId, ok} = await collection(\"users\")\n .add({\n username, \n })\n if(ok) return {userId}\n return {code:500,message:\"失败\"}\n}\n~~~\n下例是数据库更新数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\n//id直接修改\nawait collection(\"user\").doc(\"id\").update({\n name: \"Hey\",\n});\n//批量更新\nawait collection\n .where({name:\"1234\"})\n .update({\n age:18\n })\nconst _ = db.command;\nawait collection(\"user\")\n .doc(id)\n .set({\n count: _.inc(1)\n count: _.mul(2)\n count: _.remove()\n users: _.push([\"aaa\", \"bbb\"])\n users: _.push(\"aaa\")\n users: _.pop()\n users: _.unshift()\n users: _.shift()\n })\n}\n~~~\n下例是删除数据库记录\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\ncollection(\"user\").doc(id).remove();\n//批量删除\ncollection\n .where({age:18}) \n .remove({multi: true})\nreturn \"success\"\n}\n~~~\n你只需返回 ts 代码块!不需要说明.\n用户的问题与 Laf 代码无关时,你直接回答: \"我不确定,我只会写 Laf 代码。\"`,
|
|
||||||
// limitPrompt:
|
|
||||||
// '你是由 Laf 团队开发的代码助手,把我的需求用 Laf 代码实现.参考知识库中 Laf 的例子.'
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: 'quotePrompt',
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'answer',
|
|
||||||
// answer: true,
|
|
||||||
// value: undefined,
|
|
||||||
// targets: []
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'd',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '你好,我没有理解你的意思,请问你有什么 Laf 相关的问题么?'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const lafClassifyQuestionDemo: AppItemType = {
|
|
||||||
// id: 'test',
|
|
||||||
// // 标记字段
|
|
||||||
// modules: [
|
|
||||||
// {
|
|
||||||
// moduleId: '1',
|
|
||||||
// type: AppModuleItemTypeEnum.http,
|
|
||||||
// url: '/openapi/modules/agent/classifyQuestion',
|
|
||||||
// body: {
|
|
||||||
// systemPrompt:
|
|
||||||
// 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。\nsealos是一个 k8s 云平台,可以让用户快速部署云服务。',
|
|
||||||
// agents: [
|
|
||||||
// {
|
|
||||||
// desc: '打招呼、问候、身份询问等问题',
|
|
||||||
// key: 'a'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "询问 'laf 的使用和介绍'",
|
|
||||||
// key: 'b'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "询问 'laf 代码相关问题'",
|
|
||||||
// key: 'c'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "用户希望运行或知道 'laf 代码' 运行结果",
|
|
||||||
// key: 'g'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: "询问 'sealos 相关问题'",
|
|
||||||
// key: 'd'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: '其他问题',
|
|
||||||
// key: 'e'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// desc: '商务类问题',
|
|
||||||
// key: 'f'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'a',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'a',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'b',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'b',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'c',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'c',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'd',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'd',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'e',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'e',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'f',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'f',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'g',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'g',
|
|
||||||
// key: SystemInputEnum.switch
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'a',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '你好,我是 环界云 助手,你有什么 Laf 或者 sealos 的 问题么?'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'b',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '查询 Laf 通用知识库:xxxxx'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'c',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '查询 Laf 代码知识库:xxxxx'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'd',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '查询 sealos 通用知识库: xxxx'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'e',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '其他问题。回复引导语:xxxx'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'f',
|
|
||||||
// type: 'answer',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SpecificInputEnum.answerText,
|
|
||||||
// value: '商务类问题,联系方式:xxxxx'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: []
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'g',
|
|
||||||
// type: 'http',
|
|
||||||
// url: '/openapi/modules/agent/extract',
|
|
||||||
// body: {
|
|
||||||
// description: '运行 laf 代码',
|
|
||||||
// agents: [
|
|
||||||
// {
|
|
||||||
// desc: '代码内容',
|
|
||||||
// key: 'code'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.switch,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.history,
|
|
||||||
// value: undefined
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: SystemInputEnum.userChatInput,
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'code',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// moduleId: 'code_run',
|
|
||||||
// key: 'code'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// moduleId: 'code_run',
|
|
||||||
// type: AppModuleItemTypeEnum.http,
|
|
||||||
// url: 'https://v1cde7.laf.run/tess',
|
|
||||||
// body: {},
|
|
||||||
// inputs: [
|
|
||||||
// {
|
|
||||||
// key: 'code',
|
|
||||||
// value: undefined
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// outputs: [
|
|
||||||
// {
|
|
||||||
// key: 'star',
|
|
||||||
// value: undefined,
|
|
||||||
// targets: []
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// };
|
|
||||||
|
|||||||
@@ -93,6 +93,5 @@ export const defaultApp: AppSchema = {
|
|||||||
|
|
||||||
export const defaultShareChat: ShareChatEditType = {
|
export const defaultShareChat: ShareChatEditType = {
|
||||||
name: '',
|
name: '',
|
||||||
password: '',
|
|
||||||
maxContext: 5
|
maxContext: 5
|
||||||
};
|
};
|
||||||
|
|||||||
53
client/src/pages/api/app/data/tokenUsage.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Bill } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
import type { ChatHistoryItemType } from '@/types/chat';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
|
||||||
|
/* get one app chat history content number. */
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
const { appId, start, end } = req.body as { appId: string; start: Date; end: Date };
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const result = await Bill.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
appId: new Types.ObjectId(appId),
|
||||||
|
userId: new Types.ObjectId(userId),
|
||||||
|
time: { $gte: new Date(start) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
year: { $year: '$time' },
|
||||||
|
month: { $month: '$time' },
|
||||||
|
day: { $dayOfMonth: '$time' }
|
||||||
|
},
|
||||||
|
tokenLen: { $sum: '$tokenLen' } // 对tokenLen的值求和
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
|
||||||
|
tokenLen: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $sort: { date: 1 } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
|
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
// 验证是否是该用户的 model
|
// 验证是否是该用户的 app
|
||||||
await authApp({
|
await authApp({
|
||||||
appId,
|
appId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ import { Types } from 'mongoose';
|
|||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
const { chatId, historyId } = req.query as {
|
const { historyId, contentId } = req.query as {
|
||||||
chatId: string;
|
|
||||||
historyId: string;
|
historyId: string;
|
||||||
|
contentId: string;
|
||||||
};
|
};
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
if (!chatId || !historyId) {
|
if (!historyId || !contentId) {
|
||||||
throw new Error('params is error');
|
throw new Error('params is error');
|
||||||
}
|
}
|
||||||
|
|
||||||
const history = await Chat.aggregate([
|
const history = await Chat.aggregate([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
_id: new Types.ObjectId(chatId),
|
_id: new Types.ObjectId(historyId),
|
||||||
userId: new Types.ObjectId(userId)
|
userId: new Types.ObjectId(userId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
'content._id': new Types.ObjectId(historyId)
|
'content._id': new Types.ObjectId(contentId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { Types } from 'mongoose';
|
|||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
let {
|
let {
|
||||||
chatId,
|
|
||||||
historyId,
|
historyId,
|
||||||
|
contentId,
|
||||||
quoteId,
|
quoteId,
|
||||||
sourceText = ''
|
sourceText = ''
|
||||||
} = req.query as {
|
} = req.query as {
|
||||||
chatId: string;
|
|
||||||
historyId: string;
|
historyId: string;
|
||||||
|
contentId: string;
|
||||||
quoteId: string;
|
quoteId: string;
|
||||||
sourceText: string;
|
sourceText: string;
|
||||||
};
|
};
|
||||||
@@ -21,15 +21,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
if (!chatId || !historyId || !quoteId) {
|
if (!contentId || !historyId || !quoteId) {
|
||||||
throw new Error('params is error');
|
throw new Error('params is error');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Chat.updateOne(
|
await Chat.updateOne(
|
||||||
{
|
{
|
||||||
_id: new Types.ObjectId(chatId),
|
_id: new Types.ObjectId(historyId),
|
||||||
userId: new Types.ObjectId(userId),
|
userId: new Types.ObjectId(userId),
|
||||||
'content._id': new Types.ObjectId(historyId)
|
'content._id': new Types.ObjectId(contentId)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } =
|
const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } =
|
||||||
await (async () => {
|
await (async () => {
|
||||||
if (historyId) {
|
if (historyId) {
|
||||||
// auth chatId
|
// auth historyId
|
||||||
const chat = await Chat.findOne({
|
const chat = await Chat.findOne({
|
||||||
_id: historyId,
|
_id: historyId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
throw new Error('params is error');
|
throw new Error('params is error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// auth model
|
// auth app
|
||||||
const { app } = await authApp({
|
const { app } = await authApp({
|
||||||
appId,
|
appId,
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await appKbSearch({
|
const result = await appKbSearch({
|
||||||
model: app,
|
app,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote: [],
|
fixedQuote: [],
|
||||||
prompt: prompts[prompts.length - 1],
|
prompt: prompts[prompts.length - 1],
|
||||||
@@ -81,21 +81,21 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function appKbSearch({
|
export async function appKbSearch({
|
||||||
model,
|
app,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote = [],
|
fixedQuote = [],
|
||||||
prompt,
|
prompt,
|
||||||
similarity = 0.8,
|
similarity = 0.8,
|
||||||
limit = 5
|
limit = 5
|
||||||
}: {
|
}: {
|
||||||
model: AppSchema;
|
app: AppSchema;
|
||||||
userId: string;
|
userId: string;
|
||||||
fixedQuote?: QuoteItemType[];
|
fixedQuote?: QuoteItemType[];
|
||||||
prompt: ChatItemType;
|
prompt: ChatItemType;
|
||||||
similarity: number;
|
similarity: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}): Promise<Response> {
|
}): Promise<Response> {
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[app.chat.chatModel];
|
||||||
|
|
||||||
// get vector
|
// get vector
|
||||||
const promptVector = await openaiEmbedding({
|
const promptVector = await openaiEmbedding({
|
||||||
@@ -107,7 +107,7 @@ export async function appKbSearch({
|
|||||||
const res: any = await PgClient.query(
|
const res: any = await PgClient.query(
|
||||||
`BEGIN;
|
`BEGIN;
|
||||||
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
|
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
|
||||||
select id,q,a,source from modelData where kb_id IN (${model.chat.relatedKbs
|
select id,q,a,source from modelData where kb_id IN (${app.chat.relatedKbs
|
||||||
.map((item) => `'${item}'`)
|
.map((item) => `'${item}'`)
|
||||||
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
|
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
|
||||||
promptVector[0]
|
promptVector[0]
|
||||||
@@ -133,32 +133,32 @@ export async function appKbSearch({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 计算固定提示词的 token 数量
|
// 计算固定提示词的 token 数量
|
||||||
const userSystemPrompt = model.chat.systemPrompt // user system prompt
|
const userSystemPrompt = app.chat.systemPrompt // user system prompt
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: model.chat.systemPrompt
|
value: app.chat.systemPrompt
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
const userLimitPrompt = [
|
const userLimitPrompt = [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.Human,
|
obj: ChatRoleEnum.Human,
|
||||||
value: model.chat.limitPrompt
|
value: app.chat.limitPrompt
|
||||||
? model.chat.limitPrompt
|
? app.chat.limitPrompt
|
||||||
: `知识库是关于 ${model.name} 的内容,参考知识库回答问题。与 "${model.name}" 无关内容,直接回复: "我不知道"。`
|
: `知识库是关于 ${app.name} 的内容,参考知识库回答问题。与 "${app.name}" 无关内容,直接回复: "我不知道"。`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const fixedSystemTokens = modelToolMap.countTokens({
|
const fixedSystemTokens = modelToolMap.countTokens({
|
||||||
model: model.chat.chatModel,
|
model: app.chat.chatModel,
|
||||||
messages: [...userSystemPrompt, ...userLimitPrompt]
|
messages: [...userSystemPrompt, ...userLimitPrompt]
|
||||||
});
|
});
|
||||||
|
|
||||||
// filter part quote by maxToken
|
// filter part quote by maxToken
|
||||||
const sliceResult = modelToolMap
|
const sliceResult = modelToolMap
|
||||||
.tokenSlice({
|
.tokenSlice({
|
||||||
model: model.chat.chatModel,
|
model: app.chat.chatModel,
|
||||||
maxToken: modelConstantsData.systemMaxToken - fixedSystemTokens,
|
maxToken: modelConstantsData.systemMaxToken - fixedSystemTokens,
|
||||||
messages: filterSearch.map((item, i) => ({
|
messages: filterSearch.map((item, i) => ({
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export async function extract({ agents, history = [], userChatInput, description
|
|||||||
agents.forEach((item) => {
|
agents.forEach((item) => {
|
||||||
properties[item.key] = {
|
properties[item.key] = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: item.desc
|
description: item.value
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { Types } from 'mongoose';
|
|||||||
import { moduleFetch } from '@/service/api/request';
|
import { moduleFetch } from '@/service/api/request';
|
||||||
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
||||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||||
|
import { pushChatBill } from '@/service/events/pushBill';
|
||||||
|
import { BillTypeEnum } from '@/constants/user';
|
||||||
|
|
||||||
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
||||||
type FastGptWebChatProps = {
|
type FastGptWebChatProps = {
|
||||||
@@ -168,6 +170,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushChatBill({
|
||||||
|
isPay: true,
|
||||||
|
chatModel: 'gpt-3.5-turbo',
|
||||||
|
userId,
|
||||||
|
appId,
|
||||||
|
textLen: 1,
|
||||||
|
tokens: 100,
|
||||||
|
type: BillTypeEnum.chat
|
||||||
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (stream) {
|
if (stream) {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
kbId: id
|
kbId: id
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete related model
|
// delete related app
|
||||||
await App.updateMany(
|
await App.updateMany(
|
||||||
{
|
{
|
||||||
userId
|
userId
|
||||||
|
|||||||
198
client/src/pages/app/detail/components/Charts/TokenUsage.tsx
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
import { getTokenUsage } from '@/api/app';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
blue: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 182, 240, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#36ADEF'
|
||||||
|
},
|
||||||
|
deepBlue: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(94, 159, 235, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false
|
||||||
|
},
|
||||||
|
lineColor: '#3293EC'
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(52, 60, 255, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#8172D8'
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
backgroundColor: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(19, 217, 181, 0)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
global: false // 缺省为 false
|
||||||
|
},
|
||||||
|
lineColor: '#00A9A6',
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenUsage = ({ appId }: { appId: string }) => {
|
||||||
|
const { screenWidth } = useGlobalStore();
|
||||||
|
|
||||||
|
const Dom = useRef<HTMLDivElement>(null);
|
||||||
|
const myChart = useRef<echarts.ECharts>();
|
||||||
|
const { data = [] } = useQuery(['init'], () => getTokenUsage({ appId }));
|
||||||
|
|
||||||
|
const option = useMemo(
|
||||||
|
() => ({
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
show: false,
|
||||||
|
boundaryGap: false,
|
||||||
|
data: data.map((item) => item.date)
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
boundaryGap: false,
|
||||||
|
splitNumber: 5,
|
||||||
|
max: Math.max(...data.map((item) => item.tokenLen)),
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false,
|
||||||
|
left: 5,
|
||||||
|
right: 5,
|
||||||
|
top: 5,
|
||||||
|
bottom: 5
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line'
|
||||||
|
},
|
||||||
|
formatter: (e: any[]) => {
|
||||||
|
const data = e[0];
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
<div>${dayjs(data.axisValue).format('YYYY/MM/DD')}</div>
|
||||||
|
<div>${((e[0]?.value || 0) / 1000).toFixed(2)}k Tokens</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: data.map((item) => item.tokenLen),
|
||||||
|
type: 'line',
|
||||||
|
showSymbol: true,
|
||||||
|
animationDuration: 300,
|
||||||
|
animationEasingUpdate: 'linear',
|
||||||
|
areaStyle: {
|
||||||
|
color: map['blue'].backgroundColor
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: '1',
|
||||||
|
color: map['blue'].lineColor
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
width: 1.5,
|
||||||
|
color: map['blue'].lineColor
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
// highlight
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
// init chart
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Dom.current || myChart?.current?.getOption()) return;
|
||||||
|
myChart.current = echarts.init(Dom.current);
|
||||||
|
myChart.current && myChart.current.setOption(option);
|
||||||
|
}, [Dom]);
|
||||||
|
|
||||||
|
// data changed, update
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart?.current?.getOption()) return;
|
||||||
|
myChart.current.setOption(option);
|
||||||
|
}, [data, option]);
|
||||||
|
|
||||||
|
// limit changed, update
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart?.current?.getOption()) return;
|
||||||
|
myChart.current.setOption(option);
|
||||||
|
}, [option]);
|
||||||
|
|
||||||
|
// resize chart
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myChart.current || !myChart.current.getOption()) return;
|
||||||
|
myChart.current.resize();
|
||||||
|
}, [screenWidth]);
|
||||||
|
|
||||||
|
return <div ref={Dom} style={{ width: '100%', height: '100%' }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TokenUsage);
|
||||||
194
client/src/pages/app/detail/components/InfoModal.tsx
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { AppSchema } from '@/types/mongoSchema';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { delModelById, putAppById } from '@/api/app';
|
||||||
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
|
import { compressImg } from '@/utils/file';
|
||||||
|
import { getErrText } from '@/utils/tools';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
|
const InfoModal = ({
|
||||||
|
defaultApp,
|
||||||
|
onClose,
|
||||||
|
onSuccess
|
||||||
|
}: {
|
||||||
|
defaultApp: AppSchema;
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
|
fileType: '.jpg,.png',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
handleSubmit
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: defaultApp
|
||||||
|
});
|
||||||
|
const [btnLoading, setBtnLoading] = useState(false);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
|
// 提交保存模型修改
|
||||||
|
const saveSubmitSuccess = useCallback(
|
||||||
|
async (data: AppSchema) => {
|
||||||
|
setBtnLoading(true);
|
||||||
|
try {
|
||||||
|
await putAppById(data._id, {
|
||||||
|
name: data.name,
|
||||||
|
avatar: data.avatar,
|
||||||
|
intro: data.intro,
|
||||||
|
chat: data.chat,
|
||||||
|
share: data.share
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '更新失败',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setBtnLoading(false);
|
||||||
|
},
|
||||||
|
[toast]
|
||||||
|
);
|
||||||
|
// 提交保存表单失败
|
||||||
|
const saveSubmitError = useCallback(() => {
|
||||||
|
// deep search message
|
||||||
|
const deepSearch = (obj: any): string => {
|
||||||
|
if (!obj) return '提交表单错误';
|
||||||
|
if (!!obj.message) {
|
||||||
|
return obj.message;
|
||||||
|
}
|
||||||
|
return deepSearch(Object.values(obj)[0]);
|
||||||
|
};
|
||||||
|
toast({
|
||||||
|
title: deepSearch(errors),
|
||||||
|
status: 'error',
|
||||||
|
duration: 4000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
}, [errors, toast]);
|
||||||
|
|
||||||
|
const saveUpdateModel = useCallback(
|
||||||
|
() => handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
||||||
|
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSelectFile = useCallback(
|
||||||
|
async (e: File[]) => {
|
||||||
|
const file = e[0];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const src = await compressImg({
|
||||||
|
file,
|
||||||
|
maxW: 100,
|
||||||
|
maxH: 100
|
||||||
|
});
|
||||||
|
setValue('avatar', src);
|
||||||
|
setRefresh((state) => !state);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: getErrText(err, '头像选择异常'),
|
||||||
|
status: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue, toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent maxW={'min(90vw,470px)'}>
|
||||||
|
<ModalHeader>应用信息设置</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Box>头像 & 名称</Box>
|
||||||
|
<Flex mt={2} alignItems={'center'}>
|
||||||
|
<Avatar
|
||||||
|
src={getValues('avatar')}
|
||||||
|
w={['26px', '34px']}
|
||||||
|
h={['26px', '34px']}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
mr={4}
|
||||||
|
title={'点击切换头像'}
|
||||||
|
onClick={() => onOpenSelectFile()}
|
||||||
|
/>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
bg={'myWhite.600'}
|
||||||
|
placeholder={'给应用设置一个名称'}
|
||||||
|
{...register('name', {
|
||||||
|
required: '展示名称不能为空'
|
||||||
|
})}
|
||||||
|
></Input>
|
||||||
|
</FormControl>
|
||||||
|
</Flex>
|
||||||
|
<Box mt={6} mb={1}>
|
||||||
|
应用介绍
|
||||||
|
</Box>
|
||||||
|
<Box color={'myGray.500'} mb={2} fontSize={'sm'}>
|
||||||
|
该介绍主要用于记忆和在应用市场展示
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={4}
|
||||||
|
maxLength={500}
|
||||||
|
placeholder={'给你的 AI 应用一个介绍'}
|
||||||
|
bg={'myWhite.600'}
|
||||||
|
{...register('intro')}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
isLoading={btnLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await saveUpdateModel();
|
||||||
|
onSuccess();
|
||||||
|
onClose();
|
||||||
|
toast({
|
||||||
|
title: '更新成功',
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<File onSelect={onSelectFile} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoModal;
|
||||||
@@ -1,97 +1,34 @@
|
|||||||
import React, { useCallback, useState, useMemo } from 'react';
|
import React, { useCallback, useState, useMemo } from 'react';
|
||||||
import { Box, Flex, Button, FormControl, Input, Textarea, Divider } from '@chakra-ui/react';
|
import { Box, Flex, Button, Grid, useTheme, BoxProps, IconButton } from '@chakra-ui/react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import { delModelById, putAppById } from '@/api/app';
|
import { delModelById } from '@/api/app';
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
|
||||||
import { compressImg } from '@/utils/file';
|
|
||||||
import { getErrText } from '@/utils/tools';
|
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import type { AppSchema } from '@/types/mongoSchema';
|
import { AppSchema } from '@/types/mongoSchema';
|
||||||
|
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
|
const InfoModal = dynamic(() => import('./InfoModal'));
|
||||||
|
const TokenUsage = dynamic(() => import('./Charts/TokenUsage'));
|
||||||
|
const AppEdit = dynamic(() => import('./edit'));
|
||||||
|
import styles from '../../list/index.module.scss';
|
||||||
|
|
||||||
const Settings = ({ appId }: { appId: string }) => {
|
const Settings = ({ appId }: { appId: string }) => {
|
||||||
|
const theme = useTheme();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { Loading, setIsLoading } = useLoading();
|
const { Loading, setIsLoading } = useLoading();
|
||||||
const { userInfo, appDetail, myApps, loadAppDetail, setLastModelId } = useUserStore();
|
const { appDetail, loadAppDetail } = useUserStore();
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
|
||||||
fileType: '.jpg,.png',
|
|
||||||
multiple: false
|
|
||||||
});
|
|
||||||
const { openConfirm, ConfirmChild } = useConfirm({
|
const { openConfirm, ConfirmChild } = useConfirm({
|
||||||
content: '确认删除该应用?'
|
content: '确认删除该应用?'
|
||||||
});
|
});
|
||||||
|
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||||
const [btnLoading, setBtnLoading] = useState(false);
|
const [fullScreen, setFullScreen] = useState(false);
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
formState: { errors },
|
|
||||||
reset,
|
|
||||||
handleSubmit
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: appDetail
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOwner = useMemo(
|
|
||||||
() => appDetail.userId === userInfo?._id,
|
|
||||||
[appDetail.userId, userInfo?._id]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 提交保存模型修改
|
|
||||||
const saveSubmitSuccess = useCallback(
|
|
||||||
async (data: AppSchema) => {
|
|
||||||
setBtnLoading(true);
|
|
||||||
try {
|
|
||||||
await putAppById(data._id, {
|
|
||||||
name: data.name,
|
|
||||||
avatar: data.avatar,
|
|
||||||
intro: data.intro,
|
|
||||||
chat: data.chat,
|
|
||||||
share: data.share
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '更新失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setBtnLoading(false);
|
|
||||||
},
|
|
||||||
[toast]
|
|
||||||
);
|
|
||||||
// 提交保存表单失败
|
|
||||||
const saveSubmitError = useCallback(() => {
|
|
||||||
// deep search message
|
|
||||||
const deepSearch = (obj: any): string => {
|
|
||||||
if (!obj) return '提交表单错误';
|
|
||||||
if (!!obj.message) {
|
|
||||||
return obj.message;
|
|
||||||
}
|
|
||||||
return deepSearch(Object.values(obj)[0]);
|
|
||||||
};
|
|
||||||
toast({
|
|
||||||
title: deepSearch(errors),
|
|
||||||
status: 'error',
|
|
||||||
duration: 4000,
|
|
||||||
isClosable: true
|
|
||||||
});
|
|
||||||
}, [errors, toast]);
|
|
||||||
|
|
||||||
const saveUpdateModel = useCallback(
|
|
||||||
() => handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
|
||||||
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
|
||||||
);
|
|
||||||
|
|
||||||
/* 点击删除 */
|
/* 点击删除 */
|
||||||
const handleDelModel = useCallback(async () => {
|
const handleDelModel = useCallback(async () => {
|
||||||
@@ -113,156 +50,126 @@ const Settings = ({ appId }: { appId: string }) => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [appDetail, setIsLoading, toast, router]);
|
}, [appDetail, setIsLoading, toast, router]);
|
||||||
|
|
||||||
const onSelectFile = useCallback(
|
// load app data
|
||||||
async (e: File[]) => {
|
const { isLoading, refetch } = useQuery([appId], () => loadAppDetail(appId, true), {
|
||||||
const file = e[0];
|
|
||||||
if (!file) return;
|
|
||||||
try {
|
|
||||||
const src = await compressImg({
|
|
||||||
file,
|
|
||||||
maxW: 100,
|
|
||||||
maxH: 100
|
|
||||||
});
|
|
||||||
setValue('avatar', src);
|
|
||||||
setRefresh((state) => !state);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: getErrText(err, '头像选择异常'),
|
|
||||||
status: 'warning'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setValue, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
// load model data
|
|
||||||
const { isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
|
|
||||||
onSuccess(res) {
|
|
||||||
res && reset(res);
|
|
||||||
appId && setLastModelId(appId);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
},
|
|
||||||
onError(err: any) {
|
onError(err: any) {
|
||||||
toast({
|
toast({
|
||||||
title: err?.message || '获取应用异常',
|
title: err?.message || '获取应用异常',
|
||||||
status: 'error'
|
status: 'error'
|
||||||
});
|
});
|
||||||
setLastModelId('');
|
router.replace('/app/list');
|
||||||
router.replace('/model');
|
},
|
||||||
|
onSettled() {
|
||||||
|
router.prefetch(`/chat?appId=${appId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Flex h={'100%'} flexDirection={'column'} position={'relative'}>
|
||||||
|
<Box w={'100%'} pt={[0, 7]} px={[2, 5, 8]}>
|
||||||
|
<Grid gridTemplateColumns={['1fr', 'repeat(2,1fr)']} gridGap={[2, 4, 6]}>
|
||||||
|
<Box>
|
||||||
|
<Box mb={2} fontSize={['md', 'xl']}>
|
||||||
|
概览
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
pt={[0, 5]}
|
border={theme.borders.sm}
|
||||||
pb={3}
|
borderRadius={'lg'}
|
||||||
px={[5, '25px', '50px']}
|
px={5}
|
||||||
fontSize={['sm', 'lg']}
|
py={4}
|
||||||
|
bg={'rgba(235,245,255,0.4)'}
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
maxW={['auto', '800px']}
|
|
||||||
>
|
>
|
||||||
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
|
<Flex alignItems={'center'} py={2}>
|
||||||
基本信息
|
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
|
||||||
|
{appDetail.name}
|
||||||
</Box>
|
</Box>
|
||||||
<Flex mt={5} alignItems={'center'}>
|
<IconButton
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
className="delete"
|
||||||
头像
|
position={'absolute'}
|
||||||
</Box>
|
top={4}
|
||||||
<Avatar
|
right={4}
|
||||||
src={getValues('avatar')}
|
size={'sm'}
|
||||||
w={['32px', '40px']}
|
icon={<MyIcon name={'delete'} w={'14px'} />}
|
||||||
h={['32px', '40px']}
|
variant={'base'}
|
||||||
cursor={isOwner ? 'pointer' : 'default'}
|
borderRadius={'md'}
|
||||||
title={'点击切换头像'}
|
aria-label={'delete'}
|
||||||
onClick={() => isOwner && onOpenSelectFile()}
|
_hover={{
|
||||||
|
bg: 'myGray.100',
|
||||||
|
color: 'red.600'
|
||||||
|
}}
|
||||||
|
onClick={openConfirm(handleDelModel)}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<FormControl mt={5}>
|
<Box className={styles.intro} py={3} wordBreak={'break-all'} color={'myGray.600'}>
|
||||||
<Flex alignItems={'center'}>
|
{appDetail.intro || '快来给应用一个介绍~'}
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
名称
|
|
||||||
</Box>
|
</Box>
|
||||||
<Input
|
<Flex>
|
||||||
isDisabled={!isOwner}
|
|
||||||
{...register('name', {
|
|
||||||
required: '展示名称不能为空'
|
|
||||||
})}
|
|
||||||
></Input>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
<Flex mt={5} alignItems={'flex-start'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
介绍
|
|
||||||
</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={4}
|
|
||||||
maxLength={500}
|
|
||||||
placeholder={'给你的 AI 应用一个介绍'}
|
|
||||||
{...register('intro')}
|
|
||||||
></Textarea>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Divider mt={5} />
|
|
||||||
|
|
||||||
<Flex mt={5} alignItems={'center'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}></Box>
|
|
||||||
<Button
|
<Button
|
||||||
mr={3}
|
|
||||||
w={'120px'}
|
|
||||||
size={['sm', 'md']}
|
|
||||||
isLoading={btnLoading}
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await saveUpdateModel();
|
|
||||||
toast({
|
|
||||||
title: '更新成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
error;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOwner ? '保存' : '仅读,无法修改'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
mr={3}
|
|
||||||
w={'100px'}
|
|
||||||
size={['sm', 'md']}
|
size={['sm', 'md']}
|
||||||
variant={'base'}
|
variant={'base'}
|
||||||
color={'myBlue.600'}
|
leftIcon={<MyIcon name={'chatLight'} w={'16px'} />}
|
||||||
borderColor={'myBlue.600'}
|
onClick={() => router.push(`/chat?appId=${appId}`)}
|
||||||
isLoading={btnLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
router.prefetch('/chat');
|
|
||||||
await saveUpdateModel();
|
|
||||||
} catch (error) {}
|
|
||||||
router.push(`/chat?appId=${appId}`);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
对话
|
对话
|
||||||
</Button>
|
</Button>
|
||||||
{isOwner && (
|
|
||||||
<Button
|
<Button
|
||||||
colorScheme={'gray'}
|
mx={3}
|
||||||
variant={'base'}
|
|
||||||
size={['sm', 'md']}
|
size={['sm', 'md']}
|
||||||
isLoading={btnLoading}
|
variant={'base'}
|
||||||
_hover={{ color: 'red.600' }}
|
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
|
||||||
onClick={openConfirm(handleDelModel)}
|
onClick={() => {
|
||||||
|
router.replace({
|
||||||
|
query: {
|
||||||
|
appId,
|
||||||
|
currentTab: 'share'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
删除
|
分享
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={['sm', 'md']}
|
||||||
|
variant={'base'}
|
||||||
|
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
|
||||||
|
onClick={() => setSettingAppInfo(appDetail)}
|
||||||
|
>
|
||||||
|
设置
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Box mb={2} fontSize={['md', 'xl']}>
|
||||||
|
近 7 日 Tokens 消耗
|
||||||
|
</Box>
|
||||||
|
<Box h={'150px'}>
|
||||||
|
<TokenUsage appId={appId} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<Box flex={'1 0 0'} position={'relative'}>
|
||||||
|
<AppEdit
|
||||||
|
app={appDetail}
|
||||||
|
onFullScreen={(val) => setFullScreen(val)}
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{settingAppInfo && (
|
||||||
|
<InfoModal
|
||||||
|
defaultApp={settingAppInfo}
|
||||||
|
onClose={() => setSettingAppInfo(undefined)}
|
||||||
|
onSuccess={refetch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<File onSelect={onSelectFile} />
|
|
||||||
<ConfirmChild />
|
<ConfirmChild />
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading loading={isLoading} fixed={false} />
|
||||||
</Box>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position={'relative'} pt={[0, 5]} px={5} minH={'50vh'}>
|
<Box position={'relative'} pt={[0, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
<Box fontWeight={'bold'}>
|
<Box fontWeight={'bold'}>
|
||||||
免登录聊天窗口
|
免登录聊天窗口
|
||||||
@@ -150,6 +150,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Flex>
|
<Flex>
|
||||||
|
<MyTooltip label={'复制分享地址'}>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
mr={3}
|
mr={3}
|
||||||
name="copy"
|
name="copy"
|
||||||
@@ -161,6 +162,8 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
copyData(url, '已复制分享地址');
|
copyData(url, '已复制分享地址');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<MyTooltip label={'删除链接'}>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name="delete"
|
name="delete"
|
||||||
w={'14px'}
|
w={'14px'}
|
||||||
@@ -177,6 +180,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</MyTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const ModuleStoreList = ({
|
|||||||
<Box
|
<Box
|
||||||
zIndex={2}
|
zIndex={2}
|
||||||
display={isOpen ? 'block' : 'none'}
|
display={isOpen ? 'block' : 'none'}
|
||||||
position={'fixed'}
|
position={'absolute'}
|
||||||
top={0}
|
top={0}
|
||||||
left={0}
|
left={0}
|
||||||
bottom={0}
|
bottom={0}
|
||||||
@@ -41,20 +41,21 @@ const ModuleStoreList = ({
|
|||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
top={'65px'}
|
top={'65px'}
|
||||||
left={0}
|
left={0}
|
||||||
h={isOpen ? '90%' : '0'}
|
pb={4}
|
||||||
|
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||||
w={isOpen ? '360px' : '0'}
|
w={isOpen ? '360px' : '0'}
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||||
borderRadius={'20px'}
|
borderRadius={'20px'}
|
||||||
overflow={'hidden'}
|
overflow={'hidden'}
|
||||||
transition={'.2s ease'}
|
transition={'.2s ease'}
|
||||||
px={'15px'}
|
|
||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
>
|
>
|
||||||
<Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}>
|
<Box w={'330px'} py={4} px={5} fontSize={'xl'} fontWeight={'bold'}>
|
||||||
系统模块
|
系统模块
|
||||||
</Box>
|
</Box>
|
||||||
<Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}>
|
<Box flex={'1 0 0'} overflow={'overlay'}>
|
||||||
|
<Box w={'330px'} mx={'auto'}>
|
||||||
{ModuleTemplates.map((item) =>
|
{ModuleTemplates.map((item) =>
|
||||||
item.list.map((item) => (
|
item.list.map((item) => (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -84,6 +85,7 @@ const ModuleStoreList = ({
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ const nodeTypes = {
|
|||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
buttonedge: ButtonEdge
|
buttonedge: ButtonEdge
|
||||||
};
|
};
|
||||||
type Props = { app: AppSchema; onBack: () => void };
|
type Props = { app: AppSchema; fullScreen: boolean; onFullScreen: (val: boolean) => void };
|
||||||
|
|
||||||
const AppEdit = ({ app, onBack }: Props) => {
|
const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||||
|
const theme = useTheme();
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
||||||
const theme = useTheme();
|
|
||||||
const { x, y, zoom } = useViewport();
|
const { x, y, zoom } = useViewport();
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
@@ -91,6 +91,14 @@ const AppEdit = ({ app, onBack }: Props) => {
|
|||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
|
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
|
||||||
|
|
||||||
|
const onFixView = useCallback(() => {
|
||||||
|
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
btn && btn.click();
|
||||||
|
}, 100);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onChangeNode = useCallback(
|
const onChangeNode = useCallback(
|
||||||
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
|
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
|
||||||
setNodes((nodes) =>
|
setNodes((nodes) =>
|
||||||
@@ -258,23 +266,57 @@ const AppEdit = ({ app, onBack }: Props) => {
|
|||||||
}, [app, initData]);
|
}, [app, initData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
<>
|
||||||
{/* header */}
|
{/* header */}
|
||||||
<Flex py={3} px={5} borderBottom={theme.borders.base} alignItems={'center'}>
|
<Flex
|
||||||
|
py={3}
|
||||||
|
px={[2, 5, 8]}
|
||||||
|
borderBottom={theme.borders.base}
|
||||||
|
alignItems={'center'}
|
||||||
|
userSelect={'none'}
|
||||||
|
>
|
||||||
|
{fullScreen ? (
|
||||||
|
<>
|
||||||
|
<MyTooltip label={'取消全屏'} offset={[10, 10]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
icon={<MyIcon name={'back'} w={'14px'} />}
|
|
||||||
w={'28px'}
|
w={'28px'}
|
||||||
h={'28px'}
|
h={'28px'}
|
||||||
|
icon={<MyIcon name={'fullScreenLight'} w={'16px'} />}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
borderColor={'myGray.300'}
|
borderColor={'myGray.300'}
|
||||||
variant={'base'}
|
variant={'base'}
|
||||||
aria-label={''}
|
aria-label={''}
|
||||||
onClick={onBack}
|
onClick={() => {
|
||||||
|
onFullScreen(false);
|
||||||
|
onFixView();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Box ml={5} fontSize={'xl'} flex={1}>
|
</MyTooltip>
|
||||||
|
<Box ml={5} fontSize={['lg', '2xl']} flex={1}>
|
||||||
{app.name}
|
{app.name}
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Box fontSize={['lg', '2xl']} flex={1}>
|
||||||
|
应用编排
|
||||||
|
</Box>
|
||||||
|
<MyTooltip label={'全屏'}>
|
||||||
|
<IconButton
|
||||||
|
mr={6}
|
||||||
|
icon={<MyIcon name={'fullScreenLight'} w={'16px'} />}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
variant={'base'}
|
||||||
|
aria-label={'fullScreenLight'}
|
||||||
|
onClick={() => {
|
||||||
|
onFullScreen(true);
|
||||||
|
onFixView();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{testModules ? (
|
{testModules ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
mr={6}
|
mr={6}
|
||||||
@@ -361,11 +403,7 @@ const AppEdit = ({ app, onBack }: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Background />
|
<Background />
|
||||||
<Controls
|
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||||
position={'bottom-center'}
|
|
||||||
style={{ display: 'flex' }}
|
|
||||||
showInteractive={false}
|
|
||||||
/>
|
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
||||||
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
|
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
|
||||||
@@ -376,14 +414,26 @@ const AppEdit = ({ app, onBack }: Props) => {
|
|||||||
onClose={() => setTestModules(undefined)}
|
onClose={() => setTestModules(undefined)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Flow = (data: Props) => (
|
const Flow = (data: Props) => (
|
||||||
|
<Box
|
||||||
|
h={'100%'}
|
||||||
|
position={data.fullScreen ? 'fixed' : 'relative'}
|
||||||
|
zIndex={999}
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<AppEdit {...data} />
|
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
||||||
|
{!!data.app._id && <AppEdit {...data} />}
|
||||||
|
</Flex>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default React.memo(Flow);
|
export default React.memo(Flow);
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import Avatar from '@/components/Avatar';
|
|||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import PageContainer from '@/components/PageContainer';
|
import PageContainer from '@/components/PageContainer';
|
||||||
|
|
||||||
const EditApp = dynamic(() => import('./components/edit'), {
|
|
||||||
ssr: false
|
|
||||||
});
|
|
||||||
const Share = dynamic(() => import('./components/Share'), {
|
const Share = dynamic(() => import('./components/Share'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
@@ -24,7 +21,6 @@ const API = dynamic(() => import('./components/API'), {
|
|||||||
|
|
||||||
enum TabEnum {
|
enum TabEnum {
|
||||||
'settings' = 'settings',
|
'settings' = 'settings',
|
||||||
'edit' = 'edit',
|
|
||||||
'share' = 'share',
|
'share' = 'share',
|
||||||
'API' = 'API'
|
'API' = 'API'
|
||||||
}
|
}
|
||||||
@@ -33,17 +29,11 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { appId } = router.query as { appId: string };
|
const { appId } = router.query as { appId: string };
|
||||||
const { appDetail = defaultApp, loadAppDetail, userInfo } = useUserStore();
|
const { appDetail = defaultApp } = useUserStore();
|
||||||
|
|
||||||
const isOwner = useMemo(
|
|
||||||
() => appDetail.userId === userInfo?._id,
|
|
||||||
[appDetail.userId, userInfo?._id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setCurrentTab = useCallback(
|
const setCurrentTab = useCallback(
|
||||||
(tab: `${TabEnum}`) => {
|
(tab: `${TabEnum}`) => {
|
||||||
router.replace({
|
router.replace({
|
||||||
pathname: router.pathname,
|
|
||||||
query: {
|
query: {
|
||||||
appId,
|
appId,
|
||||||
currentTab: tab
|
currentTab: tab
|
||||||
@@ -56,28 +46,23 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
const tabList = useMemo(
|
const tabList = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ label: '概览', id: TabEnum.settings, icon: 'overviewLight' },
|
{ label: '概览', id: TabEnum.settings, icon: 'overviewLight' },
|
||||||
...(isOwner ? [{ label: '高级设置', id: TabEnum.edit, icon: 'settingLight' }] : []),
|
|
||||||
{ label: '链接分享', id: TabEnum.share, icon: 'shareLight' },
|
{ label: '链接分享', id: TabEnum.share, icon: 'shareLight' },
|
||||||
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
|
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
|
||||||
{ label: '立即对话', id: 'startChat', icon: 'chatLight' }
|
{ label: '立即对话', id: 'startChat', icon: 'chatLight' }
|
||||||
],
|
],
|
||||||
[isOwner]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
window.onbeforeunload = (e) => {
|
// window.onbeforeunload = (e) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
e.returnValue = '内容已修改,确认离开页面吗?';
|
// e.returnValue = '内容已修改,确认离开页面吗?';
|
||||||
};
|
// };
|
||||||
|
|
||||||
return () => {
|
// return () => {
|
||||||
window.onbeforeunload = null;
|
// window.onbeforeunload = null;
|
||||||
};
|
// };
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadAppDetail(appId);
|
|
||||||
}, [appId, loadAppDetail]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
@@ -156,11 +141,6 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
{currentTab === TabEnum.settings && <Settings appId={appId} />}
|
{currentTab === TabEnum.settings && <Settings appId={appId} />}
|
||||||
{currentTab === TabEnum.edit && (
|
|
||||||
<Box position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
|
|
||||||
<EditApp app={appDetail} onBack={() => setCurrentTab(TabEnum.settings)} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{currentTab === TabEnum.API && <API appId={appId} />}
|
{currentTab === TabEnum.API && <API appId={appId} />}
|
||||||
{currentTab === TabEnum.share && <Share appId={appId} />}
|
{currentTab === TabEnum.share && <Share appId={appId} />}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import {
|
|||||||
MenuList,
|
MenuList,
|
||||||
MenuItem
|
MenuItem
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import MyIcon from '@/components/Icon';
|
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
type HistoryItemType = {
|
type HistoryItemType = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,6 +22,7 @@ type HistoryItemType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ChatHistorySlider = ({
|
const ChatHistorySlider = ({
|
||||||
|
appId,
|
||||||
appName,
|
appName,
|
||||||
appAvatar,
|
appAvatar,
|
||||||
history,
|
history,
|
||||||
@@ -29,6 +32,7 @@ const ChatHistorySlider = ({
|
|||||||
onSetHistoryTop,
|
onSetHistoryTop,
|
||||||
onCloseSlider
|
onCloseSlider
|
||||||
}: {
|
}: {
|
||||||
|
appId?: string;
|
||||||
appName: string;
|
appName: string;
|
||||||
appAvatar: string;
|
appAvatar: string;
|
||||||
history: HistoryItemType[];
|
history: HistoryItemType[];
|
||||||
@@ -39,6 +43,7 @@ const ChatHistorySlider = ({
|
|||||||
onCloseSlider: () => void;
|
onCloseSlider: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const router = useRouter();
|
||||||
const { isPc } = useGlobalStore();
|
const { isPc } = useGlobalStore();
|
||||||
|
|
||||||
const concatHistory = useMemo<HistoryItemType[]>(
|
const concatHistory = useMemo<HistoryItemType[]>(
|
||||||
@@ -57,12 +62,27 @@ const ChatHistorySlider = ({
|
|||||||
whiteSpace={'nowrap'}
|
whiteSpace={'nowrap'}
|
||||||
>
|
>
|
||||||
{isPc && (
|
{isPc && (
|
||||||
<Flex pt={5} pb={2} px={[2, 5]} alignItems={'center'}>
|
<MyTooltip label={appId ? '应用详情' : ''} offset={[0, 0]}>
|
||||||
|
<Flex
|
||||||
|
pt={5}
|
||||||
|
pb={2}
|
||||||
|
px={[2, 5]}
|
||||||
|
alignItems={'center'}
|
||||||
|
cursor={appId ? 'pointer' : 'default'}
|
||||||
|
onClick={() =>
|
||||||
|
appId &&
|
||||||
|
router.push({
|
||||||
|
pathname: '/app/detail',
|
||||||
|
query: { appId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Avatar src={appAvatar} />
|
<Avatar src={appAvatar} />
|
||||||
<Box ml={2} fontWeight={'bold'} className={'textEllipsis'}>
|
<Box ml={2} fontWeight={'bold'} className={'textEllipsis'}>
|
||||||
{appName}
|
{appName}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
{/* 新对话 */}
|
{/* 新对话 */}
|
||||||
<Box w={'100%'} px={[2, 5]} h={'36px'} my={5}>
|
<Box w={'100%'} px={[2, 5]} h={'36px'} my={5}>
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import { getErrText } from '@/utils/tools';
|
|||||||
|
|
||||||
const QuoteModal = ({
|
const QuoteModal = ({
|
||||||
historyId,
|
historyId,
|
||||||
chatId,
|
contentId,
|
||||||
onClose
|
onClose
|
||||||
}: {
|
}: {
|
||||||
historyId: string;
|
historyId: string;
|
||||||
chatId: string;
|
contentId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -41,7 +41,7 @@ const QuoteModal = ({
|
|||||||
data: quote = [],
|
data: quote = [],
|
||||||
refetch,
|
refetch,
|
||||||
isLoading
|
isLoading
|
||||||
} = useQuery(['getHistoryQuote'], () => getHistoryQuote({ historyId, chatId }));
|
} = useQuery(['getHistoryQuote'], () => getHistoryQuote({ historyId, contentId }));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update kbData, update mongo status and reload quotes
|
* update kbData, update mongo status and reload quotes
|
||||||
@@ -51,7 +51,7 @@ const QuoteModal = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await updateHistoryQuote({
|
await updateHistoryQuote({
|
||||||
chatId,
|
contentId,
|
||||||
historyId,
|
historyId,
|
||||||
quoteId,
|
quoteId,
|
||||||
sourceText
|
sourceText
|
||||||
@@ -66,7 +66,7 @@ const QuoteModal = ({
|
|||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
[chatId, historyId, refetch, setIsLoading, toast]
|
[contentId, historyId, refetch, setIsLoading, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
|
|||||||
px={3}
|
px={3}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
_hover={{ bg: 'myGray.200' }}
|
_hover={{ bg: 'myGray.200' }}
|
||||||
onClick={() => router.replace('/app/list')}
|
onClick={() => router.back()}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
mr={3}
|
mr={3}
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ const Chat = () => {
|
|||||||
const {
|
const {
|
||||||
lastChatAppId,
|
lastChatAppId,
|
||||||
setLastChatAppId,
|
setLastChatAppId,
|
||||||
lastChatId,
|
lastHistoryId,
|
||||||
setLastChatId,
|
setLastHistoryId,
|
||||||
history,
|
history,
|
||||||
loadHistory,
|
loadHistory,
|
||||||
updateHistory,
|
updateHistory,
|
||||||
@@ -192,13 +192,13 @@ const Chat = () => {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// reset all chat tore
|
// reset all chat tore
|
||||||
setLastChatAppId('');
|
setLastChatAppId('');
|
||||||
setLastChatId('');
|
setLastHistoryId('');
|
||||||
router.replace('/chat');
|
router.replace('/chat');
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId]
|
[setIsLoading, setChatData, router, setLastChatAppId, setLastHistoryId]
|
||||||
);
|
);
|
||||||
// 初始化聊天框
|
// 初始化聊天框
|
||||||
useQuery(['init', appId, historyId], () => {
|
useQuery(['init', appId, historyId], () => {
|
||||||
@@ -207,7 +207,7 @@ const Chat = () => {
|
|||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
appId: lastChatAppId,
|
appId: lastChatAppId,
|
||||||
historyId: lastChatId
|
historyId: lastHistoryId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -215,7 +215,7 @@ const Chat = () => {
|
|||||||
|
|
||||||
// store id
|
// store id
|
||||||
appId && setLastChatAppId(appId);
|
appId && setLastChatAppId(appId);
|
||||||
setLastChatId(historyId);
|
setLastHistoryId(historyId);
|
||||||
|
|
||||||
if (forbidRefresh.current) {
|
if (forbidRefresh.current) {
|
||||||
forbidRefresh.current = false;
|
forbidRefresh.current = false;
|
||||||
@@ -254,6 +254,7 @@ const Chat = () => {
|
|||||||
);
|
);
|
||||||
})(
|
})(
|
||||||
<ChatHistorySlider
|
<ChatHistorySlider
|
||||||
|
appId={appId}
|
||||||
appName={chatData.app.name}
|
appName={chatData.app.name}
|
||||||
appAvatar={chatData.app.avatar}
|
appAvatar={chatData.app.avatar}
|
||||||
activeHistoryId={historyId}
|
activeHistoryId={historyId}
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ const Login = () => {
|
|||||||
const { lastRoute = '' } = router.query as { lastRoute: string };
|
const { lastRoute = '' } = router.query as { lastRoute: string };
|
||||||
const { isPc } = useGlobalStore();
|
const { isPc } = useGlobalStore();
|
||||||
const [pageType, setPageType] = useState<`${PageTypeEnum}`>(PageTypeEnum.login);
|
const [pageType, setPageType] = useState<`${PageTypeEnum}`>(PageTypeEnum.login);
|
||||||
const { setUserInfo, setLastModelId, loadKbList, setLastKbId } = useUserStore();
|
const { setUserInfo, loadKbList, setLastKbId } = useUserStore();
|
||||||
const { setLastChatId, setLastChatAppId } = useChatStore();
|
const { setLastHistoryId, setLastChatAppId } = useChatStore();
|
||||||
|
|
||||||
const loginSuccess = useCallback(
|
const loginSuccess = useCallback(
|
||||||
(res: ResLogin) => {
|
(res: ResLogin) => {
|
||||||
// init store
|
// init store
|
||||||
setLastChatId('');
|
setLastHistoryId('');
|
||||||
setLastModelId('');
|
|
||||||
setLastChatAppId('');
|
setLastChatAppId('');
|
||||||
setLastKbId('');
|
setLastKbId('');
|
||||||
loadKbList(true);
|
loadKbList(true);
|
||||||
@@ -34,16 +33,7 @@ const Login = () => {
|
|||||||
router.push(lastRoute ? decodeURIComponent(lastRoute) : '/model');
|
router.push(lastRoute ? decodeURIComponent(lastRoute) : '/model');
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
[
|
[lastRoute, loadKbList, router, setLastHistoryId, setLastChatAppId, setLastKbId, setUserInfo]
|
||||||
lastRoute,
|
|
||||||
loadKbList,
|
|
||||||
router,
|
|
||||||
setLastChatId,
|
|
||||||
setLastChatAppId,
|
|
||||||
setLastKbId,
|
|
||||||
setLastModelId,
|
|
||||||
setUserInfo
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
|
function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ const BillTable = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{total > pageSize && (
|
|
||||||
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
|
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
defaultDate={dateRange}
|
defaultDate={dateRange}
|
||||||
@@ -81,7 +80,6 @@ const BillTable = () => {
|
|||||||
<Pagination />
|
<Pagination />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading loading={isLoading} fixed={false} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const pushChatBill = async ({
|
|||||||
isPay,
|
isPay,
|
||||||
chatModel,
|
chatModel,
|
||||||
userId,
|
userId,
|
||||||
chatId,
|
appId,
|
||||||
textLen,
|
textLen,
|
||||||
tokens,
|
tokens,
|
||||||
type
|
type
|
||||||
@@ -20,7 +20,7 @@ export const pushChatBill = async ({
|
|||||||
isPay: boolean;
|
isPay: boolean;
|
||||||
chatModel: ChatModelType;
|
chatModel: ChatModelType;
|
||||||
userId: string;
|
userId: string;
|
||||||
chatId?: '' | string;
|
appId: string;
|
||||||
textLen: number;
|
textLen: number;
|
||||||
tokens: number;
|
tokens: number;
|
||||||
type: BillTypeEnum.chat | BillTypeEnum.openapiChat;
|
type: BillTypeEnum.chat | BillTypeEnum.openapiChat;
|
||||||
@@ -43,7 +43,7 @@ export const pushChatBill = async ({
|
|||||||
userId,
|
userId,
|
||||||
type,
|
type,
|
||||||
modelName: chatModel,
|
modelName: chatModel,
|
||||||
chatId: chatId ? chatId : undefined,
|
appId,
|
||||||
textLen,
|
textLen,
|
||||||
tokenLen: tokens,
|
tokenLen: tokens,
|
||||||
price
|
price
|
||||||
|
|||||||
@@ -105,4 +105,4 @@ try {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const App: Model<AppType> = models['model'] || model('model', AppSchema);
|
export const App: Model<AppType> = models['app'] || model('app', AppSchema);
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ const BillSchema = new Schema({
|
|||||||
},
|
},
|
||||||
modelName: {
|
modelName: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [...Object.keys(ChatModelMap), embeddingModel],
|
enum: [...Object.keys(ChatModelMap), embeddingModel]
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
chatId: {
|
appId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'chat'
|
ref: 'app'
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
type: Date,
|
type: Date,
|
||||||
@@ -44,8 +43,9 @@ const BillSchema = new Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BillSchema.index({ time: -1 });
|
|
||||||
BillSchema.index({ userId: 1 });
|
BillSchema.index({ userId: 1 });
|
||||||
|
// BillSchema.index({ time: -1 });
|
||||||
|
// BillSchema.index({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const ChatSchema = new Schema({
|
|||||||
},
|
},
|
||||||
appId: {
|
appId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'model',
|
ref: 'app',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
updateTime: {
|
updateTime: {
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
import { ChatCompletionType, StreamResponseType } from './index';
|
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
/* 模型对话 */
|
|
||||||
export const claudChat = async ({ apiKey, messages, stream, chatId }: ChatCompletionType) => {
|
|
||||||
// get system prompt
|
|
||||||
const systemPrompt = messages
|
|
||||||
.filter((item) => item.obj === 'System')
|
|
||||||
.map((item) => item.value)
|
|
||||||
.join('\n');
|
|
||||||
const systemPromptText = systemPrompt ? `你本次知识:${systemPrompt}\n下面是我的问题:` : '';
|
|
||||||
|
|
||||||
const prompt = `${systemPromptText}'${messages[messages.length - 1].value}'`;
|
|
||||||
|
|
||||||
const response = await axios.post(
|
|
||||||
process.env.CLAUDE_BASE_URL || '',
|
|
||||||
{
|
|
||||||
prompt,
|
|
||||||
stream,
|
|
||||||
conversationId: chatId
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: apiKey
|
|
||||||
},
|
|
||||||
timeout: stream ? 60000 : 480000,
|
|
||||||
responseType: stream ? 'stream' : 'json'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const responseText = stream ? '' : response.data?.text || '';
|
|
||||||
|
|
||||||
return {
|
|
||||||
streamResponse: response,
|
|
||||||
responseMessages: messages.concat({
|
|
||||||
obj: ChatRoleEnum.AI,
|
|
||||||
value: responseText
|
|
||||||
}),
|
|
||||||
responseText,
|
|
||||||
totalTokens: 0
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* openai stream response */
|
|
||||||
export const claudStreamResponse = async ({ res, chatResponse, prompts }: StreamResponseType) => {
|
|
||||||
try {
|
|
||||||
let responseContent = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
for await (const chunk of chatResponse.data as any) {
|
|
||||||
if (res.closed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const content = decoder.decode(chunk);
|
|
||||||
responseContent += content;
|
|
||||||
content && res.write(content);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('pipe error', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const finishMessages = prompts.concat({
|
|
||||||
obj: ChatRoleEnum.AI,
|
|
||||||
value: responseContent
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
responseContent,
|
|
||||||
totalTokens: 0,
|
|
||||||
finishMessages
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -14,7 +14,7 @@ export type ChatCompletionType = {
|
|||||||
temperature: number;
|
temperature: number;
|
||||||
maxToken?: number;
|
maxToken?: number;
|
||||||
messages: ChatItemType[];
|
messages: ChatItemType[];
|
||||||
chatId?: string;
|
historyId?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
export type ChatCompletionResponseType = {
|
export type ChatCompletionResponseType = {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ type State = {
|
|||||||
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
|
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
|
||||||
lastChatAppId: string;
|
lastChatAppId: string;
|
||||||
setLastChatAppId: (id: string) => void;
|
setLastChatAppId: (id: string) => void;
|
||||||
lastChatId: string;
|
lastHistoryId: string;
|
||||||
setLastChatId: (id: string) => void;
|
setLastHistoryId: (id: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultChatData: InitChatResponse = {
|
const defaultChatData: InitChatResponse = {
|
||||||
@@ -43,10 +43,10 @@ export const useChatStore = create<State>()(
|
|||||||
state.lastChatAppId = id;
|
state.lastChatAppId = id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
lastChatId: '',
|
lastHistoryId: '',
|
||||||
setLastChatId(id: string) {
|
setLastHistoryId(id: string) {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.lastChatId = id;
|
state.lastHistoryId = id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
history: [],
|
history: [],
|
||||||
@@ -95,7 +95,7 @@ export const useChatStore = create<State>()(
|
|||||||
name: 'chatStore',
|
name: 'chatStore',
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
lastChatAppId: state.lastChatAppId,
|
lastChatAppId: state.lastChatAppId,
|
||||||
lastChatId: state.lastChatId
|
lastHistoryId: state.lastHistoryId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ type State = {
|
|||||||
initUserInfo: () => Promise<UserType>;
|
initUserInfo: () => Promise<UserType>;
|
||||||
setUserInfo: (user: UserType | null) => void;
|
setUserInfo: (user: UserType | null) => void;
|
||||||
updateUserInfo: (user: UserUpdateParams) => void;
|
updateUserInfo: (user: UserUpdateParams) => void;
|
||||||
// model
|
|
||||||
lastModelId: string;
|
|
||||||
setLastModelId: (id: string) => void;
|
|
||||||
myApps: AppListItemType[];
|
myApps: AppListItemType[];
|
||||||
myCollectionApps: AppListItemType[];
|
myCollectionApps: AppListItemType[];
|
||||||
loadMyModels: () => Promise<null>;
|
loadMyModels: () => Promise<null>;
|
||||||
@@ -63,12 +60,6 @@ export const useUserStore = create<State>()(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
lastModelId: '',
|
|
||||||
setLastModelId(id: string) {
|
|
||||||
set((state) => {
|
|
||||||
state.lastModelId = id;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
myApps: [],
|
myApps: [],
|
||||||
myCollectionApps: [],
|
myCollectionApps: [],
|
||||||
async loadMyModels() {
|
async loadMyModels() {
|
||||||
@@ -119,7 +110,6 @@ export const useUserStore = create<State>()(
|
|||||||
{
|
{
|
||||||
name: 'userStore',
|
name: 'userStore',
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
lastModelId: state.lastModelId,
|
|
||||||
lastKbId: state.lastKbId
|
lastKbId: state.lastKbId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
4
client/src/types/mongoSchema.d.ts
vendored
@@ -91,8 +91,8 @@ export interface BillSchema {
|
|||||||
_id: string;
|
_id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
type: `${BillTypeEnum}`;
|
type: `${BillTypeEnum}`;
|
||||||
modelName: ChatModelType | EmbeddingModelType;
|
modelName?: ChatModelType | EmbeddingModelType;
|
||||||
chatId: string;
|
appId?: string;
|
||||||
time: Date;
|
time: Date;
|
||||||
textLen: number;
|
textLen: number;
|
||||||
tokenLen: number;
|
tokenLen: number;
|
||||||
|
|||||||