From 118e3336001bd44549beb51818c9215d12022d79 Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Thu, 27 Jul 2023 12:10:30 +0800 Subject: [PATCH] perf: flow value type --- client/src/constants/flow/ModuleTemplate.ts | 30 ++++- client/src/constants/flow/index.ts | 32 ++++++ client/src/constants/flow/inputTemplate.ts | 11 +- client/src/pages/api/admin/initv4.ts | 1 - .../AdEdit/components/Nodes/NodeCQNode.tsx | 17 +-- .../components/Nodes/NodeQuestionInput.tsx | 27 ++--- .../AdEdit/components/modules/NodeCard.tsx | 6 +- .../AdEdit/components/render/RenderInput.tsx | 24 +--- .../AdEdit/components/render/RenderOutput.tsx | 17 +-- .../AdEdit/components/render/SourceHandle.tsx | 42 +++++++ .../AdEdit/components/render/TargetHandle.tsx | 44 ++++++++ .../src/service/moduleDispatch/chat/oneapi.ts | 4 +- client/src/types/flow.d.ts | 5 +- client/src/utils/adapt.ts | 5 +- docSite/docs/datasets/prompt.md | 106 ++++++++++++++++++ 15 files changed, 292 insertions(+), 79 deletions(-) create mode 100644 client/src/pages/app/detail/components/AdEdit/components/render/SourceHandle.tsx create mode 100644 client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx create mode 100644 docSite/docs/datasets/prompt.md diff --git a/client/src/constants/flow/ModuleTemplate.ts b/client/src/constants/flow/ModuleTemplate.ts index ff2371dcf..9188121d4 100644 --- a/client/src/constants/flow/ModuleTemplate.ts +++ b/client/src/constants/flow/ModuleTemplate.ts @@ -4,7 +4,8 @@ import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum, - SpecialInputKeyEnum + SpecialInputKeyEnum, + FlowValueTypeEnum } from './index'; import type { AppItemType } from '@/types/app'; import type { FlowModuleTemplateType } from '@/types/flow'; @@ -56,7 +57,7 @@ export const UserGuideModule: FlowModuleTemplateType = { }; export const UserInputModule: FlowModuleTemplateType = { logo: '/imgs/module/userChatInput.png', - name: '用户问题', + name: '用户问题(对话入口)', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', flowType: FlowModuleTypeEnum.questionInput, url: '/app/modules/init/userChatInput', @@ -72,6 +73,7 @@ export const UserInputModule: FlowModuleTemplateType = { key: SystemInputEnum.userChatInput, label: '用户问题', type: FlowOutputItemTypeEnum.source, + valueType: FlowValueTypeEnum.string, targets: [] } ] @@ -101,6 +103,7 @@ export const HistoryModule: FlowModuleTemplateType = { { key: SystemInputEnum.history, label: '聊天记录', + valueType: FlowValueTypeEnum.chatHistory, type: FlowOutputItemTypeEnum.source, targets: [] } @@ -155,6 +158,7 @@ export const ChatModule: FlowModuleTemplateType = { key: 'systemPrompt', type: FlowInputItemTypeEnum.textarea, label: '系统提示词', + valueType: FlowValueTypeEnum.string, description: ChatModelSystemTip, placeholder: ChatModelSystemTip, value: '' @@ -162,6 +166,7 @@ export const ChatModule: FlowModuleTemplateType = { { key: 'limitPrompt', type: FlowInputItemTypeEnum.textarea, + valueType: FlowValueTypeEnum.string, label: '限定词', description: ChatModelLimitTip, placeholder: ChatModelLimitTip, @@ -171,7 +176,8 @@ export const ChatModule: FlowModuleTemplateType = { { key: 'quoteQA', type: FlowInputItemTypeEnum.target, - label: '引用内容' + label: '引用内容', + valueType: FlowValueTypeEnum.kbQuote }, Input_Template_History, Input_Template_UserChatInput @@ -183,6 +189,14 @@ export const ChatModule: FlowModuleTemplateType = { description: '直接响应,无需配置', type: FlowOutputItemTypeEnum.hidden, targets: [] + }, + { + key: 'finish', + label: '回复结束', + description: 'AI 回复完成后触发', + valueType: FlowValueTypeEnum.boolean, + type: FlowOutputItemTypeEnum.source, + targets: [] } ] }; @@ -236,12 +250,14 @@ export const KBSearchModule: FlowModuleTemplateType = { key: 'isEmpty', label: '搜索结果为空', type: FlowOutputItemTypeEnum.source, + valueType: FlowValueTypeEnum.boolean, targets: [] }, { key: 'unEmpty', label: '搜索结果不为空', type: FlowOutputItemTypeEnum.source, + valueType: FlowValueTypeEnum.boolean, targets: [] }, { @@ -249,6 +265,7 @@ export const KBSearchModule: FlowModuleTemplateType = { label: '引用内容', description: '搜索结果为空时不返回', type: FlowOutputItemTypeEnum.source, + valueType: FlowValueTypeEnum.kbQuote, targets: [] } ] @@ -257,7 +274,8 @@ export const KBSearchModule: FlowModuleTemplateType = { export const AnswerModule: FlowModuleTemplateType = { logo: '/imgs/module/reply.png', name: '指定回复', - intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。', + intro: '该模块可以直接回复一段指定的内容。常用于引导、提示', + description: '该模块可以直接回复一段指定的内容。常用于引导、提示', flowType: FlowModuleTypeEnum.answerNode, inputs: [ Input_Template_TFSwitch, @@ -265,7 +283,8 @@ export const AnswerModule: FlowModuleTemplateType = { key: SpecialInputKeyEnum.answerText, value: '', type: FlowInputItemTypeEnum.textarea, - label: '回复的内容' + label: '回复的内容', + description: '可以使用 \\n 来实现换行' } ], outputs: [] @@ -309,6 +328,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = { { key: 'systemPrompt', type: FlowInputItemTypeEnum.textarea, + valueType: FlowValueTypeEnum.string, label: '系统提示词', description: '你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。', diff --git a/client/src/constants/flow/index.ts b/client/src/constants/flow/index.ts index a67584b3b..70cc3aa64 100644 --- a/client/src/constants/flow/index.ts +++ b/client/src/constants/flow/index.ts @@ -1,3 +1,5 @@ +import type { BoxProps } from '@chakra-ui/react'; + export enum FlowInputItemTypeEnum { systemInput = 'systemInput', // history, userChatInput, variableInput input = 'input', @@ -35,6 +37,36 @@ export enum SpecialInputKeyEnum { 'answerText' = 'text' } +export enum FlowValueTypeEnum { + 'string' = 'string', + 'number' = 'number', + 'boolean' = 'boolean', + 'chatHistory' = 'chatHistory', + 'kbQuote' = 'kbQuote', + 'other' = 'other' +} + +export const FlowValueTypeStyle: Record<`${FlowValueTypeEnum}`, BoxProps> = { + [FlowValueTypeEnum.string]: { + background: '#36ADEF' + }, + [FlowValueTypeEnum.number]: { + background: '#FB7C3C' + }, + [FlowValueTypeEnum.boolean]: { + background: '#E7D118' + }, + [FlowValueTypeEnum.chatHistory]: { + background: '#00A9A6' + }, + [FlowValueTypeEnum.kbQuote]: { + background: '#A558C9' + }, + [FlowValueTypeEnum.other]: { + background: '#9CA2A8' + } +}; + export const initModuleType: Record = { [FlowModuleTypeEnum.historyNode]: true, [FlowModuleTypeEnum.questionInput]: true diff --git a/client/src/constants/flow/inputTemplate.ts b/client/src/constants/flow/inputTemplate.ts index 3dbef267a..5dec33240 100644 --- a/client/src/constants/flow/inputTemplate.ts +++ b/client/src/constants/flow/inputTemplate.ts @@ -1,22 +1,25 @@ import { FlowInputItemType } from '@/types/flow'; import { SystemInputEnum } from '../app'; -import { FlowInputItemTypeEnum } from './index'; +import { FlowInputItemTypeEnum, FlowValueTypeEnum } from './index'; export const Input_Template_TFSwitch: FlowInputItemType = { key: SystemInputEnum.switch, type: FlowInputItemTypeEnum.target, - label: '触发器' + label: '触发器', + valueType: FlowValueTypeEnum.boolean }; export const Input_Template_History: FlowInputItemType = { key: SystemInputEnum.history, type: FlowInputItemTypeEnum.target, - label: '聊天记录' + label: '聊天记录', + valueType: FlowValueTypeEnum.chatHistory }; export const Input_Template_UserChatInput: FlowInputItemType = { key: SystemInputEnum.userChatInput, type: FlowInputItemTypeEnum.target, label: '用户问题', - required: true + required: true, + valueType: FlowValueTypeEnum.string }; diff --git a/client/src/pages/api/admin/initv4.ts b/client/src/pages/api/admin/initv4.ts index a9c07b26d..ef7d67096 100644 --- a/client/src/pages/api/admin/initv4.ts +++ b/client/src/pages/api/admin/initv4.ts @@ -3,7 +3,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { authUser } from '@/service/utils/auth'; import { connectToDatabase, App } from '@/service/mongo'; -import { EditFormType } from '@/utils/app'; import { AppModuleInputItemType } from '@/types/app'; import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow'; import { TaskResponseKeyEnum } from '@/constants/chat'; diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx index 775f5715c..b93fb512b 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx @@ -11,7 +11,8 @@ import { Handle, Position } from 'reactflow'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4); import MyIcon from '@/components/Icon'; -import { FlowOutputItemTypeEnum } from '@/constants/flow'; +import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow'; +import SourceHandle from '../render/SourceHandle'; const NodeCQNode = ({ data: { moduleId, inputs, outputs, onChangeNode, ...props } @@ -82,19 +83,7 @@ const NodeCQNode = ({ }); }} /> - + diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx index e70f41fac..bed59d523 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx @@ -1,31 +1,26 @@ import React from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; +import { NodeProps } from 'reactflow'; import { Box } from '@chakra-ui/react'; import NodeCard from '../modules/NodeCard'; import { FlowModuleItemType } from '@/types/flow'; import Container from '../modules/Container'; import { SystemInputEnum } from '@/constants/app'; +import { FlowValueTypeEnum } from '@/constants/flow'; +import SourceHandle from '../render/SourceHandle'; const QuestionInputNode = ({ data: { inputs, outputs, onChangeNode, ...props } }: NodeProps) => { return ( - + - 用户问题 - + + 用户问题 + + ); diff --git a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx index f1ff835ce..bd24bd52b 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx @@ -37,7 +37,11 @@ const NodeCard = ({ {description && ( - + )} diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx index 931557428..6ccc4a2b3 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx @@ -12,10 +12,10 @@ import { } from '@chakra-ui/react'; import { FlowInputItemTypeEnum } from '@/constants/flow'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import { Handle, Position } from 'reactflow'; import MySelect from '@/components/Select'; import MySlider from '@/components/Slider'; import MyTooltip from '@/components/MyTooltip'; +import TargetHandle from './TargetHandle'; export const Label = ({ required = false, @@ -35,7 +35,7 @@ export const Label = ({ )} {description && ( - + )} @@ -61,6 +61,10 @@ const RenderBody = ({ {!!item.label && ( )} @@ -148,22 +152,6 @@ const RenderBody = ({ {item.type === FlowInputItemTypeEnum.custom && CustomComponent[item.key] && ( <>{CustomComponent[item.key]({ ...item })} )} - {item.type === FlowInputItemTypeEnum.target && ( - console.log('handle onConnect', params)} - /> - )} ) diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx index f8df4bddb..3ad299abb 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx @@ -5,6 +5,7 @@ import { FlowOutputItemTypeEnum } from '@/constants/flow'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { Handle, Position } from 'reactflow'; import MyTooltip from '@/components/MyTooltip'; +import SourceHandle from './SourceHandle'; const Label = ({ children, @@ -16,7 +17,7 @@ const Label = ({ {description && ( - + )} {children} @@ -33,19 +34,7 @@ const RenderBody = ({ flowOutputList }: { flowOutputList: FlowOutputItemType[] } {item.type === FlowOutputItemTypeEnum.source && ( - + )} diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/SourceHandle.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/SourceHandle.tsx new file mode 100644 index 000000000..8d7e74e0a --- /dev/null +++ b/client/src/pages/app/detail/components/AdEdit/components/render/SourceHandle.tsx @@ -0,0 +1,42 @@ +import React, { useMemo } from 'react'; +import { Box, BoxProps } from '@chakra-ui/react'; +import { Handle, Position } from 'reactflow'; +import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow'; + +interface Props extends BoxProps { + handleKey: string; + valueType?: `${FlowValueTypeEnum}`; +} + +const SourceHandle = ({ handleKey, valueType, ...props }: Props) => { + const valueStyle = useMemo( + () => + valueType + ? FlowValueTypeStyle[valueType] + : (FlowValueTypeStyle[FlowValueTypeEnum.other] as any), + [] + ); + + return ( + + + + ); +}; + +export default SourceHandle; diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx new file mode 100644 index 000000000..fca1dc485 --- /dev/null +++ b/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx @@ -0,0 +1,44 @@ +import React, { useMemo } from 'react'; +import { Box, BoxProps } from '@chakra-ui/react'; +import { Handle, OnConnect, Position } from 'reactflow'; +import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow'; + +interface Props extends BoxProps { + handleKey: string; + valueType?: `${FlowValueTypeEnum}`; + onConnect?: OnConnect; +} + +const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => { + const valueStyle = useMemo( + () => + valueType + ? FlowValueTypeStyle[valueType] + : (FlowValueTypeStyle[FlowValueTypeEnum.other] as any), + [] + ); + + return ( + + + + ); +}; + +export default TargetHandle; diff --git a/client/src/service/moduleDispatch/chat/oneapi.ts b/client/src/service/moduleDispatch/chat/oneapi.ts index 23919a5d8..e33986904 100644 --- a/client/src/service/moduleDispatch/chat/oneapi.ts +++ b/client/src/service/moduleDispatch/chat/oneapi.ts @@ -30,6 +30,7 @@ export type ChatProps = { export type ChatResponse = { [TaskResponseKeyEnum.answerText]: string; [TaskResponseKeyEnum.responseData]: ChatHistoryItemResType; + finish: boolean; }; /* request openai chat */ @@ -143,7 +144,8 @@ export const dispatchChatCompletion = async (props: Record): Promis maxToken, quoteList: filterQuoteQA, completeMessages - } + }, + finish: true }; }; diff --git a/client/src/types/flow.d.ts b/client/src/types/flow.d.ts index a41bc38d0..90edf567f 100644 --- a/client/src/types/flow.d.ts +++ b/client/src/types/flow.d.ts @@ -1,7 +1,8 @@ import { FlowBodyItemTypeEnum, FlowInputItemTypeEnum, - FlowOutputItemTypeEnum + FlowOutputItemTypeEnum, + FlowValueTypeEnum } from '@/constants/flow'; import { Connection } from 'reactflow'; import type { AppModuleItemType } from './app'; @@ -18,6 +19,7 @@ export type FlowModuleItemChangeProps = { export type FlowInputItemType = { key: string; // 字段名 value?: any; + valueType?: `${FlowValueTypeEnum}`; type: `${FlowInputItemTypeEnum}`; label: string; connected?: boolean; @@ -39,6 +41,7 @@ export type FlowOutputItemType = { key: string; // 字段名 label: string; description?: string; + valueType?: `${FlowValueTypeEnum}`; type: `${FlowOutputItemTypeEnum}`; targets: FlowOutputTargetItemType[]; }; diff --git a/client/src/utils/adapt.ts b/client/src/utils/adapt.ts index 176a1b8c8..da1d0fcac 100644 --- a/client/src/utils/adapt.ts +++ b/client/src/utils/adapt.ts @@ -76,10 +76,7 @@ export const appModule2FlowNode = ({ // replace item data const moduleItem: FlowModuleItemType = { ...item, - logo: template.logo, - name: template.name, - intro: template.intro, - url: template.url, + ...template, inputs: template.inputs.map((templateInput) => { // use latest inputs const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput; diff --git a/docSite/docs/datasets/prompt.md b/docSite/docs/datasets/prompt.md new file mode 100644 index 000000000..25d887e41 --- /dev/null +++ b/docSite/docs/datasets/prompt.md @@ -0,0 +1,106 @@ +# 提示词示例 + +## 客服 + +``` +您好您将扮演万千紧固件公司的售前客服。在第一次咨询中,您需要获取客户的联系人姓名和手机号码,并了解客户是终端生产企业还是工贸经销商。以下是万千紧固件的背景资料:成立于2009年,是一家致力于高质服务的工业零部B2B平台,主要售紧固件,提供标准件和非标定制服务,在河北永年和江苏戴南均有自己的源头工厂,总部位于江苏省无锡市。 +您需要遵守以下规则: +1、热情友好,让客户感到愉悦; +2、对于产品专业问题无法回答或客户不满意,留下联系方式让专业产品经理负责; +3、回答问题时言简意赅,仔细思考; +4、客户问产品是否有货时,不要回答没有,而是说我们都能提供; +5、与客户进行简短沟通,了解需求并确认所需产品后,索要联系方式。 + + +样本示例1: +客户:在吗? +客服:在的,请问您需要咨询什么产品? +客户:防盗螺栓有吗? +客服:有的,我们提供全品类的紧固件产品,请告诉我具体的型号和用途。 +客户:用在铁塔机器上的,2040304不锈钢 +客服:请问需要多少件? +客户:大概10万颗左右吧 +客户:给我个报价,优惠些,长期合作 +客服:非常抱歉,报价需要我们专业的产品同事,您能留下联系方式吗?我会安排他们立即与您联系。 +客户:王先,13928916752今天能联系么 +客服:可以的,我们的同事会在1个小时内与您联系,请注意接听来自无锡的电话。谢谢您的咨询。 + +样本示例2: +客户:请问一下这个螺栓有没有33长度的? +客服:您好,我是客服小万,请问您需要的是哪种螺栓?能说得更具体一些吗? +客户:圆头方颈。 +客服:头部多大? +客户:13。 +客户:M6的。 +客服:您要的是哪个材质的呢? +客户:碳钢的,强度高一点。 +客服:您需要多少个呢? +客户:大约2000个左右。 +客服:方便留下您的联系方式吗? +客服:我会让同事在上午与您确认并回复。 +客户:18217699040。 +客服:请问您的姓氏是? +客户:王。 +客服:好的。 +客户:谢谢。 + +样本示例3: +客户:你们是生产还是贸易? +客服:亲,感谢您的咨询。我们既生产也贸易,同时接受标准件和非标定制的订单。请问您需要采购什么产品? +客户:SPR25x100L铜套的304材质螺母M64。 +客户:我想找源头工厂。 +客服:亲,我们就是源头工厂哦!请问您是关心价格还是质量呢?我们万千是国内大品牌,价格实惠,质量和交期都有保障。 +客户:工厂放心点,经销商门道太多。 +客服:亲,您可以放心,我们的产品质量有保障,如有不满意可退换;有问题我们也有专属客服经理24小时处理。 +客服:请问您具体需要多少数量呢? +客户:2456个。 +客户:什么时候能给出报价?我这边比较着急使用。 +客服:亲,请留下您的联系方式,我会让我们专业的产品同事与您联系。 +客户:13928956789,李。 + +样本示例4: +客户:你好,在吗? +客服:在的。 +客服:请问您需要什么产品? +客户:卡箍有吗? +客服:有的,您需要哪一种? +客户:我是用在电线杆上的。 +客户:我需要200个。 +客服:好的,您需要什么材质的? +客户:有图片吗?我想看看是不是我需要的那种,材质无所谓。 +客服:好的,方便加您微信吗?在线上不方便发图片,加微信后我会给您发送图片以进行确认。 +客户:您的微信是多少?我加您。 +客服:好的,我的微信是18626076792,您可以添加一下。您也可以直接拨打电话与我们联系。 +客户:已添加,请通过微信沟通。 +客服:好的,已通过,我需要记录一下,请问您的电话方便吗? +客户:187628100000。 +服:请问您的姓氏是? +客户:陈。 +客服:好的。 +客户:谢谢。 + +样本示例5: +户:87322.5。 +客服:好的,您是要扁圆头半空心铆钉吗?您需要具体什么材质的呢? +客户:材质无所谓。您们的最低起订量是多少? +客服:起订量这边需要帮您查询一下系统。您是只要规格,材质无所谓吗? +客户:是的,因为不好买。 +客服:好的,这边稍后查一下系统,确认是否有现货。如果没有现货,是否可以接受定制?我们公司也支持定制服务。 +客户:定制需要多少起订量?您能帮我询问一下不同材质的要求吗? +客服:好的,方便留下您的联系方式吗?稍后我们会安排专业的同事与您联系,直接对接沟通。 +客户:18190818931。 +客服:请问您的姓氏是? +客户:杨。 +客服:好的,请稍等。 + +样品示例6 +客户:不锈钢201 M87,单价多少? +客服说:你好,请问您需要的是哪一种螺丝? +客户:外六角螺栓 +客服说:好的,你需要多少啊?我们量大优惠越大 +客户:10000左右 +客服说:好的,只有这一种产品需要帮你报价吗,还有其他的产品需要一并帮你确认价格吗? +客户:全牙201不锈钢 M825 +客服说:好的,您总共有几种产品需要报价? +客户:十几种 +```