Feat: Allow agent operators to select speech-to-text models #3221 (#9534)

### What problem does this PR solve?

Feat: Allow agent operators to select speech-to-text models #3221
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-08-19 09:40:01 +08:00
committed by GitHub
parent 2b9ed935f3
commit 32349481ef
12 changed files with 57 additions and 35 deletions

View File

@ -38,9 +38,12 @@ export const LargeModelFilterFormSchema = {
llm_filter: z.string().optional(), llm_filter: z.string().optional(),
}; };
type LargeModelFormFieldProps = Pick<NextInnerLLMSelectProps, 'showTTSModel'>; type LargeModelFormFieldProps = Pick<
NextInnerLLMSelectProps,
'showSpeech2TextModel'
>;
export function LargeModelFormField({ export function LargeModelFormField({
showTTSModel, showSpeech2TextModel: showTTSModel,
}: LargeModelFormFieldProps) { }: LargeModelFormFieldProps) {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslation(); const { t } = useTranslation();
@ -91,7 +94,7 @@ export function LargeModelFormField({
<NextLLMSelect <NextLLMSelect
{...field} {...field}
filter={filter} filter={filter}
showTTSModel={showTTSModel} showSpeech2TextModel={showTTSModel}
/> />
</FormControl> </FormControl>
</section> </section>

View File

@ -13,18 +13,18 @@ export interface NextInnerLLMSelectProps {
onChange?: (value: string) => void; onChange?: (value: string) => void;
disabled?: boolean; disabled?: boolean;
filter?: string; filter?: string;
showTTSModel?: boolean; showSpeech2TextModel?: boolean;
} }
const NextInnerLLMSelect = forwardRef< const NextInnerLLMSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
NextInnerLLMSelectProps NextInnerLLMSelectProps
>(({ value, disabled, filter, showTTSModel = false }, ref) => { >(({ value, disabled, filter, showSpeech2TextModel = false }, ref) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const ttsModel = useMemo(() => { const ttsModel = useMemo(() => {
return showTTSModel ? [LlmModelType.TTS] : []; return showSpeech2TextModel ? [LlmModelType.Speech2text] : [];
}, [showTTSModel]); }, [showSpeech2TextModel]);
const modelTypes = useMemo(() => { const modelTypes = useMemo(() => {
if (filter === LlmModelType.Chat) { if (filter === LlmModelType.Chat) {

View File

@ -24,7 +24,7 @@
.messageText { .messageText {
.chunkText(); .chunkText();
.messageTextBase(); .messageTextBase();
background-color: #e6f4ff; // background-color: #e6f4ff;
word-break: break-word; word-break: break-word;
} }
.messageTextDark { .messageTextDark {

View File

@ -9,6 +9,7 @@ import {
useFetchDocumentThumbnailsByIds, useFetchDocumentThumbnailsByIds,
} from '@/hooks/document-hooks'; } from '@/hooks/document-hooks';
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
import { cn } from '@/lib/utils';
import { IMessage } from '@/pages/chat/interface'; import { IMessage } from '@/pages/chat/interface';
import MarkdownContent from '@/pages/chat/markdown-content'; import MarkdownContent from '@/pages/chat/markdown-content';
import { Avatar, Flex, Space } from 'antd'; import { Avatar, Flex, Space } from 'antd';
@ -129,13 +130,14 @@ const MessageItem = ({
{/* <b>{isAssistant ? '' : nickname}</b> */} {/* <b>{isAssistant ? '' : nickname}</b> */}
</Space> </Space>
<div <div
className={ className={cn(
isAssistant isAssistant
? theme === 'dark' ? theme === 'dark'
? styles.messageTextDark ? styles.messageTextDark
: styles.messageText : styles.messageText
: styles.messageUserText : styles.messageUserText,
} { '!bg-bg-card': !isAssistant },
)}
> >
<MarkdownContent <MarkdownContent
loading={loading} loading={loading}

View File

@ -369,22 +369,28 @@ export const useScrollToBottom = (
return () => container.removeEventListener('scroll', handleScroll); return () => container.removeEventListener('scroll', handleScroll);
}, [containerRef, checkIfUserAtBottom]); }, [containerRef, checkIfUserAtBottom]);
// Imperative scroll function
const scrollToBottom = useCallback(() => {
if (containerRef?.current) {
const container = containerRef.current;
container.scrollTo({
top: container.scrollHeight - container.clientHeight,
behavior: 'smooth',
});
}
}, [containerRef]);
useEffect(() => { useEffect(() => {
if (!messages) return; if (!messages) return;
if (!containerRef?.current) return; if (!containerRef?.current) return;
requestAnimationFrame(() => { requestAnimationFrame(() => {
setTimeout(() => { setTimeout(() => {
if (isAtBottomRef.current) { if (isAtBottomRef.current) {
ref.current?.scrollIntoView({ behavior: 'smooth' }); scrollToBottom();
} }
}, 30); }, 100);
}); });
}, [messages, containerRef]); }, [messages, containerRef, scrollToBottom]);
// Imperative scroll function
const scrollToBottom = useCallback(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
return { scrollRef: ref, isAtBottom, scrollToBottom }; return { scrollRef: ref, isAtBottom, scrollToBottom };
}; };

View File

@ -5,6 +5,7 @@ export default {
deleteModalTitle: 'Are you sure to delete this item?', deleteModalTitle: 'Are you sure to delete this item?',
ok: 'Yes', ok: 'Yes',
cancel: 'No', cancel: 'No',
no: 'No',
total: 'Total', total: 'Total',
rename: 'Rename', rename: 'Rename',
name: 'Name', name: 'Name',
@ -575,6 +576,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
automatic: 'Automatic', automatic: 'Automatic',
manual: 'Manual', manual: 'Manual',
}, },
cancel: 'Cancel',
chatSetting: 'Chat setting',
}, },
setting: { setting: {
profile: 'Profile', profile: 'Profile',

View File

@ -569,6 +569,8 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
automatic: '自动', automatic: '自动',
manual: '手动', manual: '手动',
}, },
cancel: '取消',
chatSetting: '聊天设置',
}, },
setting: { setting: {
profile: '概要', profile: '概要',

View File

@ -128,7 +128,7 @@ function AgentForm({ node }: INextOperatorForm) {
<FormWrapper> <FormWrapper>
<FormContainer> <FormContainer>
{isSubAgent && <DescriptionField></DescriptionField>} {isSubAgent && <DescriptionField></DescriptionField>}
<LargeModelFormField showTTSModel></LargeModelFormField> <LargeModelFormField showSpeech2TextModel></LargeModelFormField>
{findLlmByUuid(llmId)?.model_type === LlmModelType.Image2text && ( {findLlmByUuid(llmId)?.model_type === LlmModelType.Image2text && (
<QueryVariable <QueryVariable
name="visual_files_var" name="visual_files_var"

View File

@ -1,4 +1,4 @@
import { ButtonLoading } from '@/components/ui/button'; import { Button, ButtonLoading } from '@/components/ui/button';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { useFetchDialog, useSetDialog } from '@/hooks/use-chat-request'; import { useFetchDialog, useSetDialog } from '@/hooks/use-chat-request';
@ -11,6 +11,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useParams } from 'umi'; import { useParams } from 'umi';
import { z } from 'zod'; import { z } from 'zod';
import { DatasetMetadata } from '../../constants'; import { DatasetMetadata } from '../../constants';
@ -25,6 +26,7 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
const { data } = useFetchDialog(); const { data } = useFetchDialog();
const { setDialog, loading } = useSetDialog(); const { setDialog, loading } = useSetDialog();
const { id } = useParams(); const { id } = useParams();
const { t } = useTranslation();
type FormSchemaType = z.infer<typeof formSchema>; type FormSchemaType = z.infer<typeof formSchema>;
@ -89,25 +91,26 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
return ( return (
<section className="p-5 w-[440px] border-l"> <section className="p-5 w-[440px] border-l">
<div className="flex justify-between items-center text-base pb-2"> <div className="flex justify-between items-center text-base pb-2">
Chat Settings {t('chat.chatSetting')}
<X className="size-4 cursor-pointer" onClick={switchSettingVisible} /> <X className="size-4 cursor-pointer" onClick={switchSettingVisible} />
</div> </div>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit, onInvalid)}> <form onSubmit={form.handleSubmit(onSubmit, onInvalid)}>
<section className="space-y-6 overflow-auto max-h-[85vh] pr-4"> <section className="space-y-6 overflow-auto max-h-[82vh] pr-4">
<ChatBasicSetting></ChatBasicSetting> <ChatBasicSetting></ChatBasicSetting>
<Separator /> <Separator />
<ChatPromptEngine></ChatPromptEngine> <ChatPromptEngine></ChatPromptEngine>
<Separator /> <Separator />
<ChatModelSettings></ChatModelSettings> <ChatModelSettings></ChatModelSettings>
</section> </section>
<ButtonLoading <div className="space-x-5 text-right">
className="w-full my-4" <Button variant={'outline'} onClick={switchSettingVisible}>
type="submit" {t('chat.cancel')}
loading={loading} </Button>
> <ButtonLoading className=" my-4" type="submit" loading={loading}>
Update {t('common.save')}
</ButtonLoading> </ButtonLoading>
</div>
</form> </form>
</Form> </Form>
</section> </section>

View File

@ -23,7 +23,7 @@ interface IProps {
export function SingleChatBox({ controller }: IProps) { export function SingleChatBox({ controller }: IProps) {
const { const {
value, value,
// scrollRef, scrollRef,
messageContainerRef, messageContainerRef,
sendLoading, sendLoading,
derivedMessages, derivedMessages,
@ -47,7 +47,7 @@ export function SingleChatBox({ controller }: IProps) {
return ( return (
<section className="flex flex-col p-5 h-full"> <section className="flex flex-col p-5 h-full">
<div ref={messageContainerRef} className="flex-1 overflow-auto min-h-0"> <div ref={messageContainerRef} className="flex-1 overflow-auto min-h-0">
<div className="w-full"> <div className="w-full pr-5">
{derivedMessages?.map((message, i) => { {derivedMessages?.map((message, i) => {
return ( return (
<MessageItem <MessageItem
@ -77,7 +77,7 @@ export function SingleChatBox({ controller }: IProps) {
); );
})} })}
</div> </div>
{/* <div ref={scrollRef} /> */} <div ref={scrollRef} />
</div> </div>
<NextMessageInput <NextMessageInput
disabled={disabled} disabled={disabled}

View File

@ -100,7 +100,7 @@ export default function Chat() {
{t('common.embedIntoSite')} {t('common.embedIntoSite')}
</Button> </Button>
</PageHeader> </PageHeader>
<div className="flex flex-1 min-h-0"> <div className="flex flex-1 min-h-0 pb-9">
<Sessions <Sessions
hasSingleChatBox={hasSingleChatBox} hasSingleChatBox={hasSingleChatBox}
handleConversationCardClick={handleConversationCardClick} handleConversationCardClick={handleConversationCardClick}

View File

@ -11,6 +11,7 @@ import {
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { PanelLeftClose, PanelRightClose, Plus } from 'lucide-react'; import { PanelLeftClose, PanelRightClose, Plus } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useHandleClickConversationCard } from '../hooks/use-click-card'; import { useHandleClickConversationCard } from '../hooks/use-click-card';
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list'; import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
import { ConversationDropdown } from './conversation-dropdown'; import { ConversationDropdown } from './conversation-dropdown';
@ -24,6 +25,7 @@ export function Sessions({
handleConversationCardClick, handleConversationCardClick,
switchSettingVisible, switchSettingVisible,
}: SessionProps) { }: SessionProps) {
const { t } = useTranslation();
const { const {
list: conversationList, list: conversationList,
addTemporaryConversation, addTemporaryConversation,
@ -102,8 +104,9 @@ export function Sessions({
className="w-full" className="w-full"
onClick={switchSettingVisible} onClick={switchSettingVisible}
disabled={!hasSingleChatBox} disabled={!hasSingleChatBox}
variant={'outline'}
> >
Chat Settings {t('chat.chatSetting')}
</Button> </Button>
</div> </div>
</section> </section>