From 323d80ef55d3487356d87f7ed23ee7123d4d25d9 Mon Sep 17 00:00:00 2001
From: Andrew Pareles
Date: Thu, 6 Feb 2025 03:02:46 -0800
Subject: [PATCH 01/34] add divs for html
---
.../browser/react/src/markdown/ChatMarkdownRender.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index 28cd2539..9c214055 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -193,11 +193,11 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
if (t.type === "html") {
return (
-
- {``}
+
+
{``}
{t.raw}
- {``}
-
+
{``}
+
)
}
From 826d148305ab15b52fd8de5717b35f1785344b54 Mon Sep 17 00:00:00 2001
From: Andrew Pareles
Date: Thu, 6 Feb 2025 03:03:14 -0800
Subject: [PATCH 02/34] html
---
.../void/browser/react/src/markdown/ChatMarkdownRender.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index 9c214055..6d25cb24 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -193,11 +193,11 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
if (t.type === "html") {
return (
-
+
)
}
From 63efa219f5014caafecac1de4c3a7a68cfca39fe Mon Sep 17 00:00:00 2001
From: Andrew Pareles
Date: Thu, 6 Feb 2025 03:04:48 -0800
Subject: [PATCH 03/34] div
---
.../void/browser/react/src/markdown/ChatMarkdownRender.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index 6d25cb24..0bbb573b 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -193,11 +193,11 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
if (t.type === "html") {
return (
-
+
)
}
From 5ce25e20c333284a655203bf28c6f3984495cddd Mon Sep 17 00:00:00 2001
From: Andrew Pareles
Date: Thu, 6 Feb 2025 03:10:07 -0800
Subject: [PATCH 04/34] css for md tags
---
.../browser/react/src/markdown/ChatMarkdownRender.tsx | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index 0bbb573b..ec0d2241 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -188,16 +188,14 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
>
if (nested)
return contents
- return {contents}
+ return {contents}
}
if (t.type === "html") {
return (
-
-
{``}
+
{t.raw}
-
{``}
-
+
)
}
From 4ffd269db5f923c2b30b23f7ab2b537118a3e8e4 Mon Sep 17 00:00:00 2001
From: Andrew Pareles
Date: Thu, 6 Feb 2025 03:11:26 -0800
Subject: [PATCH 05/34] +
---
.../browser/react/src/markdown/ChatMarkdownRender.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index ec0d2241..15f67d9a 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -186,9 +186,11 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
))}
>
- if (nested)
- return contents
- return {contents}
+ if (nested) return contents
+
+ return
+ {contents}
+
}
if (t.type === "html") {
From 387fd64db0e89b1ecb1490d8fca5870530132393 Mon Sep 17 00:00:00 2001
From: Mathew Pareles
Date: Thu, 6 Feb 2025 03:18:48 -0800
Subject: [PATCH 06/34] edit ability
---
.../contrib/void/browser/chatThreadService.ts | 151 ++++++++++++++++--
.../react/src/sidebar-tsx/SidebarChat.tsx | 104 +++++++++---
.../void/browser/react/src/util/services.tsx | 11 ++
.../contrib/void/browser/sidebarActions.ts | 32 ++--
4 files changed, 255 insertions(+), 43 deletions(-)
diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
index 34b4dad4..0027c42f 100644
--- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts
+++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
@@ -40,6 +40,7 @@ export type ChatMessage =
content: string | null; // content sent to the llm - allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user - allowed to be '', will be ignored
selections: StagingSelectionItem[] | null; // the user's selection
+ stagingSelections: StagingSelectionItem[] | null; // staging selections in edit mode
}
| {
role: 'assistant';
@@ -59,13 +60,14 @@ export type ChatThreads = {
createdAt: string; // ISO string
lastModified: string; // ISO string
messages: ChatMessage[];
+ stagingSelections: StagingSelectionItem[] | null;
+ focusedMessageIdx?: number | undefined; // index of the message that is being edited (undefined if none)
};
}
export type ThreadsState = {
allThreads: ChatThreads;
currentThreadId: string; // intended for internal use only
- currentStagingSelections: StagingSelectionItem[] | null;
}
export type ThreadStreamState = {
@@ -84,6 +86,8 @@ const newThreadObject = () => {
createdAt: now,
lastModified: now,
messages: [],
+ focusedMessageIdx: undefined,
+ stagingSelections: null,
} satisfies ChatThreads[string]
}
@@ -105,8 +109,12 @@ export interface IChatThreadService {
openNewThread(): void;
switchToThread(threadId: string): void;
- setStaging(stagingSelection: StagingSelectionItem[] | null): void;
+ getFocusedMessageIdx(): number | undefined;
+ setFocusedMessageIdx(messageIdx: number | undefined): void;
+ _useStagingSelectionsState(messageIdx?: number | undefined): readonly [StagingSelectionItem[], (selections: StagingSelectionItem[]) => void];
+
+ editUserMessageAndStreamResponse(userMessage: string, messageIdx: number): Promise;
addUserMessageAndStreamResponse(userMessage: string): Promise;
cancelStreaming(threadId: string): void;
dismissStreamError(threadId: string): void;
@@ -137,7 +145,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this.state = {
allThreads: this._readAllThreads(),
currentThreadId: null as unknown as string, // gets set in startNewThread()
- currentStagingSelections: null,
}
// always be in a thread
@@ -187,18 +194,50 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error })
}
- async addUserMessageAndStreamResponse(userMessage: string) {
- const threadId = this.getCurrentThread().id
- const currSelns = this.state.currentStagingSelections ?? []
+ async editUserMessageAndStreamResponse(userMessage: string, messageIdx: number) {
+
+ const thread = this.getCurrentThread()
+
+ const messageToReplace = thread.messages[messageIdx]
+ if (messageToReplace?.role !== 'user') {
+ console.log(`Error: tried to edit non-user message. messageIdx=${messageIdx}, numMessages=${thread.messages.length}`)
+ return
+ }
+
+ // clear messages up to the index
+ const slicedMessages = thread.messages.slice(0, messageIdx)
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ messages: slicedMessages
+ }
+ }
+ }, true)
+
+ // stream the edit
+ this.addUserMessageAndStreamResponse(userMessage, messageToReplace.stagingSelections)
+
+ }
+
+ async addUserMessageAndStreamResponse(userMessage: string, selectionsOverride?: StagingSelectionItem[] | null) {
+
+
+ const thread = this.getCurrentThread()
+ const threadId = thread.id
+
+ let defaultThreadSelections = thread.stagingSelections
+
+ const currSelns = selectionsOverride ?? defaultThreadSelections ?? [] // don't use _useFocusedStagingState to avoid race conditions with focusing
// add user's message to chat history
const instructions = userMessage
const content = await chat_userMessage(instructions, currSelns, this._modelService)
- const userHistoryElt: ChatMessage = { role: 'user', content: content, displayContent: instructions, selections: currSelns }
+ const userHistoryElt: ChatMessage = { role: 'user', content: content, displayContent: instructions, selections: currSelns, stagingSelections: [], }
this._addMessageToThread(threadId, userHistoryElt)
-
this._setStreamState(threadId, { error: undefined })
const llmCancelToken = this._llmMessageService.sendLLMMessage({
@@ -210,12 +249,15 @@ class ChatThreadService extends Disposable implements IChatThreadService {
...this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(null)' })),
],
onText: ({ newText, fullText }) => {
+ console.log('onText', fullText)
this._setStreamState(threadId, { messageSoFar: fullText })
},
onFinalMessage: ({ fullText: content }) => {
+ console.log('finalMessage', JSON.stringify(content))
this.finishStreaming(threadId, content)
},
onError: (error) => {
+ console.log('onError', content)
this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '', error)
},
@@ -241,7 +283,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
getCurrentThread(): ChatThreads[string] {
const state = this.state
- return state.allThreads[state.currentThreadId];
+ return state.allThreads[state.currentThreadId]
+ }
+
+ getFocusedMessageIdx() {
+ const thread = this.getCurrentThread()
+ return thread.focusedMessageIdx
}
switchToThread(threadId: string) {
@@ -291,11 +338,93 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this._setState({ allThreads: newThreads }, true) // the current thread just changed (it had a message added to it)
}
+ // sets the currently selected message (must be undefined if no message is selected)
+ setFocusedMessageIdx(messageIdx: number | undefined) {
- setStaging(stagingSelection: StagingSelectionItem[] | null): void {
- this._setState({ currentStagingSelections: stagingSelection }, true) // this is a hack for now
+ const threadId = this.state.currentThreadId
+ const thread = this.state.allThreads[threadId]
+ if (!thread) return
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [threadId]: {
+ ...thread,
+ focusedMessageIdx: messageIdx
+ }
+ }
+ }, true)
}
+ // set thread.messages[messageIdx].stagingSelections
+ private setEditMessageStagingSelections(stagingSelections: StagingSelectionItem[], messageIdx: number): void {
+
+ const thread = this.getCurrentThread()
+ const message = thread.messages[messageIdx]
+ if (message.role !== 'user') return;
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ messages: thread.messages.map((m, i) =>
+ i === messageIdx ? { ...m, stagingSelections } : m
+ )
+ }
+ }
+ }, true)
+
+ }
+
+ // set thread.stagingSelections
+ private setDefaultStagingSelections(stagingSelections: StagingSelectionItem[]): void {
+
+
+ console.log('Default1')
+ const thread = this.getCurrentThread()
+
+ console.log('Default2')
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ stagingSelections
+ }
+ }
+ }, true)
+
+ }
+
+ // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected)
+ _useStagingSelectionsState(messageIdx?: number | undefined) {
+
+ let staging: StagingSelectionItem[] = []
+ let setStaging: (selections: StagingSelectionItem[]) => void = () => { }
+
+ const thread = this.getCurrentThread()
+ const isFocusingMessage = messageIdx !== undefined
+ if (isFocusingMessage) { // is editing message
+
+ const message = thread.messages[messageIdx!]
+ if (message.role === 'user') {
+ staging = message.stagingSelections || []
+ setStaging = (s) => this.setEditMessageStagingSelections(s, messageIdx)
+ }
+
+ }
+ else { // is editing the default input box
+ staging = thread.stagingSelections || []
+ setStaging = this.setDefaultStagingSelections.bind(this)
+ }
+
+ return [staging, setStaging] as const
+ }
+
+
+
}
registerSingleton(IChatThreadService, ChatThreadService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
index 7b46422a..f175c300 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
@@ -25,6 +25,7 @@ import { Pencil } from 'lucide-react';
import { FeatureName } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
+
export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps) => {
return (