mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
update message display
This commit is contained in:
parent
e1692758c6
commit
0855376e02
5 changed files with 39 additions and 79 deletions
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
|
|
|
|||
Loading…
Reference in a new issue