123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- import React, { useState, useEffect, useRef } from 'react';
- import { Button, Form, Spinner, Card, Container, Row, Col, Alert } from 'react-bootstrap';
- import { toast } from 'react-toastify';
- import {
- textToSpeech,
- speechToText,
- translateText,
- summarizeText,
- cozeService
- } from '../utils/cozeExample';
- /**
- * Coze工作流演示组件
- */
- const CozeWorkflowDemo = () => {
- // 状态管理
- const [loading, setLoading] = useState(false);
- const [audioUrl, setAudioUrl] = useState(null);
- const [inputText, setInputText] = useState('');
- const [outputText, setOutputText] = useState('');
- const [targetLanguage, setTargetLanguage] = useState('en');
- const [audioFile, setAudioFile] = useState(null);
- const [errorMessage, setErrorMessage] = useState('');
- const [workflowId, setWorkflowId] = useState('');
- const [activeTab, setActiveTab] = useState('tts'); // tts, stt, translate, summarize
-
- const audioRef = useRef(null);
-
- useEffect(() => {
- // 在组件卸载时清理音频URL
- return () => {
- if (audioUrl) {
- URL.revokeObjectURL(audioUrl);
- }
- };
- }, [audioUrl]);
-
- // 处理文本转语音
- const handleTextToSpeech = async () => {
- if (!inputText.trim()) {
- setErrorMessage('请输入要转换的文本');
- return;
- }
-
- setLoading(true);
- setErrorMessage('');
-
- try {
- if (audioUrl) {
- URL.revokeObjectURL(audioUrl);
- setAudioUrl(null);
- }
-
- const response = await textToSpeech(inputText, {
- voice: 'female', // 可以是不同的声音选项
- speed: 1.0 // 语速
- });
-
- // 处理流式响应
- if (response && response.audio) {
- // 假设返回的是Base64格式的音频数据
- const blob = base64ToBlob(response.audio, 'audio/mp3');
- const url = URL.createObjectURL(blob);
- setAudioUrl(url);
-
- // 设置输出文本为成功消息
- setOutputText('语音合成成功,可点击播放按钮收听');
- } else {
- throw new Error('未接收到有效的音频数据');
- }
- } catch (error) {
- console.error('文本转语音失败:', error);
- setErrorMessage(`文本转语音失败: ${error.message}`);
- toast.error(`文本转语音失败: ${error.message}`);
- } finally {
- setLoading(false);
- }
- };
-
- // 处理语音转文本
- const handleSpeechToText = async () => {
- if (!audioFile) {
- setErrorMessage('请先选择音频文件');
- return;
- }
-
- setLoading(true);
- setErrorMessage('');
-
- try {
- const response = await speechToText(audioFile);
-
- if (response && response.text) {
- setOutputText(response.text);
- } else {
- throw new Error('未接收到有效的识别结果');
- }
- } catch (error) {
- console.error('语音识别失败:', error);
- setErrorMessage(`语音识别失败: ${error.message}`);
- toast.error(`语音识别失败: ${error.message}`);
- } finally {
- setLoading(false);
- }
- };
-
- // 处理文本翻译
- const handleTextTranslation = async () => {
- if (!inputText.trim()) {
- setErrorMessage('请输入要翻译的文本');
- return;
- }
-
- setLoading(true);
- setErrorMessage('');
-
- try {
- const response = await translateText(inputText, targetLanguage);
-
- if (response && response.translated_text) {
- setOutputText(response.translated_text);
- } else {
- throw new Error('未接收到有效的翻译结果');
- }
- } catch (error) {
- console.error('文本翻译失败:', error);
- setErrorMessage(`文本翻译失败: ${error.message}`);
- toast.error(`文本翻译失败: ${error.message}`);
- } finally {
- setLoading(false);
- }
- };
-
- // 处理文本摘要
- const handleTextSummarization = async () => {
- if (!inputText.trim()) {
- setErrorMessage('请输入要摘要的文本');
- return;
- }
-
- setLoading(true);
- setErrorMessage('');
-
- try {
- const response = await summarizeText(inputText);
-
- if (response && response.summary) {
- setOutputText(response.summary);
- } else {
- throw new Error('未接收到有效的摘要结果');
- }
- } catch (error) {
- console.error('文本摘要失败:', error);
- setErrorMessage(`文本摘要失败: ${error.message}`);
- toast.error(`文本摘要失败: ${error.message}`);
- } finally {
- setLoading(false);
- }
- };
-
- // 直接调用工作流
- const handleRunWorkflow = async () => {
- if (!workflowId.trim()) {
- setErrorMessage('请输入工作流ID');
- return;
- }
-
- if (!inputText.trim()) {
- setErrorMessage('请输入参数');
- return;
- }
-
- setLoading(true);
- setErrorMessage('');
-
- try {
- // 解析JSON参数
- let parameters = {};
- try {
- parameters = JSON.parse(inputText);
- } catch (e) {
- // 如果不是有效的JSON,则作为text参数传递
- parameters = { text: inputText };
- }
-
- const response = await cozeService.runWorkflow(workflowId, parameters);
-
- // 在实际应用中,您可能需要根据工作流的不同输出格式进行不同的处理
- setOutputText(JSON.stringify(response, null, 2));
- } catch (error) {
- console.error('运行工作流失败:', error);
- setErrorMessage(`运行工作流失败: ${error.message}`);
- toast.error(`运行工作流失败: ${error.message}`);
- } finally {
- setLoading(false);
- }
- };
-
- // 处理音频文件选择
- const handleFileChange = (e) => {
- const file = e.target.files[0];
- if (file) {
- setAudioFile(file);
- // 创建本地URL以便预览
- if (audioUrl) {
- URL.revokeObjectURL(audioUrl);
- }
- const url = URL.createObjectURL(file);
- setAudioUrl(url);
- }
- };
-
- // 辅助函数:将Base64字符串转换为Blob对象
- const base64ToBlob = (base64, mimeType) => {
- const byteString = atob(base64);
- const ab = new ArrayBuffer(byteString.length);
- const ia = new Uint8Array(ab);
-
- for (let i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
-
- return new Blob([ab], { type: mimeType });
- };
-
- // 渲染不同的功能面板
- const renderPanel = () => {
- switch (activeTab) {
- case 'tts':
- return (
- <>
- <Form.Group className="mb-3">
- <Form.Label>输入文本</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={inputText}
- onChange={(e) => setInputText(e.target.value)}
- placeholder="请输入要转换为语音的文本..."
- />
- </Form.Group>
-
- <Button
- variant="primary"
- onClick={handleTextToSpeech}
- disabled={loading || !inputText.trim()}
- >
- {loading ? (
- <>
- <Spinner size="sm" animation="border" className="me-2" />
- 处理中...
- </>
- ) : '转换为语音'}
- </Button>
-
- {audioUrl && (
- <div className="mt-3">
- <audio ref={audioRef} src={audioUrl} controls className="w-100" />
- </div>
- )}
- </>
- );
-
- case 'stt':
- return (
- <>
- <Form.Group className="mb-3">
- <Form.Label>选择音频文件</Form.Label>
- <Form.Control
- type="file"
- accept="audio/*"
- onChange={handleFileChange}
- />
- <Form.Text className="text-muted">
- 支持常见的音频格式,如MP3、WAV等
- </Form.Text>
- </Form.Group>
-
- {audioUrl && (
- <div className="mb-3">
- <audio src={audioUrl} controls className="w-100" />
- </div>
- )}
-
- <Button
- variant="primary"
- onClick={handleSpeechToText}
- disabled={loading || !audioFile}
- >
- {loading ? (
- <>
- <Spinner size="sm" animation="border" className="me-2" />
- 识别中...
- </>
- ) : '识别语音'}
- </Button>
-
- {outputText && (
- <div className="mt-3">
- <Form.Group>
- <Form.Label>识别结果</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={outputText}
- readOnly
- />
- </Form.Group>
- </div>
- )}
- </>
- );
-
- case 'translate':
- return (
- <>
- <Form.Group className="mb-3">
- <Form.Label>输入文本</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={inputText}
- onChange={(e) => setInputText(e.target.value)}
- placeholder="请输入要翻译的文本..."
- />
- </Form.Group>
-
- <Form.Group className="mb-3">
- <Form.Label>目标语言</Form.Label>
- <Form.Select
- value={targetLanguage}
- onChange={(e) => setTargetLanguage(e.target.value)}
- >
- <option value="en">英语</option>
- <option value="zh">中文</option>
- <option value="ja">日语</option>
- <option value="ko">韩语</option>
- <option value="fr">法语</option>
- <option value="de">德语</option>
- <option value="es">西班牙语</option>
- <option value="ru">俄语</option>
- </Form.Select>
- </Form.Group>
-
- <Button
- variant="primary"
- onClick={handleTextTranslation}
- disabled={loading || !inputText.trim()}
- >
- {loading ? (
- <>
- <Spinner size="sm" animation="border" className="me-2" />
- 翻译中...
- </>
- ) : '翻译文本'}
- </Button>
-
- {outputText && (
- <div className="mt-3">
- <Form.Group>
- <Form.Label>翻译结果</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={outputText}
- readOnly
- />
- </Form.Group>
- </div>
- )}
- </>
- );
-
- case 'summarize':
- return (
- <>
- <Form.Group className="mb-3">
- <Form.Label>输入文本</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={inputText}
- onChange={(e) => setInputText(e.target.value)}
- placeholder="请输入要摘要的文本..."
- />
- </Form.Group>
-
- <Button
- variant="primary"
- onClick={handleTextSummarization}
- disabled={loading || !inputText.trim()}
- >
- {loading ? (
- <>
- <Spinner size="sm" animation="border" className="me-2" />
- 摘要中...
- </>
- ) : '生成摘要'}
- </Button>
-
- {outputText && (
- <div className="mt-3">
- <Form.Group>
- <Form.Label>摘要结果</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={outputText}
- readOnly
- />
- </Form.Group>
- </div>
- )}
- </>
- );
-
- case 'custom':
- return (
- <>
- <Form.Group className="mb-3">
- <Form.Label>工作流ID</Form.Label>
- <Form.Control
- type="text"
- value={workflowId}
- onChange={(e) => setWorkflowId(e.target.value)}
- placeholder="请输入Coze工作流ID..."
- />
- </Form.Group>
-
- <Form.Group className="mb-3">
- <Form.Label>参数 (JSON格式或文本)</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={inputText}
- onChange={(e) => setInputText(e.target.value)}
- placeholder='{"key": "value"} 或直接输入文本'
- />
- </Form.Group>
-
- <Button
- variant="primary"
- onClick={handleRunWorkflow}
- disabled={loading || !workflowId.trim() || !inputText.trim()}
- >
- {loading ? (
- <>
- <Spinner size="sm" animation="border" className="me-2" />
- 处理中...
- </>
- ) : '运行工作流'}
- </Button>
-
- {outputText && (
- <div className="mt-3">
- <Form.Group>
- <Form.Label>输出结果</Form.Label>
- <Form.Control
- as="textarea"
- rows={5}
- value={outputText}
- readOnly
- />
- </Form.Group>
- </div>
- )}
- </>
- );
-
- default:
- return null;
- }
- };
-
- return (
- <Container className="my-4">
- <h2 className="mb-4 text-center">Coze工作流演示</h2>
-
- {errorMessage && (
- <Alert variant="danger" className="mb-3" onClose={() => setErrorMessage('')} dismissible>
- {errorMessage}
- </Alert>
- )}
-
- <Card>
- <Card.Header>
- <Nav variant="tabs" className="flex-row">
- <Nav.Item>
- <Nav.Link
- active={activeTab === 'tts'}
- onClick={() => setActiveTab('tts')}
- >
- 文本转语音
- </Nav.Link>
- </Nav.Item>
- <Nav.Item>
- <Nav.Link
- active={activeTab === 'stt'}
- onClick={() => setActiveTab('stt')}
- >
- 语音识别
- </Nav.Link>
- </Nav.Item>
- <Nav.Item>
- <Nav.Link
- active={activeTab === 'translate'}
- onClick={() => setActiveTab('translate')}
- >
- 文本翻译
- </Nav.Link>
- </Nav.Item>
- <Nav.Item>
- <Nav.Link
- active={activeTab === 'summarize'}
- onClick={() => setActiveTab('summarize')}
- >
- 文本摘要
- </Nav.Link>
- </Nav.Item>
- <Nav.Item>
- <Nav.Link
- active={activeTab === 'custom'}
- onClick={() => setActiveTab('custom')}
- >
- 自定义工作流
- </Nav.Link>
- </Nav.Item>
- </Nav>
- </Card.Header>
- <Card.Body>
- {renderPanel()}
- </Card.Body>
- </Card>
- </Container>
- );
- };
- export default CozeWorkflowDemo;
|