### What problem does this PR solve? Feat: Display the thinking process according to the start_to_think flag of the message #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| import * as SelectPrimitive from '@radix-ui/react-select'; | import * as SelectPrimitive from '@radix-ui/react-select'; | ||||
| import { forwardRef, memo, useState } from 'react'; | import { forwardRef, memo, useState } from 'react'; | ||||
| import { LlmSettingFieldItems } from '../llm-setting-items/next'; | import { LlmSettingFieldItems } from '../llm-setting-items/next'; | ||||
| import { SelectWithSearch } from '../originui/select-with-search'; | |||||
| import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; | import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; | ||||
| import { Select, SelectTrigger, SelectValue } from '../ui/select'; | import { Select, SelectTrigger, SelectValue } from '../ui/select'; | ||||
| id?: string; | id?: string; | ||||
| value?: string; | value?: string; | ||||
| onInitialValue?: (value: string, option: any) => void; | onInitialValue?: (value: string, option: any) => void; | ||||
| onChange?: (value: string, option: any) => void; | |||||
| onChange?: (value: string) => void; | |||||
| disabled?: boolean; | disabled?: boolean; | ||||
| } | } | ||||
| const NextInnerLLMSelect = forwardRef< | const NextInnerLLMSelect = forwardRef< | ||||
| React.ElementRef<typeof SelectPrimitive.Trigger>, | React.ElementRef<typeof SelectPrimitive.Trigger>, | ||||
| IProps | IProps | ||||
| >(({ value, disabled }, ref) => { | |||||
| >(({ value, disabled, onChange }, ref) => { | |||||
| const [isPopoverOpen, setIsPopoverOpen] = useState(false); | const [isPopoverOpen, setIsPopoverOpen] = useState(false); | ||||
| const modelOptions = useComposeLlmOptionsByModelTypes([ | const modelOptions = useComposeLlmOptionsByModelTypes([ | ||||
| LlmModelType.Chat, | LlmModelType.Chat, | ||||
| LlmModelType.Image2text, | LlmModelType.Image2text, | ||||
| ]); | ]); | ||||
| return ( | |||||
| <SelectWithSearch | |||||
| options={modelOptions} | |||||
| value={value} | |||||
| onChange={onChange} | |||||
| ></SelectWithSearch> | |||||
| ); | |||||
| return ( | return ( | ||||
| <Select disabled={disabled} value={value}> | <Select disabled={disabled} value={value}> | ||||
| <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}> | <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}> |
| import { CheckIcon, ChevronDownIcon } from 'lucide-react'; | import { CheckIcon, ChevronDownIcon } from 'lucide-react'; | ||||
| import { | import { | ||||
| Fragment, | Fragment, | ||||
| ReactNode, | |||||
| forwardRef, | forwardRef, | ||||
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| import { RAGFlowSelectOptionType } from '../ui/select'; | import { RAGFlowSelectOptionType } from '../ui/select'; | ||||
| export type SelectWithSearchFlagOptionType = { | export type SelectWithSearchFlagOptionType = { | ||||
| label: string; | |||||
| label: ReactNode; | |||||
| value?: string; | value?: string; | ||||
| options?: RAGFlowSelectOptionType[]; | options?: RAGFlowSelectOptionType[]; | ||||
| }; | }; | ||||
| <CommandInput placeholder="Search ..." /> | <CommandInput placeholder="Search ..." /> | ||||
| <CommandList> | <CommandList> | ||||
| <CommandEmpty>No data found.</CommandEmpty> | <CommandEmpty>No data found.</CommandEmpty> | ||||
| {options.map((group) => { | |||||
| {options.map((group, idx) => { | |||||
| if (group.options) { | if (group.options) { | ||||
| return ( | return ( | ||||
| <Fragment key={group.label}> | |||||
| <Fragment key={idx}> | |||||
| <CommandGroup heading={group.label}> | <CommandGroup heading={group.label}> | ||||
| {group.options.map((option) => ( | {group.options.map((option) => ( | ||||
| <CommandItem | <CommandItem |
| export interface IMessageData { | export interface IMessageData { | ||||
| content: string; | content: string; | ||||
| start_to_think?: boolean; | |||||
| end_to_think?: boolean; | |||||
| } | } | ||||
| export interface IMessageEndData { | export interface IMessageEndData { |
| const messageEventList = eventList.filter( | const messageEventList = eventList.filter( | ||||
| (x) => x.event === MessageEventType.Message, | (x) => x.event === MessageEventType.Message, | ||||
| ) as IMessageEvent[]; | ) as IMessageEvent[]; | ||||
| let nextContent = ''; | |||||
| let startIndex = -1; | |||||
| let endIndex = -1; | |||||
| messageEventList.forEach((x, idx) => { | |||||
| const { data } = x; | |||||
| const { content, start_to_think, end_to_think } = data; | |||||
| if (start_to_think === true) { | |||||
| nextContent += '<think>' + content; | |||||
| startIndex = idx; | |||||
| return; | |||||
| } | |||||
| if (end_to_think === true) { | |||||
| endIndex = idx; | |||||
| nextContent += content + '</think>'; | |||||
| return; | |||||
| } | |||||
| nextContent += content; | |||||
| }); | |||||
| const currentIdx = messageEventList.length - 1; | |||||
| // Make sure that after start_to_think === true and before end_to_think === true, add a </think> tag at the end. | |||||
| if (startIndex >= 0 && startIndex <= currentIdx && endIndex === -1) { | |||||
| nextContent += '</think>'; | |||||
| } | |||||
| return { | return { | ||||
| id: eventList[0]?.message_id, | id: eventList[0]?.message_id, | ||||
| content: messageEventList.map((x) => x.data.content).join(''), | |||||
| content: nextContent, | |||||
| }; | }; | ||||
| } | } | ||||