mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-18 09:24:03 +00:00
fix: bill, app detail
This commit is contained in:
@@ -27,19 +27,20 @@ import { useMarkdown } from '@/hooks/useMarkdown';
|
|||||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||||
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
|
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import MySelect from '@/components/Select';
|
|
||||||
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
||||||
import MyTooltip from '../MyTooltip';
|
|
||||||
import { fileDownload } from '@/utils/file';
|
import { fileDownload } from '@/utils/file';
|
||||||
import { htmlTemplate } from '@/constants/common';
|
import { htmlTemplate } from '@/constants/common';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useGlobalStore } from '@/store/global';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
|
||||||
import { QuoteItemType } from '@/pages/api/app/modules/kb/search';
|
import { QuoteItemType } from '@/pages/api/app/modules/kb/search';
|
||||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||||
|
import MyTooltip from '../MyTooltip';
|
||||||
|
import MySelect from '@/components/Select';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const textareaMinH = '22px';
|
const textareaMinH = '22px';
|
||||||
export type StartChatFnProps = {
|
export type StartChatFnProps = {
|
||||||
@@ -139,6 +140,7 @@ const ChatBox = (
|
|||||||
const { copyData } = useCopyData();
|
const { copyData } = useCopyData();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
const { isPc } = useGlobalStore();
|
||||||
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
||||||
const controller = useRef(new AbortController());
|
const controller = useRef(new AbortController());
|
||||||
|
|
||||||
@@ -312,7 +314,7 @@ const ChatBox = (
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
generatingScroll();
|
generatingScroll();
|
||||||
TextareaDom.current?.focus();
|
isPc && TextareaDom.current?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
@@ -342,13 +344,13 @@ const ChatBox = (
|
|||||||
[
|
[
|
||||||
isChatting,
|
isChatting,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
setChatHistory,
|
|
||||||
resetInputVal,
|
resetInputVal,
|
||||||
toast,
|
toast,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
onStartChat,
|
onStartChat,
|
||||||
generatingMessage,
|
generatingMessage,
|
||||||
generatingScroll
|
generatingScroll,
|
||||||
|
isPc
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ export enum OpenAiChatEnum {
|
|||||||
export const defaultApp: AppSchema = {
|
export const defaultApp: AppSchema = {
|
||||||
_id: '',
|
_id: '',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
name: '模型名称',
|
name: '模型加载中',
|
||||||
avatar: '/icon/logo.png',
|
avatar: '/icon/logo.png',
|
||||||
intro: '',
|
intro: '',
|
||||||
updateTime: Date.now(),
|
updateTime: Date.now(),
|
||||||
|
@@ -39,8 +39,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
|
|
||||||
const { kb_ids = [], userChatInput } = req.body as Props;
|
const { kb_ids = [], userChatInput } = req.body as Props;
|
||||||
|
|
||||||
if (!userChatInput || !Array.isArray(kb_ids)) {
|
if (!userChatInput) {
|
||||||
throw new Error('params is error');
|
throw new Error('用户输入为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(kb_ids) || kb_ids.length === 0) {
|
||||||
|
throw new Error('没有选择知识库');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await kbSearch({
|
const result = await kbSearch({
|
||||||
|
@@ -27,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
_id: item._id,
|
_id: item._id,
|
||||||
shareId: item.shareId,
|
shareId: item.shareId,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
tokens: item.tokens,
|
total: item.total,
|
||||||
maxContext: item.maxContext,
|
maxContext: item.maxContext,
|
||||||
lastTime: item.lastTime
|
lastTime: item.lastTime
|
||||||
}))
|
}))
|
||||||
|
@@ -188,7 +188,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
|
|
||||||
// bill
|
// bill
|
||||||
finishTaskBill({
|
finishTaskBill({
|
||||||
billId
|
billId,
|
||||||
|
shareId
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
delTaskBill(billId);
|
delTaskBill(billId);
|
||||||
|
@@ -51,17 +51,8 @@ const Settings = ({ appId }: { appId: string }) => {
|
|||||||
}, [appDetail, setIsLoading, toast, router]);
|
}, [appDetail, setIsLoading, toast, router]);
|
||||||
|
|
||||||
// load app data
|
// load app data
|
||||||
const { isLoading, refetch } = useQuery([appId], () => loadAppDetail(appId, true), {
|
const { refetch } = useQuery([appId], () => loadAppDetail(appId, true), {
|
||||||
onError(err: any) {
|
enabled: false
|
||||||
toast({
|
|
||||||
title: err?.message || '获取应用异常',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
router.replace('/app/list');
|
|
||||||
},
|
|
||||||
onSettled() {
|
|
||||||
router.prefetch(`/chat?appId=${appId}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -168,7 +159,7 @@ const Settings = ({ appId }: { appId: string }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<ConfirmChild />
|
<ConfirmChild />
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading fixed={false} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -38,6 +38,7 @@ import { defaultShareChat } from '@/constants/model';
|
|||||||
import type { ShareChatEditType } from '@/types/app';
|
import type { ShareChatEditType } from '@/types/app';
|
||||||
import MyTooltip from '@/components/MyTooltip';
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
import { useRequest } from '@/hooks/useRequest';
|
import { useRequest } from '@/hooks/useRequest';
|
||||||
|
import { formatPrice } from '@/utils/user';
|
||||||
|
|
||||||
const Share = ({ appId }: { appId: string }) => {
|
const Share = ({ appId }: { appId: string }) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -82,12 +83,6 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// format share used token
|
|
||||||
const formatTokens = (tokens: number) => {
|
|
||||||
if (tokens < 10000) return tokens;
|
|
||||||
return `${(tokens / 10000).toFixed(2)}万`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position={'relative'} pt={[0, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
<Box position={'relative'} pt={[0, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
@@ -118,7 +113,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Th>名称</Th>
|
<Th>名称</Th>
|
||||||
<Th>最大上下文</Th>
|
<Th>最大上下文</Th>
|
||||||
<Th>tokens消耗</Th>
|
<Th>金额消耗</Th>
|
||||||
<Th>最后使用时间</Th>
|
<Th>最后使用时间</Th>
|
||||||
<Th>操作</Th>
|
<Th>操作</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
@@ -128,7 +123,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Tr key={item._id}>
|
<Tr key={item._id}>
|
||||||
<Td>{item.name}</Td>
|
<Td>{item.name}</Td>
|
||||||
<Td>{item.maxContext}</Td>
|
<Td>{item.maxContext}</Td>
|
||||||
<Td>{formatTokens(item.tokens)}</Td>
|
<Td>{formatPrice(item.total)}元</Td>
|
||||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Flex>
|
<Flex>
|
||||||
|
@@ -4,6 +4,8 @@ import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
|
|||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { defaultApp } from '@/constants/model';
|
import { defaultApp } from '@/constants/model';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import Tabs from '@/components/Tabs';
|
import Tabs from '@/components/Tabs';
|
||||||
import SideTabs from '@/components/SideTabs';
|
import SideTabs from '@/components/SideTabs';
|
||||||
@@ -28,8 +30,9 @@ enum TabEnum {
|
|||||||
const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { toast } = useToast();
|
||||||
const { appId } = router.query as { appId: string };
|
const { appId } = router.query as { appId: string };
|
||||||
const { appDetail = defaultApp, clearAppModules } = useUserStore();
|
const { appDetail = defaultApp, loadAppDetail, clearAppModules } = useUserStore();
|
||||||
|
|
||||||
const setCurrentTab = useCallback(
|
const setCurrentTab = useCallback(
|
||||||
(tab: `${TabEnum}`) => {
|
(tab: `${TabEnum}`) => {
|
||||||
@@ -65,6 +68,19 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useQuery([appId], () => loadAppDetail(appId, true), {
|
||||||
|
onError(err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '获取应用异常',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
router.replace('/app/list');
|
||||||
|
},
|
||||||
|
onSettled() {
|
||||||
|
router.prefetch(`/chat?appId=${appId}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Flex flexDirection={['column', 'row']} h={'100%'}>
|
<Flex flexDirection={['column', 'row']} h={'100%'}>
|
||||||
|
@@ -47,7 +47,7 @@ export const pushTaskBillListItem = async ({
|
|||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
export const finishTaskBill = async ({ billId }: { billId: string }) => {
|
export const finishTaskBill = async ({ billId, shareId }: { billId: string; shareId?: string }) => {
|
||||||
try {
|
try {
|
||||||
// update bill
|
// update bill
|
||||||
const res = await Bill.findByIdAndUpdate(billId, [
|
const res = await Bill.findByIdAndUpdate(billId, [
|
||||||
@@ -63,6 +63,13 @@ export const finishTaskBill = async ({ billId }: { billId: string }) => {
|
|||||||
if (!res) return;
|
if (!res) return;
|
||||||
const total = res.list.reduce((sum, item) => sum + item.amount, 0) || 0;
|
const total = res.list.reduce((sum, item) => sum + item.amount, 0) || 0;
|
||||||
|
|
||||||
|
if (shareId) {
|
||||||
|
updateShareChatBill({
|
||||||
|
shareId,
|
||||||
|
total
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log('finish bill:', formatPrice(total));
|
console.log('finish bill:', formatPrice(total));
|
||||||
|
|
||||||
// 账号扣费
|
// 账号扣费
|
||||||
@@ -85,16 +92,19 @@ export const delTaskBill = async (billId?: string) => {
|
|||||||
|
|
||||||
export const updateShareChatBill = async ({
|
export const updateShareChatBill = async ({
|
||||||
shareId,
|
shareId,
|
||||||
tokens
|
total
|
||||||
}: {
|
}: {
|
||||||
shareId: string;
|
shareId: string;
|
||||||
tokens: number;
|
total: number;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
await ShareChat.findByIdAndUpdate(shareId, {
|
await ShareChat.findOneAndUpdate(
|
||||||
$inc: { tokens },
|
{ shareId },
|
||||||
|
{
|
||||||
|
$inc: { total },
|
||||||
lastTime: new Date()
|
lastTime: new Date()
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('update shareChat error', error);
|
console.log('update shareChat error', error);
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ const ShareChatSchema = new Schema({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
tokens: {
|
total: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
|
@@ -25,7 +25,10 @@ export const generateToken = (userId: string) => {
|
|||||||
|
|
||||||
/* set cookie */
|
/* set cookie */
|
||||||
export const setCookie = (res: NextApiResponse, userId: string) => {
|
export const setCookie = (res: NextApiResponse, userId: string) => {
|
||||||
res.setHeader('Set-Cookie', `token=${generateToken(userId)}; Path=/; HttpOnly; Max-Age=604800`);
|
res.setHeader(
|
||||||
|
'Set-Cookie',
|
||||||
|
`token=${generateToken(userId)}; Path=/; HttpOnly; Max-Age=604800; Samesite=None; Secure;`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
/* clear cookie */
|
/* clear cookie */
|
||||||
export const clearCookie = (res: NextApiResponse) => {
|
export const clearCookie = (res: NextApiResponse) => {
|
||||||
|
2
client/src/types/mongoSchema.d.ts
vendored
2
client/src/types/mongoSchema.d.ts
vendored
@@ -137,7 +137,7 @@ export interface ShareChatSchema {
|
|||||||
userId: string;
|
userId: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
name: string;
|
name: string;
|
||||||
tokens: number;
|
total: number;
|
||||||
maxContext: number;
|
maxContext: number;
|
||||||
lastTime: Date;
|
lastTime: Date;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user