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