Chatbox
Chat and conversational UI, which can be used to interface models similarly to the OpenAI API.
Features
1. Core functions
- Support real-time conversation interaction
- Support streaming response (Stream) and normal request modes
- Maintain conversation history and support context association
- Support custom message rendering and formatting
- Customization capabilities
2. Interface features
- Support floating bubble mode (bubble)
- Adaptive input box (autoSize)
- Support typewriter effect (TypingEffect)
- Automatically scroll to the latest message
- Display message timestamp
- Support thinking state display (thinking state)
- Support loading status and progress display
3. Request control
- Support interrupt request (abort)
- Support pause/resume streaming response
- Request error handling and retry mechanism
- Custom request header and request body configuration
4. External method (accessed through contentRef)
- chatOpen(): Open dialog box
- chatClose(): Close dialog box
- clearData(): Clear message and history
- sendMsg(): send message
- getHistory(): get conversation history
- trimHistory(length?: number): trim conversation history
- setHistory(messages?: Record<string, any>[]): Set list of conversation history
- setVal(v: string): set input value
- getContextData(): Get current context data
- setContextData(v: Record<string, any>): set context data
- getMessages(): Get messages list
- setMessages(v: Record<string, any>[]): Set messages list
- getCustomMethods(): Get all available custom method names
- executeCustomMethod(methodName: string, ...args: any[]): A function that executes a custom method5. Speech recognition
- A separate microphone button will appear next to the send button
- Clicking the microphone button will start recording
- While recording, the microphone button shows an animated recording indicator
- Clicking the button again will stop recording and automatically recognize the speech
- The recognized text will be automatically filled into the input field
- The original send button remains unchanged and works as before
- You can then send the message normally or continue editing
General
Use the chat area alone, without bubbles + read remote parameter configuration
Open Chat
Set Default Value
Random Merged Text (set Context)
Show Code
import React, { useState, useEffect, useRef } from "react";
import axios from 'axios';
import { parse as markedParse } from "marked";
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
const aichatRef = useRef<any>(null);
const [aiConfig, setAiConfig] = useState<any>({});
const [contextData, setContextData] = useState<Record<string, any>>({});
const [customRequest, setCustomRequest] = useState<((
message: string,
config: {
requestBody: any,
apiUrl: string,
headers: any
},
customMethods: {
name: string;
func: (...args: any[]) => any;
}[],
conversationHistory: {
sender: string;
timestamp: string;
content: string;
tag: string;
}[],
) => Promise<{
content: string | Response | null;
isStream: boolean;
}>
) | undefined>();
const customMethods: {
name: string;
func: (...args: any[]) => any;
}[] = [
{
name: "sayHello",
func: (name: string) => {
console.log(`Hello, ${name}!`);
}
},
{
name: "updateUI",
func: (data: any) => {
console.log('Updating UI with:', data);
// setSomeState(data);
}
},
{
name: "getUI",
func: () => {
// getSomeState;
}
},
/* Usage: {"label":"Drop-Down","value":"lab","onClick":"return method.executeCustomMethod('getMenuList')","isSelect":true,"dynamicOptions":true}*/
{
name: "getMenuList",
func: async () =>{
const newOpts = [{
"name": "Option 1",
"id": "opt-1"
},
{
"name": "Option 2",
"id": "opt-2"
}].map((v, i) =>{
const obj = {};
obj["onSelect__" + (i + 2)] = v.name + " {#} " + v.id + " {#} method.executeCustomMethod('sayHello', '" + v.name + "');";
return obj;
});
newOpts.unshift({
"onSelect__1": "None {#} cancel {#} method.setContextData({});"
});
return newOpts;
}
}
];
async function getAiConfig() {
// const res = await axios({
// method: 'post',
// url: `http://yourapi`
// });
return {
"show": true,
"verbose": true,
"reasoningSwitchLabel": "Idea",
"maxHistoryLength": 10,
"newChatButton": "{\"label\":\"<svg width='16' height='16' viewBox='0 0 24 24'><path fill='currentColor' d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/></svg> New Chat\",\"value\":\"new\",\"onClick\":\"method.setVal(''); method.clearData();\"}",
"toolkitButtons": "[{\"label\":\"<svg fill='currentColor' width='12' height='12' viewBox='0 0 24 24'><path d='M19 2H5c-1.103 0-2 .897-2 2v12c0 1.103.897 2 2 2h3.586L12 21.414 15.414 18H19c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zm0 14h-4.414L12 18.586 9.414 16H5V4h14v12z'/></svg> Brief\",\"value\":\"brief\",\"onClick\":\"console.log(button); console.log(isActive, method.getContextData()); if(isActive) { method.setContextData({systemPrompt: 'Please keep your answer within 77 words',mergedText: method.getContextData().mergedText}); } else { method.setContextData({mergedText: method.getContextData().mergedText}); }\"},{\"label\":\"<svg width='12' height='12' viewBox='0 0 32 32'><path fill='currentColor' d='M26.4,23.9l-6.9-9.4c-0.4-0.5-0.6-1.1-0.6-1.8V8c1.1,0,2-0.9,2-2V4c0-1.1-0.9-2-2-2h-6c-1.1,0-2,0.9-2,2v2c0,1.1,0.9,2,2,2v4.7c0,0.6-0.2,1.2-0.6,1.8l-6.9,9.4C5.2,24.4,5,25,5,25.7V27c0,1.7,1.3,3,3,3h16c1.7,0,3-1.3,3-3v-1.3C27,25,26.8,24.4,26.4,23.9zM14,15.6c0.6-0.9,1-1.9,1-2.9V8c0-1.1-0.9-2-2-2V4h6v2c-1.1,0-2,0.9-2,2v4.7c0,1.1,0.3,2.1,1,2.9l4.6,6.4H9.4L14,15.6z'/></svg> Drop-Down\",\"value\":\"lab\",\"onClick\":\"method.executeCustomMethod('sayHello', 'John');\",\"isSelect\":true, \"onSelect__1\":\"None {#} cancel {#} method.setContextData({});method.executeCustomMethod('sayHello', 'John');\",\"onSelect__2\":\"Create image {#} create-img-a {#} method.setContextData({systemPrompt:'please create a image'});\"}]",
"apiUrl": "{baseUrl}/v1/chat/completions",
"requestBody": "{'model':'{model}','messages':[{'role':'user','content':'{message}'}],'stream': true}",
"responseBody": "data.choices.0.delta.content",
"headerConfig": "{'Content-Type': 'application/json'}",
"aiLabel": "AI Helper",
"coreIcon": "<svg height='18px' viewBox='0 0 55.0227 41.3595'><path fill='#fff' d='M55.6128,3.4712c-.5953-.2917-.8517.2642-1.1998.5466-.1191.0911-.2198.2095-.3206.3188-.8701.9292-1.8867,1.5398-3.2148,1.4668-1.9417-.1094-3.5995.5012-5.065,1.9863-.3114-1.8313-1.3463-2.9248-2.9217-3.6262-.8242-.3645-1.6577-.729-2.2348-1.5217-.403-.5647-.5129-1.1934-.7144-1.813-.1283-.3735-.2565-.7563-.687-.8201-.4671-.0728-.6503.3188-.8335.647-.7327,1.3394-1.0166,2.8154-.9892,4.3096.0641,3.3621,1.4838,6.0406,4.3047,7.9449.3206.2187.403.4372.3023.7563-.1924.656-.4214,1.2937-.6228,1.9497-.1283.4192-.3207.5103-.7694.3279-1.5479-.6467-2.8852-1.6035-4.0667-2.7605-2.0058-1.9407-3.8193-4.0818-6.0815-5.7583-.5312-.3918-1.0625-.7561-1.6121-1.1025-2.3081-2.2412.3023-4.0818.9068-4.3003.6319-.2278.2198-1.0115-1.8227-1.0022-2.0425.009-3.9109.6924-6.2922,1.6035-.348.1367-.7145.2368-1.09.3188-2.1615-.4099-4.4055-.5012-6.7502-.2368-4.4147.4919-7.9408,2.5784-10.5328,6.1409C.1914,13.1289-.5413,17.9941.3563,23.0691c.9434,5.3481,3.6727,9.7761,7.8676,13.2385,4.3506,3.5896,9.3606,5.3481,15.0758,5.011,3.4713-.2004,7.3364-.665,11.6961-4.355,1.099.5467,2.2531.7652,4.1674.9292,1.4746.1367,2.8943-.0728,3.9933-.3005,1.7219-.3645,1.6029-1.959.9801-2.2505-5.0466-2.3506-3.9385-1.394-4.9459-2.1685,2.5645-3.0339,6.4297-6.1865,7.9409-16.4001.119-.8108.0183-1.3211,0-1.9771-.0092-.4008.0824-.5556.5404-.6013,1.2639-.1458,2.4912-.4919,3.6178-1.1115,3.2698-1.7857,4.5886-4.7195,4.9-8.2364.0459-.5376-.0091-1.0935-.577-1.3757ZM27.119,35.123c-4.8909-3.8447-7.263-5.1113-8.2431-5.0566-.9159.0547-.751,1.1025-.5496,1.7859.2107.6741.4855,1.1389.8701,1.731.2656.3918.4489.9748-.2655,1.4123-1.5754.9749-4.314-.3281-4.4423-.3918-3.1872-1.877-5.8525-4.3553-7.7302-7.7444-1.8135-3.262-2.8667-6.7605-3.0408-10.4961-.0458-.9019.2198-1.221,1.1174-1.3848,1.1815-.2187,2.3997-.2644,3.5812-.0913,4.9918.729,9.2415,2.9612,12.8043,6.4963,2.0333,2.0135,3.572,4.419,5.1566,6.7696,1.6852,2.4963,3.4987,4.8745,5.8068,6.8242.8151.6833,1.4654,1.2026,2.0882,1.5854-1.8775.2095-5.01.2552-7.1532-1.4397ZM29.4637,20.0442c0-.4009.3206-.7197.7237-.7197.0916,0,.174.018.2473.0453.1008.0366.1924.0913.2656.1731.1283.1277.2015.3098.2015.5012,0,.4009-.3205.7197-.7234.7197s-.7145-.3188-.7145-.7197ZM36.7452,23.7798c-.4671.1914-.9342.3552-1.383.3735-.6961.0364-1.4563-.2461-1.8684-.5923-.6411-.5376-1.0991-.8381-1.2914-1.7766-.0825-.4009-.0367-1.0205.0367-1.3757.1648-.7654-.0184-1.2573-.5587-1.7039-.4397-.3645-.9984-.4646-1.6121-.4646-.229,0-.4395-.1003-.5953-.1823-.2565-.1275-.467-.4464-.2656-.8382.0641-.1274.3756-.4373.4489-.4919.8335-.4739,1.7952-.3189,2.6836.0364.8244.3371,1.4472.9567,2.3447,1.8313.9159,1.0568,1.0807,1.3486,1.6028,2.1411.4123.6196.7878,1.2573,1.0442,1.9863.1557.4556-.0458.8291-.5862,1.0569Z' /></svg>",
"modelName1": "llama3.2-vision",
"modelName2": "",
"modelName3": "",
"modelName4": "",
"modelName5": "",
"APIKey1": "",
"APIKey2": "",
"APIKey3": "",
"APIKey4": "",
"APIKey5": "",
"params1": "",
"params2": "",
"params3": "",
"params4": "",
"params5": "",
"noDataPlaceholder": "<div class='text-center'>Chat to AI <hr class='p-0 m-0 mt-2' /><small>Disclaimer: The dialogue content is generated by AI and is for reference only</small></div>",
"placeholder": "Enter your question...",
"sendLoadingLabel": "Thinking",
"bubbleLabel": "<svg width='50px' height='50px' viewBox='0 0 24 24' fill='none'><path d='M20 7L4 7' stroke='#1C274C' stroke-width='1.5' stroke-linecap='round' /><path d='M15 12L4 12' stroke='#1C274C' stroke-width='1.5' stroke-linecap='round' /><path d='M9 17H4' stroke='#1C274C' stroke-width='1.5' stroke-linecap='round' /></svg>",
"sendLabel": "<svg viewBox='0 0 512 512' width='15px'><path d='M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480l0-83.6c0-4 1.5-7.8 4.2-10.8L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z' /></svg>",
"questionName": "{icon} Me",
"answerName": "{icon} AI",
"questionNameIcon": "<svg width='15px' height='15px' viewBox='0 0 1024 1024'><path d='M512.524936 0.008332C229.809447 0.008332 0.587428 229.230351 0.587428 511.94584c0 174.13291 86.976883 327.943301 219.835665 420.423679 25.99266-90.522283 91.847122-158.622304 181.419521-189.5102a331.426209 331.426209 0 0 1 8.557288-2.807991c1.3665-0.429114 2.716335-0.870727 4.091168-1.283176a323.077229 323.077229 0 0 1 12.802603-3.562066l0.254136-0.058326a327.101737 327.101737 0 0 1 12.910924-2.978803c1.383164-0.291631 2.778827-0.554099 4.166158-0.829065a341.275007 341.275007 0 0 1 14.160772-2.512194 346.915985 346.915985 0 0 1 9.682151-1.354001c1.424826-0.179145 2.84132-0.37912 4.274478-0.5416a355.210806 355.210806 0 0 1 14.039953-1.354002h18.539404c1.891436 0.112486 3.778705 0.112486 5.670141 0h1.533146c-133.117084 0-241.028911-107.911827-241.028911-241.028911 0-1.266512 0.074991-2.516359 0.095822-3.774539-1.31234-152.377233 141.516058-244.178526 241.17056-237.37519 102.270849-6.965816 250.006982 89.397421 241.383034 248.353017-0.254136 0.158314-0.529102 0.279133-0.783237 0.43328-4.04534 129.575849-110.286537 233.392343-240.841434 233.392343h25.980162c4.724423 0.354123 9.394687 0.820733 14.035786 1.354002 1.437325 0.16248 2.857984 0.362456 4.282811 0.5416a359.614435 359.614435 0 0 1 14.402408 2.124741c3.153782 0.533268 6.2784 1.11653 9.386355 1.733121 1.412328 0.279133 2.837154 0.545767 4.236982 0.841564a327.626673 327.626673 0 0 1 12.59013 2.903813c0.279133 0.070825 0.566598 0.137483 0.84573 0.212474a318.461125 318.461125 0 0 1 12.42765 3.457911c1.470654 0.437447 2.912145 0.912389 4.366134 1.3665 2.753831 0.866561 5.490996 1.758119 8.207331 2.691338 89.605728 30.850401 155.497685 98.921258 181.536173 189.426876 132.742129-92.497042 219.635689-246.236608 219.635689-420.286195 0.008332-282.719655-229.20952-511.937508-511.929175-511.937508z' fill='#A6D4AE' /><path d='M753.36637 492.185752c-70.674706 43.161398-216.244436-31.629472-240.603963-134.708556-24.326197 103.449872-170.645836 178.444884-240.912258 134.271109a243.574433 243.574433 0 0 1-0.258302-10.977826c-0.020831 1.262346-0.095822 2.512193-0.095822 3.774539 0 133.117084 107.911827 241.028911 241.028911 241.028911 130.554896 0.004166 236.796094-103.812328 240.841434-233.388177z' fill='#FCE9EA' /><path d='M623.290581 742.805159c0.916555 0.316628 1.824777 0.645755 2.733 0.966549a119.589568 119.589568 0 0 1-10.448724 22.122299c17.243728 26.317621 12.931755 141.561886-12.944254 123.359941l-45.11116-31.650303-44.994507-31.650303 2.953806-2.083079c-0.983213 0.024997-1.96226 0.074991-2.953806 0.074991-1.033207 0-2.04975-0.05416-3.074625-0.079157l2.953806 2.087245-44.994507 31.650303-45.11116 31.650303c-25.809349 18.222776-30.167151-97.300622-12.877595-123.451596a119.714553 119.714553 0 0 1-10.373733-21.95982c0.929053-0.333293 1.858107-0.666585 2.791325-0.987379-89.572399 30.887896-155.42686 98.992083-181.41952 189.5102 82.84822 57.672127 183.519264 91.513829 292.101843 91.513828 108.670068 0 209.420269-33.895862 292.301818-91.651312-26.030156-90.501452-91.926279-158.57231-181.532007-189.42271zM468.467814 727.473697c1.424826-0.179145 2.84132-0.37912 4.274478-0.5416-1.433158 0.16248-2.845486 0.362456-4.274478 0.5416zM454.077904 729.594272zM440.458733 732.168957c1.383164-0.291631 2.778827-0.554099 4.166158-0.829065-1.387331 0.274966-2.782994 0.537434-4.166158 0.829065zM427.293673 735.20192l0.254136-0.058326c-0.083323 0.024997-0.170812 0.041662-0.254136 0.058326zM580.616624 731.331559c1.412328 0.279133 2.837154 0.545767 4.236983 0.841564-1.399829-0.295797-2.824655-0.562431-4.236983-0.841564zM610.721282 738.747321c1.470654 0.437447 2.912145 0.912389 4.366134 1.3665-1.453989-0.454111-2.899646-0.929053-4.366134-1.3665zM597.443737 735.076936c0.279133 0.070825 0.566598 0.137483 0.84573 0.212474-0.279133-0.074991-0.562431-0.141649-0.84573-0.212474zM410.399902 740.051328c1.3665-0.429114 2.716335-0.870727 4.091168-1.283176-1.370666 0.41245-2.724667 0.854062-4.091168 1.283176zM552.545051 726.932097c1.437325 0.16248 2.857984 0.362456 4.28281 0.5416-1.424826-0.179145-2.845486-0.37912-4.28281-0.5416zM566.497514 728.827698z' fill='#F08E83' /><path d='M409.425021 765.802352c3.43708-5.199365 7.724057-6.886659 12.877595-3.262102l45.11116 31.650303 42.036535 29.67971c1.024875 0.024997 2.041417 0.079157 3.074625 0.079157 0.991546 0 1.970593-0.049994 2.953806-0.074991l42.040701-29.683876 45.11116-31.650303c5.178535-3.645388 9.490508-1.920599 12.944254 3.353757a119.464584 119.464584 0 0 0 10.448724-22.122299c-0.912389-0.320794-1.820611-0.649921-2.733-0.966549a325.843558 325.843558 0 0 0-8.207331-2.691338c-1.453989-0.454111-2.89548-0.929053-4.366134-1.3665a320.515041 320.515041 0 0 0-12.427649-3.457911c-0.279133-0.074991-0.566598-0.141649-0.84573-0.212474a329.443118 329.443118 0 0 0-12.59013-2.903813c-1.399829-0.295797-2.824655-0.562431-4.236983-0.841564a336.479759 336.479759 0 0 0-23.788763-3.857862c-1.428992-0.179145-2.849652-0.37912-4.28281-0.5416a355.760739 355.760739 0 0 0-14.035787-1.354002h-27.513308c-1.891436 0.112486-3.778705 0.112486-5.670141 0h-18.539404a356.739786 356.739786 0 0 0-14.039953 1.354002c-1.433158 0.16248-2.849652 0.362456-4.274478 0.5416-3.245437 0.41245-6.478376 0.858229-9.682151 1.354001a346.299394 346.299394 0 0 0-14.160772 2.512194c-1.387331 0.274966-2.78716 0.537434-4.166158 0.829065a327.101737 327.101737 0 0 0-12.910924 2.978803l-0.254135 0.058326a337.958745 337.958745 0 0 0-12.802604 3.562066c-1.374832 0.41245-2.728834 0.854062-4.091167 1.283176a316.086415 316.086415 0 0 0-8.557289 2.807991c-0.933219 0.320794-1.862273 0.654087-2.791326 0.987379a119.514577 119.514577 0 0 0 10.369567 21.955654z' fill='#FEFEFE' /><path d='M602.630603 762.54025l-45.11116 31.650303-42.040701 29.683876-2.953806 2.083079 44.994507 31.650304 45.11116 31.650303c25.880174 18.201945 30.187982-97.042321 12.944254-123.359942-3.449579-5.278522-7.761553-7.003312-12.944254-3.357923zM422.302616 762.54025c-5.149371-3.624558-9.440514-1.937264-12.877595 3.262102-17.289556 26.146808-12.931755 141.674372 12.877595 123.451596l45.11116-31.650303 44.994507-31.650303-2.953806-2.087245-42.036535-29.67971-45.115326-31.646137z' fill='#CFE07D' /><path d='M512.762407 357.477196V243.399455C413.107905 236.596119 270.279507 328.393246 271.591847 480.774645c0.029163 3.637056 0.062492 7.274112 0.258302 10.977827 70.266423 44.165442 216.586061-30.825404 240.912258-134.275276z' fill='#ADBE20' /><path d='M512.762407 357.477196c24.359526 103.079084 169.929257 177.869954 240.603963 134.708556 0.254136-0.154148 0.529102-0.274966 0.783237-0.43328 8.619781-158.955596-139.116351-255.318833-241.383034-248.353017v114.077741z' fill='#7EA701' /></svg>",
"answerNameIcon": "<svg height='15px' viewBox='0 0 48 48' fill='none'><rect width='48' height='48' fill='white' fill-opacity='0.01'/><rect x='9' y='18' width='30' height='24' rx='2' fill='#2F88FF' stroke='#000000' stroke-width='4'/><circle cx='17' cy='26' r='2' fill='white'/><circle cx='31' cy='26' r='2' fill='white'/><path d='M20 32C18.8954 32 18 32.8954 18 34C18 35.1046 18.8954 36 20 36V32ZM28 36C29.1046 36 30 35.1046 30 34C30 32.8954 29.1046 32 28 32V36ZM20 36H28V32H20V36Z' fill='white'/><path d='M24 10V18' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M4 26V34' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><path d='M44 26V34' stroke='#000000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/><circle cx='24' cy='8' r='2' stroke='#000000' stroke-width='4'/></svg>",
"enableVoiceInput": true,
"holdToTalk": true,
"voiceInputLang": "zh_cn",
"voiceInputAppId": "your-app-id",
"voiceInputApiKey": "your-api-key",
"voiceInputApiSecret": "your-api-secret",
"voiceInputSampleRate": 16000,
"voiceInputFormat": "audio/L16;rate=16000",
"voiceInputEncoding":"raw",
"voiceInputIdleTimeoutSeconds": 10,
"voiceInputEndTimeoutSeconds": 3,
"voiceInputHost": "iat-api.xfyun.cn",
"voiceInputPath": "/v2/iat",
"voiceInputProtocol": "wss://",
"voiceInputHmacAlgorithm": "HMAC",
"voiceInputHashAlgorithm": "SHA-256",
"voiceInputRequestBodyTemplate": "{\"common\":{\"app_id\":\"{appId}\"},\"business\":{\"language\":\"{lang}\",\"domain\":\"iat\",\"accent\":\"mandarin\",\"dwa\":\"wpgs\"},\"data\":{\"status\":0,\"format\":\"{format}\",\"encoding\":\"{encoding}\"}}",
"voiceInputAudioBodyTemplate": "{\"data\":{\"status\":1,\"format\":\"{format}\",\"encoding\":\"{encoding}\",\"audio\":\"{audioData}\"}}",
"voiceInputUrlTemplate": "{protocol}{host}{path}?authorization={signature}&date={date}&host={host}",
"voiceResponseExtractor": "{{if data.result && data.result.pgs === \"apd\"}}resultText += resultTextTemp;{{/if}}{{resultTextTemp = getValueByPath(data, \"result.ws[].cw[0].w\");}}"
};
}
useEffect(() => {
// Dynamically obtain parameters
//---------------
getAiConfig().then((response) => {
setAiConfig(response);
});
// Custom request action
//---------------
// If the sent message contains the characters "image" or "img", the streaming mode will be disabled,
// //one or more custom fetch requests will be made, and an image "<img />" will be returned
setCustomRequest(() => {
return async (
message: string,
config: {
requestBody: any,
apiUrl: string,
headers: any
},
customMethods: {
name: string;
func: (...args: any[]) => any;
}[],
conversationHistory: {
sender: string;
timestamp: string;
content: string;
tag: string;
}[],
): Promise<{
content: string | Response | null;
isStream: boolean;
}> => {
// or using "typeof aichatRef.current.getContextData().xxxxx !== 'undefined'"
if (typeof message === 'string' && (message.includes('image') || message.includes('img'))) {
// set streaming mode
config.requestBody.stream = false; // if using stream mode, set it to "true"
const response = await fetch(config.apiUrl, {
method: 'POST',
headers: config.headers,
body: JSON.stringify(config.requestBody),
});
if (!response.ok) {
const _errInfo = `[ERROR] HTTP Error ${response.status}: ${response.statusText}`;
return { content: _errInfo, isStream: false };
}
// if using stream mode
/*
return {
content: response,
isStream: true
};
*/
const jsonResponse = await response.json();
let result: any = jsonResponse.choices[0].message.content + '<img src="data:image/png;base64,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" width="150" height="100" />';
return {
content: result,
isStream: false
};
}
return { content: null, isStream: false };
}
});
}, []);
return (
<>
<a href="#" onClick={(e: React.MouseEvent) => {
e.preventDefault();
if (aichatRef.current) aichatRef.current.chatOpen();
}}>Open Chat</a>
<br />
<a href="#" onClick={(e: React.MouseEvent) => {
e.preventDefault();
if (aichatRef.current) aichatRef.current.setVal('default text here...');
}}>Set Default Value</a>
<br />
<a href="#" onClick={(e: React.MouseEvent) => {
e.preventDefault();
const randomMergedText = [
"Analyze this sentence: The patient's name is Zhang, 30 years old, with fever for 3 days.",
"Analyze this sentence: The patient's name is Wang, 45 years old, experiencing headaches for 5 days.",
"Analyze this sentence: The patient's name is Li, 28 years old, reporting chest pain for 2 days.",
"Analyze this sentence: The patient's name is Chen, 35 years old, with persistent cough for 10 days.",
];
const timestamp = new Date().toLocaleString();
const _mergedText = randomMergedText[Math.floor(Math.random() * randomMergedText.length)];
if (aichatRef.current) {
const _oldData = aichatRef.current.getContextData();
const { systemPrompt } = _oldData;
setContextData({
systemPrompt: systemPrompt,
mergedText: _mergedText
});
alert('The "mergedText" field of the setting context succeeded: ' + JSON.stringify({
systemPrompt: systemPrompt,
mergedText: _mergedText
}));
}
}}>Random Merged Text (set Context)</a>
<Chatbox
bubble={false}
contentRef={aichatRef}
verbose={aiConfig.verbose}
baseUrl={"http://localhost:11434"}
apiKey={aiConfig.APIKey}
customMethods={customMethods}
onInputCallback={async (input:string)=> {
return input.replace(/(\r\n|\r|\n)/g, '<br />');
}}
renderParser={async (input: string) => {
const res = await markedParse(input);
return res;
}}
nameFormatter={(input: string) => {
return input;
}}
requestConfig={{
apiUrl: aiConfig.apiUrl,
requestBody: aiConfig.requestBody,
responseExtractor: aiConfig.responseBody
}}
headerConfig={aiConfig.headerConfig}
customRequest={customRequest}
model={aiConfig.modelName1}
reasoningSwitchLabel={aiConfig.reasoningSwitchLabel}
sendLoadingLabel={aiConfig.sendLoadingLabel}
placeholder={aiConfig.placeholder}
noDataPlaceholder={aiConfig.noDataPlaceholder}
answerName={aiConfig.answerName}
questionName={aiConfig.questionName}
questionNameIcon={aiConfig.questionNameIcon}
answerNameIcon={aiConfig.answerNameIcon}
sendLabel={aiConfig.sendLabel}
bubbleLabel={aiConfig.bubbleLabel}
contextData={contextData}
requestBodyFormatter={async (body: any, context: Record<string, any>, conversationHistory: {
sender: string;
timestamp: string;
content: string;
tag: string;
}[]) => {
/*
Target:
{
"model": "llama3.2-vision",
"messages": [
{
"role": "assistant",
"content": "Analyze this sentence: The patient's name is Liu, 25 years old, and he has had a cold for 7 consecutive days."
},
{
"role": "user",
"content": "Analyze the situation\nPlease keep your answer within 100 words"
}
],
"stream": true
}
*/
if (body.messages && Array.isArray(body.messages)) {
// Modify the content in the messages array
const modifiedMessages = body.messages.map(msg => {
if (msg.role === 'user') {
return {
...msg,
content: `${msg.content}`
};
}
return msg;
});
/*++++++++++++++++ Contextual reasoning ++++++++++++++++++++++ */
// add conversation history
conversationHistory.forEach((item: any, index: number) => {
if (index < conversationHistory.length - 1) {
modifiedMessages.unshift({
role: "assistant",
content: item.content
});
}
});
/*++++++++++++++++ /Contextual reasoning ++++++++++++++++++++++ */
// custom merged text
if (context.mergedText) {
modifiedMessages.unshift({
role: "assistant",
content: `${context.mergedText}`
});
}
// Add a certain style to your answers
if (context.systemPrompt) {
modifiedMessages.unshift({
role: "system",
content: `${context.systemPrompt}`
});
}
// Returns a new request body containing the modified messages
return {
...body,
messages: modifiedMessages
};
}
//If there is no messages field, return the original request body
return body;
}}
onInputChange={(controlRef: React.RefObject<any>, val: string) => {
console.log('onInputChange: ', val);
}}
onChunk={(controlRef: React.RefObject<any>, lastContent: string, conversationHistory: {
sender: string;
timestamp: string;
content: string;
tag: string;
}[]) => {
console.log('onChunk: ', lastContent, conversationHistory);
}}
onComplete={(controlRef: React.RefObject<any>, lastContent: string, conversationHistory: {
sender: string;
timestamp: string;
content: string;
tag: string;
}[]) => {
console.log('onComplete: ', lastContent, conversationHistory);
}}
newChatButton={aiConfig.newChatButton ? JSON.parse(aiConfig.newChatButton) : undefined}
toolkitButtons={aiConfig.toolkitButtons ? JSON.parse(aiConfig.toolkitButtons) : undefined}
/*
newChatButton={JSON.parse(JSON.stringify({
label: '<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> New Chat',
value: 'new',
onClick: 'method.setVal(""); method.clearData();'
}))}
toolkitButtons={JSON.parse(JSON.stringify([
{
label: '<svg fill="currentColor" width="12" height="12" viewBox="0 0 24 24"><path d="M19 2H5c-1.103 0-2 .897-2 2v12c0 1.103.897 2 2 2h3.586L12 21.414 15.414 18H19c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zm0 14h-4.414L12 18.586 9.414 16H5V4h14v12z"/></svg> Brief',
value: 'brief',
onClick: 'console.log(button); console.log(isActive); if(isActive) { method.setContextData({systemPrompt: "Please keep your answer within 77 words"}) } else { method.setContextData({}) }'
}
]))}
*/
// Clipboard
showCopyBtn={true}
autoCopyReply={false}
copiedContentFormatter={(input: string) => {
// Convert HTML text to plain text
return input.replace(/(<([^>]+)>)/ig, '');
}}
onCopyCallback={(res: Record<string, any>) => {
if (res.success) {
// Text copied to clipboard
alert(res.message);
} else {
// Failed to copy text
alert(res.message);
}
}}
// == Voice Input Configuration ==
voiceConfig={{
enableVoiceInput: aiConfig.enableVoiceInput,
holdToTalk: aiConfig.holdToTalk,
voiceInputLang: aiConfig.voiceInputLang,
voiceInputAppId: aiConfig.voiceInputAppId,
voiceInputApiKey: aiConfig.voiceInputApiKey,
voiceInputApiSecret: aiConfig.voiceInputApiSecret,
voiceInputSampleRate: aiConfig.voiceInputSampleRate,
voiceInputFormat: aiConfig.voiceInputFormat,
voiceInputEncoding: aiConfig.voiceInputEncoding,
voiceInputIdleTimeoutSeconds: aiConfig.voiceInputIdleTimeoutSeconds,
voiceInputEndTimeoutSeconds: aiConfig.voiceInputEndTimeoutSeconds,
voiceInputHost: aiConfig.voiceInputHost,
voiceInputPath: aiConfig.voiceInputPath,
voiceInputProtocol: aiConfig.voiceInputProtocol,
voiceInputHmacAlgorithm: aiConfig.voiceInputHmacAlgorithm,
voiceInputHashAlgorithm:aiConfig.voiceInputHashAlgorithm,
voiceInputRequestBodyTemplate: aiConfig.voiceInputRequestBodyTemplate,
voiceInputAudioBodyTemplate: aiConfig.voiceInputAudioBodyTemplate,
voiceInputUrlTemplate: aiConfig.voiceInputUrlTemplate,
voiceResponseExtractor: aiConfig.voiceResponseExtractor,
// Custom signature generator (implementation depends on external crypto libraries)
generateVoiceSignature: async (params: {
host: string;
path: string;
date: string;
apiKey: string;
apiSecret: string;
}) => {
const { host, path, date, apiKey, apiSecret } = params;
const algorithm = "hmac-sha256";
const headers = "host date request-line";
// Compose the string for signing
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
const signature = CryptoJS.enc.Base64.stringify(signatureSha);
// Build the authorization string
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
// Return Base64 encoded authorization string
return btoa(authorizationOrigin);
},
onVoiceInputStart: () => {
console.log('🎤 Recording started...');
// Add UI prompts or visual indicators here
},
onVoiceInputEnd: (text) => {
console.log('✅ Recording ended. Result:', text);
// Automatically submit the message
aichatRef.current?.sendMsg();
// 'text' contains the recognized content and is automatically filled into the textarea
},
onVoiceInputError: (error) => {
console.error('❌ Voice input error:', error);
alert('Speech recognition failed: ' + error.message);
},
onVoiceInputInterrupt: () => {
console.log('⏹️ Voice input interrupted');
},
}}
/>
</>
);
}Using bubbles
import React from "react";
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
return (
<>
<Chatbox
bubble={true}
...
/>
</>
);
}Custom styles
Use the prefix attribute and add your own stylesheet
import React from "react";
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
return (
<>
<Chatbox
prefix="app-"
...
/>
</>
);
}Set the default question
import React, { useState, useEffect } from 'react';
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
const [customQuestion, setCustomQuestion] = useState<undefined | {
title: string;
list: Array<string>;
}>(undefined);
useEffect(() => {
setCustomQuestion({
title: '<strong>FAQ: </strong>',
list: [
"What is <strong>React</strong>?",
"How to use <em>useState</em> hook?",
"Explain <code>useEffect</code> hook"
]
});
}, []);
return (
<>
<Chatbox
...
defaultQuestions={customQuestion}
onQuestionClick={(text: string, methods: Record<string, Function>) => {
console.log('Question clicked:', text);
// send message
methods.sendMsg();
}}
}}
/>
</>
)
}Copy the content to the clipboard
import React from 'react';
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
return (
<>
<Chatbox
...
showCopyBtn={true}
autoCopyReply={false}
copiedContentFormatter={(input: string) => {
// Convert HTML text to plain text
return input.replace(/(<([^>]+)>)/ig, '');
}}
onCopyCallback={(res: Record<string, any>) => {
if (res.success) {
// Text copied to clipboard
console.log(res.message);
} else {
// Failed to copy text
console.log(res.message);
}
}}
}}
/>
</>
)
}Set the active button of toolkit buttons or the default option of the "Select" in toolkit buttons
const toolkitButtons = [
{
label: "Normal Button",
value: "normal",
onClick: "() => { console.log('normal button clicked'); }",
defaultActive: true // This button will be active by default
},
{
label: "Select Button",
value: "select",
isSelect: true,
defaultSelected: 0, // This select button will have its first option selected by default
onSelect__1: "Option 1{#}value1{#}() => { console.log('option 1 selected'); }",
onSelect__2: "Option 2{#}value2{#}() => { console.log('option 2 selected'); }"
}
];Disable pop-up mode
This makes it possible to embed the chat window into normal HTML code
styles.scss:
/* disable popup */
.custom-chatbox-container.popup-none {
--custom-chatbox-w: 100%;
--custom-chatbox-h: calc(100vh - 2.625rem); /* custom height */
--custom-chatbox-bg: transparent; /* custom background */
}index.tsx:
import React from 'react';
import Chatbox from 'funda-ui/Chatbox';
// component styles
import 'funda-ui/Chatbox/index.css';
export default () => {
return (
<>
<Chatbox
...
usePopUp={false}
}}
/>
</>
)
}Speech recognition
You need to install crypto-js first, run following code:
$ npm install crypto-js
$ npm install --save-dev @types/crypto-jsimport React from 'react';
import Chatbox from 'funda-ui/Chatbox';
import 'funda-ui/Chatbox/index.css';
import CryptoJS from 'crypto-js';
export default () => {
const aichatRef = useRef<any>(null);
return (
<Chatbox
...
contentRef={aichatRef}
// == Voice Input Configuration ==
voiceConfig={{
enableVoiceInput: true,
holdToTalk: true,
voiceInputAppId: "your-app-id",
voiceInputApiKey: "your-api-key",
voiceInputApiSecret: "your-api-secret",
voiceInputSampleRate: 16000,
voiceInputFormat: "audio/L16;rate=16000",
voiceInputEncoding: "raw",
voiceInputIdleTimeoutSeconds: 10,
voiceInputEndTimeoutSeconds: 5,
// Optional configuration items
voiceInputHost: "iat-api.xfyun.cn",
voiceInputPath: "/v2/iat",
voiceInputProtocol: "wss://", // or "ws://"
voiceInputHmacAlgorithm: "HMAC",
voiceInputHashAlgorithm: "SHA-256",
// templates
voiceInputUrlTemplate: "{protocol}{host}{path}?authorization={signature}&date={date}&host={host}",
voiceInputRequestBodyTemplate: `{
"common": {
"app_id": "{appId}"
},
"business": {
"language": "{lang}",
"domain": "iat",
"accent": "mandarin",
"dwa": "wpgs"
},
"data": {
"status": 0,
"format": "{format}",
"encoding": "{encoding}"
}
}`,
voiceInputAudioBodyTemplate: `{
"data": {
"status": 1,
"format": "{format}",
"encoding": "{encoding}",
"audio": "{audioData}"
}
}`,
// To extract the recognized text from the voice input API response, which is a configurable string template of JavaScript.
voiceResponseExtractor: `{{if data.result && data.result.pgs === "apd"}}resultText += resultTextTemp;{{/if}}{{resultTextTemp = getValueByPath(data, "result.ws[].cw[0].w");}}`,
// Custom signature generator (implementation depends on external crypto libraries)
generateVoiceSignature: async (params: {
host: string;
path: string;
date: string;
apiKey: string;
apiSecret: string;
}) => {
const { host, path, date, apiKey, apiSecret } = params;
const algorithm = "hmac-sha256";
const headers = "host date request-line";
// Compose the string for signing
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
const signature = CryptoJS.enc.Base64.stringify(signatureSha);
// Build the authorization string
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
// Return Base64 encoded authorization string
return btoa(authorizationOrigin);
},
onVoiceInputStart: () => {
console.log('🎤 Recording started...');
// Add UI prompts or visual indicators here
},
onVoiceInputEnd: (text) => {
console.log('✅ Recording ended. Result:', text);
// Automatically submit the message if text is not empty
if (text && text.trim() !== '') {
aichatRef.current?.sendMsg();
}
// 'text' contains the recognized content and is automatically filled into the textarea
},
onVoiceInputError: (error) => {
console.error('❌ Voice input error:', error);
alert('Speech recognition failed: ' + error.message);
},
onVoiceInputInterrupt: () => {
console.log('⏹️ Voice input interrupted');
},
}}
/>
);
};❤️ API
Chatbox
import Chatbox from 'funda-ui/Chatbox';| Property | Type | Default | Description | Required |
|---|---|---|---|---|
contentRef | React.RefObject | - | It exposes the following methods:
| - |
debug | boolean | false | Enable debug mode to output console information | - |
defaultRows | number | 2 | The rows attribute specifies the visible height of a text area, in lines. | - |
prefix | string | custom- | Prefix for component wrapper class name | - |
usePopUp | boolean | true | Whether to use the pop-up window, if false, will be directly embedded with html | - |
model | string | - | The model name to use | - |
baseUrl | string | - | The base URL for API requests | - |
apiKey | string | - | API key for authentication. | - |
token | string | function | - | Dynamic identification string. Functional return value is supported, such as: | - |
defaultMessages | Array | - | Initial messages to display in the chat | - |
verbose | boolean | true | Whether to show reasoning details | - |
showCopyBtn | boolean | false | Whether to show copy button for each reply | - |
autoCopyReply | boolean | false | Whether to automatically copy reply to clipboard | - |
reasoningSwitchLabel | string | - | Label for the reasoning toggle button HTML tags are supported | - |
stopLabel | React.ReactNode | - | Label for the stop button HTML tags are supported | - |
questionName | React.ReactNode | - | Name to display for user messages HTML tags are supported | - |
answerName | React.ReactNode | - | Name to display for AI responses HTML tags are supported | - |
questionNameIcon | string | - | Icon URL for user messages HTML tags are supported | - |
answerNameIcon | string | - | Icon URL for AI responses HTML tags are supported | - |
bubble | boolean | false | Whether to show as a floating bubble | - |
bubbleLabel | string | - | Label for the floating bubble HTML tags are supported | - |
sendLabel | string | - | Label for the send button HTML tags are supported | - |
sendLoading | boolean | false | Whether to show loading indicator when sending | - |
sendLoadingLabel | string | - | Label to show during loading | - |
copyLabel | string | Copy | Label text of the copy button | - |
closeLabel | string | Close | Label text of the close button | - |
placeholder | string | - | Placeholder text for input field | - |
noDataPlaceholder | string | - | Text to show when no messages exist HTML tags are supported | - |
maxHistoryLength | number | 20 | Controls the length of the history | - |
requestConfig | JSON Object | - | Configuration for API requests | ✅ |
headerConfig | any | - | Configuration for request headers. Placeholder string | - |
contextData | JSON Object | - | Dynamic JSON data for request formatting | - |
defaultQuestions | JSON Object | - | Configuration for default questions to display when chat is empty. Object contains:
Example: {title:"Suggested questions:",list:["What is React?","How do I use hooks?","Explain props vs state"]}These questions will be displayed as clickable options when the chat has no messages. | - |
customMethods | Array | - | Array of custom methods that can be called via contentRef. Each method object contains:
Example: [{name:"sayHello",func:(name:string)=>{console.log("Hello, " + name + "!");}},{name:"updateUI",func:(data:any)=>{console.log('Updating UI with:',data);}}]These methods can be accessed through:
Usage Example: contentRef.current.executeCustomMethod('sayHello', 'your name');contentRef.current.getCustomMethods(); | - |
toolkitButtons | Array | - | JSON string for toolkit buttons configuration. Each button can have label, value and onClick properties. The onClick function can access contentRef methods. Example: [{"label":"Clear","value":"clear","onClick":"alert('new'); method.clearData();"},{"label":"Send","value":"send","onClick":"method.sendMsg();"},{"label":"Change Context","value":"changecontext","onClick":"method.setContextData({systemPrompt: "Please keep your answer within 77 words"});"}] Available methods in onClick:
Additional parameters available in onClick:
| - |
newChatButton | JSON Object | - | JSON string for new chat button configuration. Similar to toolkitButtons but for a single button that appears when chat has messages. Example: {label:"New Chat",value:"new",onClick:"method.clearData(); method.setVal('');"} | - |
customRequest | async Function | - | (It must return a "Promise<string>" object) Custom request handler function that allows overriding the default request behavior. The function must return a Promise<string>. Parameters:
Return Value: Promise<{content: string | Response | null;isStream: boolean;}> - The response to displayExample: ()=>{return async(message,config)=>{if(typeof message==='string'&&(message.includes('image')||message.includes('img'))){config.requestBody.stream=false;const response=await fetch(config.apiUrl,{method:'POST',headers:config.headers,body:JSON.stringify(config.requestBody),});if(!response.ok){const _errInfo="[ERROR] HTTP Error"+response.status+":"+response.statusText;return{content:_errInfo,isStream:false};}const jsonResponse=await response.json();let result=jsonResponse.choices[0].message.content;return{content:result,isStream:false};}return{content:null,isStream:false};}} | - |
renderParser | async Function | - | (It must return a "Promise<string>" object) Custom parser for rendering messages. such as async(input:string)=>{const res=await markedParse(input);return res;} | - |
requestBodyFormatter | async Function | - | (It must return a "Promise<Record<string, any>>" object) Function to format request body. such as (body:any,context:Record<string,any>,conversationHistory:any[])=>{if(body.messages&&Array.isArray(body.messages)){const modifiedMessages=body.messages.map(msg=>{if(msg.role==='user'){return{...msg,content:msg.content};}return msg;});conversationHistory.forEach((item:any,index:number)=>{if(index<conversationHistory.length-1){modifiedMessages.unshift({role:"assistant",content:item.content});}});return{...body,messages:modifiedMessages};}return body;} | - |
nameFormatter | Function | - | Function to format display names. such as (input:string)=>{return input} At the same time it returns the Control Event, you will use this function and use the return keyword to return a new value. | - |
copiedContentFormatter | Function | - | Function to format the copied content. such as (input:string)=>{return input} At the same time it returns the Control Event, you will use this function and use the return keyword to return a new value. Valid when showCopyBtn or autoCopyReply is enabled | - |
onQuestionClick | Function | - | Callback when a default question is clicked. Receives two parameters.
Example: (text: string, methods: Record<string, Function>) => {console.log('Question clicked:', text); methods.sendMsg(); } | - |
onInputChange | Function | - | Callback when input changes. It returns only two values.
| - |
onInputCallback | async function | - | (It must return a "Promise<string>" object) Return value from onInputCallback property to format the data of the control element, which will sanitize input is the process of securing/cleaning/filtering input data. such as async (input:string)=>{return input.replace(/(\r\n|\r|\n)/g, '<br />');} At the same time it returns the Control Event, you will use this function and use the return keyword to return a new value. It fires in real time as the user enters. If return is not set, it will not return. | - |
onChunk | Function | - | Callback when data processing. It returns three values.
| - |
onComplete | Function | - | Callback when message sending completes. It returns three values.
| - |
voiceConfig | VoiceConfig Object | - | Configuration for voice input and output features. See Voice Configuration section below for details. | - |
JSON Object Literals configuration properties of the toolkitButtons and newChatButton (JSON Object && Array):
| Property | Type | Default | Description | Required |
|---|---|---|---|---|
label | string | - | The text displayed by the button HTML tags are supported | ✅ |
value | string | - | Button identification | ✅ |
onClick | string | - | Function expression in character format onClick uses a string to fully customize the configuration in the business | ✅ |
active | boolean | false | Specify if the button should be active by default | - |
isSelect | boolean | - | Mark whether it is a drop-down selection button. If enabled, the onSelect__<number> attribute is supported to be dynamically added as a different option, such as onSelect__1, onSelect__2, ... Label, value and onClick methods are separated by When the value of onSelect__<number> is | - |
onSelect__<number> | string | - | Function expression in character format onSelect__<number> uses a string to fully customize the configuration in the business | - |
dynamicOptions | boolean | - | Mark whether to use dynamic options. such as {"label":"Drop-Down","value":"lab","onClick":"return method.executeCustomMethod('getMenuList')","isSelect":true,"dynamicOptions":true} Custom functions: | - |
defaultSelected | number | - | Specify default selected option index. Valid when onSelect__<number> is used | - |
JSON Object Literals configuration properties of the requestConfig (JSON Object):
| Property | Type | Default | Description | Required |
|---|---|---|---|---|
apiUrl | string | - | The URL from which the request was made. such as http://127.0.0.1:11434/v1/chat/completions Placeholder string | ✅ |
requestBody | string | - | JSON string for request body template. such as {'model':'{model}','messages':[{'role':'user','content':'{message}'}],'stream': true} Placeholder string | ✅ |
responseExtractor | string | - | JSON path to extract response. such as data.choices.0.delta.content Please use | ✅ |
Array Object configuration properties of the defaultMessages (Array):
| Property | Type | Default | Description | Required |
|---|---|---|---|---|
sender | string | - | Sender's name HTML tags are supported | ✅ |
content | string | - | The message content HTML tags are supported | ✅ |
timestamp | string | - | Time when the message was sent | ✅ |
tag | string | - | For an identifier, it is recommended to use [] separately, such as [reply][response] | ✅ |
[
{
"sender": "David",
"timestamp": "9:53:09 AM",
"content": "<p>hello</p>\n",
"tag": ""
},
{
"sender": "System",
"timestamp": "9:53:09 AM",
"content": " \n\n<p>Hello! How can I assist you today? 😊</p>\n",
"tag": "[reply]"
},
{
"sender": "David",
"timestamp": "9:53:15 AM",
"content": "<p>how are you</p>\n",
"tag": ""
},
{
"sender": "System",
"timestamp": "9:53:19 AM",
"content": "<p>I'm just a computer program, so I don't have feelings, but thanks for asking! How can I assist you today?</p>\n",
"tag": "[reply]"
},
...
]Voice Configuration
The voiceConfig property allows you to enable and configure voice input (speech-to-text) and voice output (text-to-speech) features using third-party voice services.
iFlytek is recommended by default. Other service providers also need to configure parameters according to the actual situation.
JSON Object configuration properties of the voiceConfig (JSON Object):
| Property | Type | Default | Description | Required |
|---|---|---|---|---|
enableVoiceInput | boolean | false | Enable voice input (speech-to-text) feature. When enabled, a microphone button will appear next to the send button. | ✅ |
holdToTalk | boolean | true | Enable long press recording. | - |
voiceInputAppId | string | - | Voice service provider AppID for voice input. | - |
voiceInputApiKey | string | - | Voice service provider APIKey for voice input. | - |
voiceInputApiSecret | string | - | Voice service provider APISecret for voice input. Used for signature generation. | - |
voiceInputSampleRate | number | 16000 | Audio sample rate for voice input (Hz). Default is 16000. | - |
voiceInputFormat | string | audio/L16;rate=16000 | Audio format for voice input. Default is "audio/L16;rate=16000". | - |
voiceInputEncoding | string | raw | Audio encoding format for voice input. Default is "raw". | - |
voiceInputHost | string | custom-host.com | WebSocket host for voice input. | - |
voiceInputPath | string | /custom/path | WebSocket path for voice input. | - |
voiceInputProtocol | string | wss:// | WebSocket protocol for voice input (for example "wss://" or "ws://"). | - |
voiceInputHmacAlgorithm | string | HMAC | HMAC algorithm name used when generating the authorization header. | - |
voiceInputHashAlgorithm | string | SHA-256 | Hash algorithm used when generating the authorization header. | - |
voiceInputUrlTemplate | string | {protocol}{host}{path}?authorization={signature}&date={date}&host={host} | WebSocket URL template used to build the final URL. Supports placeholders such as | - |
voiceInputRequestBodyTemplate | string | {"common":{"app_id":"{appId}"},"business":{"language":"{lang}","domain":"iat","accent":"mandarin","dwa":"wpgs"},"data":{"status":0,"format":"{format}","encoding":"{encoding}"}} | WebSocket URL template used to build the final URL. Supports placeholders that consistent with | - |
voiceInputAudioBodyTemplate | string | {"data":{"status":1,"format":"{format}","encoding":"{encoding}","audio":"{audioData}"}} | Template for each frame of audio data. Supports placeholders that consistent with | - |
voiceInputAccessKeyId | string | - | Explicit accessKeyId used in URL parameters. If not provided, voiceInputApiKey will be used. | - |
voiceInputUuid | string | auto-generated | UUID used to identify the voice session. If not provided, a UUID will be generated automatically. | - |
voiceInputLang | string | autodialect | Language parameter for recognition (for example "zh_cn", "en_us"). Default is "autodialect". | - |
voiceInputUtc | string | auto-generated | UTC time string in ISO 8601 format with timezone (e.g. 2025-09-04T15:38:07+0800). If not provided, it will be generated automatically. | - |
voiceResponseExtractor | string | {{if data.result && data.result.pgs === "apd"}}resultText += resultTextTemp;{{/if}}{{resultTextTemp = getValueByPath(data, "result.ws[].cw[0].w");}} | To extract the recognized text from the voice input API response, which is a configurable string template of JavaScript. Supports Syntax Translation for Sandbox Execution Environment: External Variable: | - |
voiceInputIdleTimeoutSeconds | number | 10 | Auto-disconnect timeout (in seconds) after recording starts if no content is recognized. After recording starts, if no content is recognized within this time, the connection will be closed automatically (default 10 seconds) | - |
voiceInputEndTimeoutSeconds | number | 5 | Auto-disconnect timeout (in seconds) after recognition completes if there is no further activity. After recognition results are available, if there is no further operation within this time, the connection will also be closed automatically (default 5 seconds) | - |
generateVoiceSignature | Function | - | Custom signature generator. Receives { host, path, date, apiKey, apiSecret } and should return a string or Promise<string> representing the authorization signature. such as (params:{host:string;path:string;date:string;apiKey:string;apiSecret:string;})=>{const{host,path,date,apiKey,apiSecret}=params;const algorithm="hmac-sha256";const headers="host date request-line";const signatureOrigin="host: " + host + "\n" + "date: " + date + "\n" + "GET /v2/iat HTTP/1.1";const signatureSha=CryptoJS.HmacSHA256(signatureOrigin,apiSecret);const signature=CryptoJS.enc.Base64.stringify(signatureSha);const authorizationOrigin='api_key="' + apiKey + '",algorithm="' + algorithm + '",headers="' + headers + '",signature="' + signature + '"';const authorization=btoa(authorizationOrigin);return authorization;}` | - |
onVoiceInputStart | Function | - | Callback when voice input starts. | - |
onVoiceInputEnd | Function | - | Callback when voice input ends. Receives the recognized text as parameter: (text: string) => void. | - |
onVoiceInputError | Function | - | Callback when voice input encounters an error. Receives the error as parameter: (error: Error) => void. | - |
onVoiceInputInterrupt | Function | - | Callback when voice input is interrupted. | - |