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 method

General

Use the chat area alone, without bubbles + read remote parameter configuration


Open Chat
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
        }
    ) => 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);
            }
        },
        /* 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>"
        };
 
    }
 
 
    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
                }
            ): 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();
 
                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}
                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: any[]) => {
                    /*
                    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: any[]) => {
                    console.log('onChunk: ', lastContent, conversationHistory);
                }}
                onComplete={(controlRef: React.RefObject<any>, lastContent: string, conversationHistory: any[]) => {
                    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({}) }'
                    }
                ]))}
                */
 
            />
        </>
    );
 
}

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);
                    }
                }}
            }}
            />
        </>
    )
}

API

Chatbox

import Chatbox from 'funda-ui/Chatbox';
PropertyTypeDefaultDescriptionRequired
contentRefReact.RefObject-It exposes the following methods:
  1. contentRef.current.chatOpen()
  2. contentRef.current.chatClose()
  3. contentRef.current.clearData()
  4. contentRef.current.sendMsg()
  5. contentRef.current.getHistory()
  6. contentRef.current.trimHistory(10)
  7. contentRef.current.setHistory([{sender:"User",timestamp:"12:00:00",content:"Hello",tag:""},{sender:"AI",timestamp:"12:00:01",content:"Hi there!",tag:"[reply]"}])
  8. contentRef.current.setVal('new value')
  9. contentRef.current.getContextData()
  10. contentRef.current.setContextData({systemPrompt: "Please keep your answer within 77 words"})
  11. contentRef.current.getMessages()
  12. contentRef.current.setMessages([{"sender":"Sender","timestamp":"4:19:50 PM","content":"My custom new message\n","tag":"[reply]"}])
  13. contentRef.current.getCustomMethods()
  14. contentRef.current.executeCustomMethod('sayHello', 'your name')
-
debugbooleanfalseEnable debug mode to output console information-
defaultRowsnumber3The rows attribute specifies the visible height of a text area, in lines.-
prefixstringcustom-Prefix for component wrapper class name-
modelstring-The model name to use-
baseUrlstring-The base URL for API requests-
apiKeystring-API key for authentication-
defaultMessagesArray-Initial messages to display in the chat-
verbosebooleantrueWhether to show reasoning details-
showCopyBtnbooleanfalseWhether to show copy button for each reply-
autoCopyReplybooleanfalseWhether to automatically copy reply to clipboard-
reasoningSwitchLabelstring-Label for the reasoning toggle button
HTML tags are supported
-
stopLabelReact.ReactNode-Label for the stop button
HTML tags are supported
-
questionNameReact.ReactNode-Name to display for user messages
HTML tags are supported
-
answerNameReact.ReactNode-Name to display for AI responses
HTML tags are supported
-
questionNameIconstring-Icon URL for user messages
HTML tags are supported
-
answerNameIconstring-Icon URL for AI responses
HTML tags are supported
-
bubblebooleanfalseWhether to show as a floating bubble-
bubbleLabelstring-Label for the floating bubble
HTML tags are supported
-
sendLabelstring-Label for the send button
HTML tags are supported
-
sendLoadingbooleanfalseWhether to show loading indicator when sending-
sendLoadingLabelstring-Label to show during loading-
placeholderstring-Placeholder text for input field-
noDataPlaceholderstring-Text to show when no messages exist
HTML tags are supported
-
maxHistoryLengthnumber20Controls the length of the history-
requestConfigJSON Object-Configuration for API requests
headerConfigany-Configuration for request headers.
Placeholder string
  • {apiKey} -> Your Secret API key on the API key page, It will use the incoming apiKey instead
-
contextDataJSON Object-Dynamic JSON data for request formatting-
defaultQuestionsJSON Object-Configuration for default questions to display when chat is empty. Object contains:
  1. title: Title text to show above questions (string)
  2. list: Array of question strings to display

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.
-
customMethodsArray-Array of custom methods that can be called via contentRef. Each method object contains:
  1. name: Method name (string)
  2. func: Function to execute

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:
  1. contentRef.current.executeCustomMethod('methodName', ...args): Execute a specific custom method
  2. contentRef.current.getCustomMethods(): Get list of available custom method names

Usage Example:
contentRef.current.executeCustomMethod('sayHello', 'your name');
contentRef.current.getCustomMethods();
-
toolkitButtonsArray-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:
  1. method.chatOpen(): Open the dialog box
  2. method.chatClose(): Close the dialog box
  3. method.clearData(): Clear the data
  4. method.sendMsg(): Send a message
  5. method.getHistory(): Get the history
  6. method.trimHistory(10): Trim the history
  7. method.setHistory([{sender:"User",timestamp:"12:00:00",content:"Hello",tag:""},{sender:"AI",timestamp:"12:00:01",content:"Hi there!",tag:"[reply]"}]): Set list of conversation history
  8. method.setVal('new value'): Set the default value of the input box
  9. method.getContextData(): Get current context data
  10. method.setContextData({systemPrompt: "Please keep your answer within 77 words"}): Modify the context data
  11. contentRef.current.getMessages(): Get messages list
  12. contentRef.current.setMessages([{"sender":"Sender","timestamp":"4:19:50 PM","content":"My custom new message\n","tag":"[reply]"}]): Set messages list
  13. method.getCustomMethods(): Get all available custom method names
  14. method.executeCustomMethod('sayHello', 'your name'): A function that executes a custom method

Additional parameters available in onClick:
  • isActive: The activation status of the current button
  • button: HTML object for the current button
-
newChatButtonJSON 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('');"}
-
customRequestasync 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:
  1. message: The user's input message (string)
  2. config: Configuration object containing:
    • requestBody: The parsed request body object
    • apiUrl: The API endpoint URL
    • headers: The request headers object

Return Value:
Promise<{content: string | Response | null;isStream: boolean;}> - The response to display

Example:
()=>{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};}}
-
renderParserasync 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;}-
requestBodyFormatterasync 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;}-
nameFormatterFunction-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.
-
copiedContentFormatterFunction-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
-
onQuestionClickFunction-Callback when a default question is clicked. Receives two parameters.
  1. The one is the question text that was clicked (String)
  2. The second parameter is Object containing all exposed methods from contentRef (React.RefObject)

Example:
(text: string, methods: Record<string, Function>) => {console.log('Question clicked:', text); methods.sendMsg(); }
-
onInputChangeFunction-Callback when input changes. It returns only two values.
  1. The one is method reference of the input HTMLElement (React.RefObject)
  2. The second parameter is the current value (String)
-
onInputCallbackasync 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}
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.
-
onChunkFunction-Callback when data processing. It returns three values.
  1. The one is method reference of the input HTMLElement (React.RefObject)
  2. The second parameter is the current value (String)
  3. The third parameter is the conversation history (Array)
-
onCompleteFunction-Callback when message sending completes. It returns three values.
  1. The one is method reference of the input HTMLElement (React.RefObject)
  2. The second parameter is the current value (String)
  3. The third parameter is the conversation history (Array)
-

JSON Object Literals configuration properties of the toolkitButtons and newChatButton (JSON Object && Array):

PropertyTypeDefaultDescriptionRequired
labelstring-The text displayed by the button
HTML tags are supported
valuestring-Button identification
onClickstring-Function expression in character format
onClick uses a string to fully customize the configuration in the business
isSelectboolean-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 {#}, eg. {"label":"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'});"}

When the value of onSelect__<number> is cancel, it will restore the default label
-
onSelect__<number>string-Function expression in character format
onSelect__<number> uses a string to fully customize the configuration in the business
-
dynamicOptionsboolean-Mark whether to use dynamic options. such as {"label":"Drop-Down","value":"lab","onClick":"return method.executeCustomMethod('getMenuList')","isSelect":true,"dynamicOptions":true}
Custom functions:
const customMethods=[{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;}}];
-

JSON Object Literals configuration properties of the requestConfig (JSON Object):

PropertyTypeDefaultDescriptionRequired
apiUrlstring-The URL from which the request was made. such as http://127.0.0.1:11434/v1/chat/completions
Placeholder string
  • {baseUrl} -> A URL assigned to API to convert "relative URLs" into "absolute URLs" by the addition of a document name or trailing slash (including port), It will use the incoming baseUrl instead
requestBodystring-JSON string for request body template. such as {'model':'{model}','messages':[{'role':'user','content':'{message}'}],'stream': true}
Placeholder string
  • {model} -> The name of the model
  • {message} -> Content sent by the user
  • {token} -> The identity string of the current session.
responseExtractorstring-JSON path to extract response. such as data.choices.0.delta.content
Please use . to separate fields. 0 represents the first element of the array.

Array Object configuration properties of the defaultMessages (Array):

PropertyTypeDefaultDescriptionRequired
senderstring-Sender's name
HTML tags are supported
contentstring-The message content
HTML tags are supported
timestampstring-Time when the message was sent
tagstring-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&#39;m just a computer program, so I don&#39;t have feelings, but thanks for asking! How can I assist you today?</p>\n",
        "tag": "[reply]"
    },
    ...
]