Team Ai
Apppublic

KBaba7/llama.cpp

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
0likes
ChatMessage.tsx230 linesDownload Raw Back to components
1import { useMemo, useState } from 'react';2import { useAppContext } from '../utils/app.context';3import { Message, PendingMessage } from '../utils/types';4import { classNames } from '../utils/misc';5import MarkdownDisplay, { CopyButton } from './MarkdownDisplay';6 7interface SplitMessage {8  content: PendingMessage['content'];9  thought?: string;10  isThinking?: boolean;11}12 13export default function ChatMessage({14  msg,15  id,16  scrollToBottom,17  isPending,18}: {19  msg: Message | PendingMessage;20  id?: string;21  scrollToBottom: (requiresNearBottom: boolean) => void;22  isPending?: boolean;23}) {24  const { viewingConversation, replaceMessageAndGenerate, config } =25    useAppContext();26  const [editingContent, setEditingContent] = useState<string | null>(null);27  const timings = useMemo(28    () =>29      msg.timings30        ? {31            ...msg.timings,32            prompt_per_second:33              (msg.timings.prompt_n / msg.timings.prompt_ms) * 1000,34            predicted_per_second:35              (msg.timings.predicted_n / msg.timings.predicted_ms) * 1000,36          }37        : null,38    [msg.timings]39  );40 41  // for reasoning model, we split the message into content and thought42  // TODO: implement this as remark/rehype plugin in the future43  const { content, thought, isThinking }: SplitMessage = useMemo(() => {44    if (msg.content === null || msg.role !== 'assistant') {45      return { content: msg.content };46    }47    let actualContent = '';48    let thought = '';49    let isThinking = false;50    let thinkSplit = msg.content.split('<think>', 2);51    actualContent += thinkSplit[0];52    while (thinkSplit[1] !== undefined) {53      // <think> tag found54      thinkSplit = thinkSplit[1].split('</think>', 2);55      thought += thinkSplit[0];56      isThinking = true;57      if (thinkSplit[1] !== undefined) {58        // </think> closing tag found59        isThinking = false;60        thinkSplit = thinkSplit[1].split('<think>', 2);61        actualContent += thinkSplit[0];62      }63    }64    return { content: actualContent, thought, isThinking };65  }, [msg]);66 67  if (!viewingConversation) return null;68 69  const regenerate = async () => {70    replaceMessageAndGenerate(viewingConversation.id, msg.id, undefined, () =>71      scrollToBottom(true)72    );73  };74 75  return (76    <div className="group" id={id}>77      <div78        className={classNames({79          chat: true,80          'chat-start': msg.role !== 'user',81          'chat-end': msg.role === 'user',82        })}83      >84        <div85          className={classNames({86            'chat-bubble markdown': true,87            'chat-bubble-base-300': msg.role !== 'user',88          })}89        >90          {/* textarea for editing message */}91          {editingContent !== null && (92            <>93              <textarea94                dir="auto"95                className="textarea textarea-bordered bg-base-100 text-base-content w-[calc(90vw-8em)] lg:w-96"96                value={editingContent}97                onChange={(e) => setEditingContent(e.target.value)}98              ></textarea>99              <br />100              <button101                className="btn btn-ghost mt-2 mr-2"102                onClick={() => setEditingContent(null)}103              >104                Cancel105              </button>106              <button107                className="btn mt-2"108                onClick={() =>109                  replaceMessageAndGenerate(110                    viewingConversation.id,111                    msg.id,112                    editingContent113                  )114                }115              >116                Submit117              </button>118            </>119          )}120          {/* not editing content, render message */}121          {editingContent === null && (122            <>123              {content === null ? (124                <>125                  {/* show loading dots for pending message */}126                  <span className="loading loading-dots loading-md"></span>127                </>128              ) : (129                <>130                  {/* render message as markdown */}131                  <div dir="auto">132                    {thought && (133                      <details134                        className="collapse bg-base-200 collapse-arrow mb-4"135                        open={isThinking && config.showThoughtInProgress}136                      >137                        <summary className="collapse-title">138                          {isPending && isThinking ? (139                            <span>140                              <span141                                v-if="isGenerating"142                                className="loading loading-spinner loading-md mr-2"143                                style={{ verticalAlign: 'middle' }}144                              ></span>145                              <b>Thinking</b>146                            </span>147                          ) : (148                            <b>Thought Process</b>149                          )}150                        </summary>151                        <div className="collapse-content">152                          <MarkdownDisplay content={thought} />153                        </div>154                      </details>155                    )}156                    <MarkdownDisplay content={content} />157                  </div>158                </>159              )}160              {/* render timings if enabled */}161              {timings && config.showTokensPerSecond && (162                <div className="dropdown dropdown-hover dropdown-top mt-2">163                  <div164                    tabIndex={0}165                    role="button"166                    className="cursor-pointer font-semibold text-sm opacity-60"167                  >168                    Speed: {timings.predicted_per_second.toFixed(1)} t/s169                  </div>170                  <div className="dropdown-content bg-base-100 z-10 w-64 p-2 shadow mt-4">171                    <b>Prompt</b>172                    <br />- Tokens: {timings.prompt_n}173                    <br />- Time: {timings.prompt_ms} ms174                    <br />- Speed: {timings.prompt_per_second.toFixed(1)} t/s175                    <br />176                    <b>Generation</b>177                    <br />- Tokens: {timings.predicted_n}178                    <br />- Time: {timings.predicted_ms} ms179                    <br />- Speed: {timings.predicted_per_second.toFixed(1)} t/s180                    <br />181                  </div>182                </div>183              )}184            </>185          )}186        </div>187      </div>188 189      {/* actions for each message */}190      {msg.content !== null && (191        <div192          className={classNames({193            'mx-4 mt-2 mb-2': true,194            'text-right': msg.role === 'user',195          })}196        >197          {/* user message */}198          {msg.role === 'user' && (199            <button200              className="badge btn-mini show-on-hover"201              onClick={() => setEditingContent(msg.content)}202              disabled={msg.content === null}203            >204              โœ๏ธ Edit205            </button>206          )}207          {/* assistant message */}208          {msg.role === 'assistant' && (209            <>210              {!isPending && (211                <button212                  className="badge btn-mini show-on-hover mr-2"213                  onClick={regenerate}214                  disabled={msg.content === null}215                >216                  ๐Ÿ”„ Regenerate217                </button>218              )}219              <CopyButton220                className="badge btn-mini show-on-hover mr-2"221                content={msg.content}222              />223            </>224          )}225        </div>226      )}227    </div>228  );229}230