update message display

This commit is contained in:
Andrew Pareles 2024-12-04 21:36:33 -08:00
parent e1692758c6
commit 0855376e02
5 changed files with 39 additions and 79 deletions

View file

@ -212,7 +212,7 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested?
}
export const MarkdownRender = ({ string, nested = false }: { string: string, nested?: boolean }) => {
const tokens = marked.lexer(string ?? '(empty)'); // https://marked.js.org/using_pro#renderer
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
return (
<>
{tokens.map((token, index) => (

View file

@ -68,7 +68,6 @@ export const ErrorDisplay = ({
error,
onDismiss = null,
showDismiss = true,
className = ''
}: {
error: Error | object | string,
onDismiss: (() => void) | null,
@ -81,7 +80,7 @@ export const ErrorDisplay = ({
const hasDetails = details.cause || Object.keys(details.additional).length > 0;
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 ${className}`}>
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 overflow-auto`}>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex gap-3">

View file

@ -8,7 +8,7 @@ import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState }
import { useConfigState, useService, useThreadsState } from '../util/services.js';
import { generateDiffInstructions } from '../../../prompt/systemPrompts.js';
import { userInstructionsStr } from '../../../prompt/stringifyFiles.js';
import { CodeSelection, CodeStagingSelection } from '../../../registerThreads.js';
import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../registerThreads.js';
import { BlockCode } from '../markdown/BlockCode.js';
import { MarkdownRender } from '../markdown/MarkdownRender.js';
@ -20,8 +20,6 @@ import { ErrorDisplay } from './ErrorDisplay.js';
import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { getCmdKey } from '../../../getCmdKey.js'
import { VSCodeDropdown } from '@vscode/webview-ui-toolkit/react';
// read files from VSCode
const VSReadFile = async (modelService: IModelService, uri: URI): Promise<string | null> => {
const model = modelService.getModel(uri)
@ -30,27 +28,6 @@ const VSReadFile = async (modelService: IModelService, uri: URI): Promise<string
}
export type ChatMessage =
| {
role: 'user';
content: string; // content sent to the llm
displayContent: string; // content displayed to user
selections: CodeSelection[] | null; // the user's selection
}
| {
role: 'assistant';
content: string; // content received from LLM
displayContent: string | undefined; // content displayed to user (this is the same as content for now)
}
| {
role: 'system';
content: string;
displayContent?: undefined;
}
const getBasename = (pathStr: string) => {
// 'unixify' path
pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with /
@ -174,7 +151,7 @@ export const SidebarChat = () => {
const [instructions, setInstructions] = useState('') // the user's instructions
// state of chat
const [messageStream, setMessageStream] = useState('')
const [messageStream, setMessageStream] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const latestRequestIdRef = useRef<string | null>(null)
@ -204,8 +181,8 @@ export const SidebarChat = () => {
threadsStateService.addMessageToCurrentThread(systemPromptElt)
const userContent = userInstructionsStr(instructions, selections)
const newHistoryElt: ChatMessage = { role: 'user', content: userContent, displayContent: instructions, selections }
threadsStateService.addMessageToCurrentThread(newHistoryElt)
const userHistoryElt: ChatMessage = { role: 'user', content: userContent, displayContent: instructions, selections }
threadsStateService.addMessageToCurrentThread(userHistoryElt)
const currentThread = threadsStateService.getCurrentThread(threadsStateService.state) // the the instant state right now, don't wait for the React state
@ -214,24 +191,24 @@ export const SidebarChat = () => {
const object: LLMMessageServiceParams = {
logging: { loggingName: 'Chat' },
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })),],
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content || '(null)' })),],
onText: ({ newText, fullText }) => setMessageStream(fullText),
onFinalMessage: ({ fullText: content }) => {
console.log('chat: running final message')
// add assistant's message to chat history, and clear selection
const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content }
threadsStateService.addMessageToCurrentThread(newHistoryElt)
setMessageStream('')
const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null }
threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
setMessageStream(null)
setIsLoading(false)
},
onError: ({ error }) => {
console.log('chat: running error', error)
// add assistant's message to chat history, and clear selection
let content = messageStream; // just use the current content
const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
threadsStateService.addMessageToCurrentThread(newHistoryElt)
let content = messageStream ?? ''; // just use the current content
const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null, }
threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
setMessageStream('')
setIsLoading(false)
@ -259,9 +236,9 @@ export const SidebarChat = () => {
sendLLMMessageService.abort(latestRequestIdRef.current)
// if messageStream was not empty, add it to the history
const llmContent = messageStream || '(empty)'
const newHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream, }
threadsStateService.addMessageToCurrentThread(newHistoryElt)
const llmContent = messageStream ?? ''
const assistantHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream || null, }
threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
setMessageStream('')
setIsLoading(false)
@ -280,7 +257,7 @@ export const SidebarChat = () => {
<ChatBubble key={i} chatMessage={message} />
)}
{/* message stream */}
<ChatBubble chatMessage={{ role: 'assistant', content: messageStream, displayContent: messageStream }} />
<ChatBubble chatMessage={{ role: 'assistant', content: messageStream , displayContent: messageStream || null }} />
</div>
{/* chatbar */}
<div className="shrink-0 py-4">
@ -331,7 +308,7 @@ export const SidebarChat = () => {
<button
onClick={onAbort}
type='button'
className="btn btn-primary font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
className="font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
>
<svg
className='scale-50'
@ -342,7 +319,7 @@ export const SidebarChat = () => {
:
// submit button (up arrow)
<button
className="btn btn-primary font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
className="font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
disabled={isDisabled}
type='submit'
>

View file

@ -56,21 +56,28 @@ export const SidebarThreadSelector = () => {
let btnStringArr: string[] = []
let msg1 = truncate(allThreads[threadId].messages[0]?.displayContent ?? '(empty)')
btnStringArr.push(msg1)
const firstUserMsg = allThreads[threadId].messages.find(msg => msg.role === 'user')?.displayContent ?? ''
let msg1 = truncate(firstUserMsg)
if (msg1)
btnStringArr.push(msg1)
else
btnStringArr.push('""')
let msg2 = truncate(allThreads[threadId].messages[1]?.displayContent ?? '')
const firstAssistantMsg = allThreads[threadId].messages.find(msg => msg.role === 'assistant')?.displayContent ?? ''
let msg2 = truncate(firstAssistantMsg)
if (msg2)
btnStringArr.push(msg2)
btnStringArr.push(allThreads[threadId].messages.length + '')
const numMessages = allThreads[threadId].messages.filter(msg => msg.role !== 'system').length
if (firstUserMsg && firstAssistantMsg)
btnStringArr.push((numMessages - 2) + '')
const btnString = btnStringArr.join(' / ')
return (
<button
key={pastThread.id}
className={`btn btn-sm rounded-sm ${pastThread.id === threadsStateService.getCurrentThread(threadsState)?.id ? "btn-primary" : "btn-secondary"}`}
className={`rounded-sm`}
onClick={() => threadsStateService.switchToThread(pastThread.id)}
title={new Date(pastThread.createdAt).toLocaleString()}
>

View file

@ -23,17 +23,19 @@ export type CodeStagingSelection = {
fileURI: URI;
}
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
export type ChatMessage =
| {
role: 'user';
content: string; // content sent to the llm
displayContent: string; // content displayed to user
content: string | null; // content sent to the llm - yes, allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user - yes, allowed to be '', will be ignored
selections: CodeSelection[] | null; // the user's selection
}
| {
role: 'assistant';
content: string; // content received from LLM
displayContent: string | undefined; // content displayed to user (this is the same as content for now)
content: string | null; // content received from LLM - yes, allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user (this is the same as content for now) - yes, allowed to be '', will be ignored
}
| {
role: 'system';
@ -133,8 +135,8 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
}
switchToThread(threadId: string) {
// console.log('threadId', threadId)
// console.log('messages', this.state.allThreads[threadId].messages)
console.log('threadId', threadId)
console.log('messages', this.state.allThreads[threadId].messages)
this._setState({ _currentThreadId: threadId }, true)
}
@ -197,28 +199,3 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
registerSingleton(IThreadHistoryService, ThreadHistoryService, InstantiationType.Eager);
// [
// {
// "role": "system",
// "content": "\nYou are a coding assistant. You are given a list of relevant files `files`, a selection that the user is making `selection`, and instructions to follow `instructions`.\n\nPlease edit the selected file following the user's instructions (or, if appropriate, answer their question instead).\n\nAll changes made to files must be outputted in unified diff format.\nUnified diff format instructions:\n1. Each diff must begin with ```@@ ... @@```.\n2. Each line must start with a `+` or `-` or ` ` symbol.\n3. Make diffs more than a few lines.\n4. Make high-level diffs rather than many one-line diffs.\n\nHere's an example of unified diff format:\n\n```\n@@ ... @@\n-def factorial(n):\n- if n == 0:\n- return 1\n- else:\n- return n * factorial(n-1)\n+def factorial(number):\n+ if number == 0:\n+ return 1\n+ else:\n+ return number * factorial(number-1)\n```\n\nPlease create high-level diffs where you group edits together if they are near each other, like in the above example. Another way to represent the above example is to make many small line edits. However, this is less preferred, because the edits are not high-level. The edits are close together and should be grouped:\n\n```\n@@ ... @@ # This is less preferred because edits are close together and should be grouped:\n-def factorial(n):\n+def factorial(number):\n- if n == 0:\n+ if number == 0:\n return 1\n else:\n- return n * factorial(n-1)\n+ return number * factorial(number-1)\n```\n\n# Example 1:\n\nFILES\nselected file `test.ts`:\n```\nx = 1\n\n{{selection}}\n\nz = 3\n```\n\nSELECTION\n```const y = 2```\n\nINSTRUCTIONS\n```y = 3```\n\nEXPECTED RESULT\n\nWe should change the selection from ```y = 2``` to ```y = 3```.\n```\n@@ ... @@\n-x = 1\n-\n-y = 2\n+x = 1\n+\n+y = 3\n```\n\n# Example 2:\n\nFILES\nselected file `Sidebar.tsx`:\n```\nimport React from 'react';\nimport styles from './Sidebar.module.css';\n\ninterface SidebarProps {\n items: { label: string; href: string }[];\n onItemSelect?: (label: string) => void;\n onExtraButtonClick?: () => void;\n}\n\nconst Sidebar: React.FC<SidebarProps> = ({ items, onItemSelect, onExtraButtonClick }) => {\n return (\n <div className={styles.sidebar}>\n <ul>\n {items.map((item, index) => (\n <li key={index}>\n {{selection}}\n className={styles.sidebarButton}\n onClick={() => onItemSelect?.(item.label)}\n >\n {item.label}\n </button>\n </li>\n ))}\n </ul>\n <button className={styles.extraButton} onClick={onExtraButtonClick}>\n Extra Action\n </button>\n </div>\n );\n};\n\nexport default Sidebar;\n```\n\nSELECTION\n``` <button```\n\nINSTRUCTIONS\n```make all the buttons like this into divs```\n\nEXPECTED OUTPUT\n\nWe should change all the buttons like the one selected into a div component. Here is the change:\n```\n@@ ... @@\n-<div className={styles.sidebar}>\n-<ul>\n- {items.map((item, index) => (\n-\t<li key={index}>\n-\t <button\n-\t\tclassName={styles.sidebarButton}\n-\t\tonClick={() => onItemSelect?.(item.label)}\n-\t >\n-\t\t{item.label}\n-\t </button>\n-\t</li>\n- ))}\n-</ul>\n-<button className={styles.extraButton} onClick={onExtraButtonClick}>\n- Extra Action\n-</button>\n-</div>\n+<div className={styles.sidebar}>\n+<ul>\n+ {items.map((item, index) => (\n+\t<li key={index}>\n+\t <div\n+\t\tclassName={styles.sidebarButton}\n+\t\tonClick={() => onItemSelect?.(item.label)}\n+\t >\n+\t\t{item.label}\n+\t </div>\n+\t</li>\n+ ))}\n+</ul>\n+<div className={styles.extraButton} onClick={onExtraButtonClick}>\n+ Extra Action\n+</div>\n+</div>\n```\n"
// },
// {
// "role": "user",
// "content": "test",
// "displayContent": "test",
// "selections": null
// },
// {
// "role": "assistant",
// "content": {
// "requestId": "49d4c9e6-5e53-4768-a77e-5c297223fa9c",
// "fullText": "I apologize, but I don't have enough context to provide a meaningful response based on just the word \"test\". If you have a specific question or topic you'd like me to assist with, please provide more details or context so I can better understand how to help you. I'm here to engage in conversation and provide information to the best of my abilities."
// },
// "displayContent": {
// "requestId": "49d4c9e6-5e53-4768-a77e-5c297223fa9c",
// "fullText": "I apologize, but I don't have enough context to provide a meaningful response based on just the word \"test\". If you have a specific question or topic you'd like me to assist with, please provide more details or context so I can better understand how to help you. I'm here to engage in conversation and provide information to the best of my abilities."
// }
// }
// ]