Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VITE_TOKEN=pat_4KAirQsqDKWsZrQx2e0CJ2Lo2t0KTh4WbzYlylGGOqeSoXvifbnZn1GbkUDtECII
VITE_BOT_ID=7470493448832172049
VITE_TOKEN=pat_oQFI8coeYjMX5gpokCM7hlaCpokoybY7ZSGKns3ttI7R4qwxPhBIvuPLZj7zTUh7
VITE_BOT_ID=7477148643439968264
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# 项目简介
一个基于coze-api的AI对话LLM组件,支持流式输出,支持图片、视频、音频、文件等类型消息的传输,记录历史对话
一个基于coze-api的AI对话LLM组件
👏🏻支持流式输出ai对话并展示markdown格式
✍🏻支持图片、视频、音频、文件等类型消息的传输
⭐️记录历史对话折叠框
✨切换黑白主题
🧉支持H5端适配
使用时需要自行配置bot ID与api key,调整于根目录.env文件中
后台官网https://api.coze.com
后台官网:https://api.coze.com
在线预览:https://charlotte21110.github.io/byteDanceLLM/

# node版本
Expand Down Expand Up @@ -45,4 +50,6 @@ git pull origin develop // 拉取最新代码
git add xxx.js // 示例添加文件
pnpm commit // 提交
git push origin develop // 推送到远程
```
```
# 界面预览
![显示界面](./src/assets/picture_white.png)
148 changes: 118 additions & 30 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,143 @@
import { CozeAPI, COZE_COM_BASE_URL, ChatEventType, RoleType, ContentType } from '@coze/api';
import {
CozeAPI,
COZE_COM_BASE_URL,
ChatEventType,
RoleType,
ContentType,
} from '@coze/api';
import axios from 'axios';
import { AdditionalMessage } from '../types/additionalMessage';

export interface Iquery {
query: string;
query: string;
}
const token = import.meta.env?.VITE_TOKEN || window.__RUNTIME_CONFIG__?.REACT_APP_TOKEN || '';
const botId = import.meta.env?.VITE_BOT_ID || window.__RUNTIME_CONFIG__?.REACT_APP_BOT_ID || '';
const token =
import.meta.env?.VITE_TOKEN ||
window.__RUNTIME_CONFIG__?.REACT_APP_TOKEN ||
'';
const botId =
import.meta.env?.VITE_BOT_ID ||
window.__RUNTIME_CONFIG__?.REACT_APP_BOT_ID ||
'';

const client = new CozeAPI({
token: token,
allowPersonalAccessTokenInBrowser: true,
baseURL: '/api',
headers: new Headers({
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
Authorization: `Bearer ${token}`,
}),
});
export const fetchAIResponse = async (
input: string,
input: string,
additionalMessages: AdditionalMessage[],
onData: (data: string) => void,
messageType: string,
signal?: AbortSignal,
signal?: AbortSignal
): Promise<void> => {
const contentTypeMap: { [key: string] : string} = {
const contentTypeMap: { [key: string]: string } = {
text: 'text',
image: 'object_string',
}
};
const contentType = contentTypeMap[messageType] || 'text';

try {
const stream = await client.chat.stream({
bot_id: botId,
auto_save_history: true,
user_id: '123',
additional_messages: [
...additionalMessages,
{
role: RoleType.User,
content: input,
content_type: contentType as ContentType,
},
],
}, { signal });
// 如果不使用mock数据,就打开这一段代码,底下那段注释掉

/** 调api开始 */

const stream = await client.chat.stream(
{
bot_id: botId,
auto_save_history: true,
user_id: '123',
additional_messages: [
...additionalMessages,
{
role: RoleType.User,
content: input,
content_type: contentType as ContentType,
},
],
},
{ signal }
);

/** 调api结束 */

/** 读mock数据开始(还有一点问题服了) */

// const mockResponse = await fetch('/src/mock/response.txt');
// const text = await mockResponse.text();
// const events = text.split('\n').filter(line => line.trim());

// // 创建一个异步迭代器来模拟流式响应
// const stream = {
// [Symbol.asyncIterator]() {
// let index = 0;
// return {
// async next(): Promise<IteratorResult<{ event: string; data: { content: string } }, void>> {
// if (index >= events.length) {
// return { done: true, value: undefined };
// }

// const event = events[index];
// index++;

// if (event.startsWith('event:')) {
// const eventType = event.split(':')[1].trim();
// const dataLine = events[index];
// index++;

// if (dataLine && dataLine.startsWith('data:')) {
// const data = JSON.parse(dataLine.slice(5));
// return {
// value: {
// event: eventType,
// data: data
// },
// done: false
// };
// }
// }

// return this.next();
// }
// };
// }

// };

/** 读mock数据结束 */
const followUps: string[] = [];
for await (const part of stream) {
if (signal?.aborted) {
break;
}
if (part.event === ChatEventType.CONVERSATION_MESSAGE_DELTA) {
onData(part.data.content);
}
if (part.event === ChatEventType.CONVERSATION_MESSAGE_COMPLETED) {
// 拿回复结束的时候的时候紧接着的建议
if ('type' in part.data && part.data.type === 'follow_up') {
onData(
JSON.stringify({
type: 'suggestions',
suggestions: [...followUps, part.data.content],
})
);
followUps.push(part.data.content);
}
}
}
} catch (error: unknown) {
if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
if (
error &&
typeof error === 'object' &&
'name' in error &&
error.name === 'AbortError'
) {
onData('\n[已停止回复]');
} else {
console.error('Error fetching AI response:', error);
Expand All @@ -67,19 +151,23 @@ export const uploadFile = async (file: File) => {
formData.append('file', file);

try {
const response = await axios.post(`${COZE_COM_BASE_URL}/v1/files/upload`, formData, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'multipart/form-data',
const response = await axios.post(
`${COZE_COM_BASE_URL}/v1/files/upload`,
formData,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'multipart/form-data',
},
}
});
);
if (response.data.code === 0) {
return response.data.data;
} else {
throw new Error(response.data.data)
throw new Error(response.data.data);
}
} catch (error) {
console.error('上传文件发生错误:', error);
throw error;
}
}
};
Binary file added src/assets/picture_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 4 additions & 31 deletions src/components/AIanswer/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import hljs from '../../utils/highlightConfig';
import './index.css';

interface CodeBlockProps {
language: string;
Expand All @@ -21,39 +22,11 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ language, value }) => {

return (
<div style={{ position: 'relative' }}>
<button
onClick={handleCopy}
style={{
position: 'absolute',
right: '10px',
top: '10px',
background: 'none',
border: 'none',
cursor: 'pointer',
color: 'var(--button-text)',
borderRadius: '5px',
padding: '5px 10px',
backgroundColor: 'var(--button-bg)',
}}
>
<button onClick={handleCopy} className="copy-button">
{copyMessage || 'Copy'}
</button>
<pre
style={{
backgroundColor: '#1E1E1E',
padding: '1em',
borderRadius: '6px',
overflow: 'auto',
}}
>
<code
dangerouslySetInnerHTML={{ __html: highlighted }}
style={{
fontFamily: 'Consolas, Monaco, monospace',
backgroundColor: 'black',
color: '#fff',
}}
/>
<pre className="code-pre">
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
</pre>
</div>
);
Expand Down
48 changes: 48 additions & 0 deletions src/components/AIanswer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,51 @@
.chat-ai-answer a {
color: var(--accent-color);
}

.chat-ai-answer-suggestion-box {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 15px;
margin-left: 10px;
flex-wrap: wrap;
}

.chat-ai-answer-suggestion-item {
width: fit-content;
display: inline-block;
padding: 8px 15px;
background-color: var(--button-bg);
color: var(--button-text);
border-radius: 15px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
border: 1px solid var(--border-color);
}

.copy-button {
position: absolute;
right: 10px;
top: 10px;
background: none;
border: none;
cursor: pointer;
color: var(--accent-color);
border-radius: 5px;
padding: 5px 10px;
background-color: var(--button-bg);
}

.code-pre {
background-color: #1e1e1e;
padding: 1em;
border-radius: 6px;
overflow: auto;
}

.code-pre code {
font-family: Consolas, Monaco, monospace;
background-color: black;
color: #fff;
}
Loading