Skip to content

Commit

Permalink
Add feature for deleting chat messages+replies in a Response thread
Browse files Browse the repository at this point in the history
  • Loading branch information
sedwards2009 committed Dec 11, 2023
1 parent ec5efea commit d7be64c
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 8 deletions.
42 changes: 40 additions & 2 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func setupRouter() *gin.Engine {
r.POST("/api/model/scan", handleModelScanPost)
r.PUT("/api/session/:sessionId/modelSettings", handleSessionModelSettingsPut)
r.POST("/api/session/:sessionId/response/:responseId/message", handleNewMessagePost)
r.DELETE("/api/session/:sessionId/response/:responseId/message/:messageId", handleResponseMessageDelete)
r.POST("/api/session/:sessionId/response/:responseId/continue", handleMessageContinuePost)
r.POST("/api/session/:sessionId/response/:responseId/abort", handleResponseAbortPost)
r.GET("/api/template", handleTemplateOverviewGet)
Expand Down Expand Up @@ -375,7 +376,7 @@ func handleResponseDelete(c *gin.Context) {

session := sessionStorage.ReadSession(sessionId)
if session == nil {
c.String(http.StatusInternalServerError, fmt.Sprintf("Unable to find session with ID %s\n", sessionId))
c.String(http.StatusNotFound, fmt.Sprintf("Unable to find session with ID %s\n", sessionId))
return
}

Expand All @@ -384,7 +385,7 @@ func handleResponseDelete(c *gin.Context) {
return r.ID != responseId
})
if originalLength == len(session.Responses) {
c.String(http.StatusInternalServerError, fmt.Sprintf("Unable to find response with ID %s\n", responseId))
c.String(http.StatusNotFound, fmt.Sprintf("Unable to find response with ID %s\n", responseId))
return
}
sessionStorage.WriteSession(session)
Expand Down Expand Up @@ -564,6 +565,43 @@ func handleNewMessagePost(c *gin.Context) {
c.JSON(http.StatusOK, foundResponse)
}

func handleResponseMessageDelete(c *gin.Context) {
sessionId := c.Params.ByName("sessionId")
responseId := c.Params.ByName("responseId")
messageId := c.Params.ByName("messageId")

session := sessionStorage.ReadSession(sessionId)
if session == nil {
c.String(http.StatusNotFound, fmt.Sprintf("Unable to find session with ID %s\n", sessionId))
return
}

response := getResponseFromSessionByID(session, responseId)
if response == nil {
c.String(http.StatusNotFound, fmt.Sprintf("Unable to find respone with ID %s\n", responseId))
return
}

if deleteMessagePair(response, messageId) {
sessionStorage.WriteSession(session)
c.Status(http.StatusNoContent)
} else {
c.String(http.StatusNotFound, fmt.Sprintf("Unable to find message with ID %s\n", messageId))
return
}
}

func deleteMessagePair(response *data.Response, messageId string) bool {
messageIndex := slices.IndexFunc(response.Messages, func(m data.Message) bool {
return m.ID == messageId
})
if messageIndex == -1 {
return false
}
response.Messages = slices.Delete(response.Messages, messageIndex, messageIndex+2)
return true
}

func handleTemplateOverviewGet(c *gin.Context) {
templateOverview := templates.TemplateOverview()
c.JSON(http.StatusOK, templateOverview)
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/dataloading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ export async function deleteResponse(sessionId: string, responseId: string): Pro
return response.ok;
}

export async function deleteResponseMessage(sessionId: string, responseId: string, messageId: string): Promise<boolean> {
await flushQueues();
const response = await fetch(`${SERVER_BASE_URL}/session/${sessionId}/response/${responseId}/message/${messageId}`,
{method: "DELETE"});
return response.ok;
}

export async function newMessage(session: Session, responseId: string, reply: string): Promise<void> {
await flushQueues();

Expand Down
6 changes: 5 additions & 1 deletion frontend/src/responseeditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export interface Props {
onAbortClicked: () => void;
onContinueClicked: () => void;
onDeleteClicked: () => void;
onDeleteMessageClicked: (messageId: string) => void;
onReplySubmit: (replyText: string) => void;
}

export function ResponseEditor({response, modelOverview, presetOverview, templateOverview,
onAbortClicked, onContinueClicked, onDeleteClicked, onReplySubmit: onReply}: Props): JSX.Element {
onAbortClicked, onContinueClicked, onDeleteClicked, onDeleteMessageClicked,
onReplySubmit: onReply}: Props): JSX.Element {

const [isPromptOpen, setIsPromptOpen] = useState<boolean>(false);
const [reply, setReply] = useState<string>("");
Expand Down Expand Up @@ -103,6 +105,7 @@ export function ResponseEditor({response, modelOverview, presetOverview, templat
<ResponseMessage
message={response.messages[0]}
onContinueClicked={null}
onDeleteClicked={null}
/>
}
{response.messages.slice(1).map((m ,i) =>
Expand All @@ -111,6 +114,7 @@ export function ResponseEditor({response, modelOverview, presetOverview, templat
message={m}
onContinueClicked={supportsContinue && isSendEnabled && response.status === "Done" &&
response.messages.length-1 === i+1 ? onContinueClicked : null}
onDeleteClicked={isSendEnabled && response.status === "Done" && m.role == "User"? () => onDeleteMessageClicked(m.id) : null}
/>
)}
{isSendEnabled && response.status === "Done" &&
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/responsemessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import { Message } from "./data";
export interface Props {
message: Message;
onContinueClicked: (() => void) | null;
onDeleteClicked: (() => void) | null;
}

export function ResponseMessage({message, onContinueClicked}: Props): JSX.Element {
export function ResponseMessage({message, onContinueClicked, onDeleteClicked}: Props): JSX.Element {
const iconName = message.role === "Assistant" ? "fa-robot" : "fa-user";
return <div className="response-message">
<div className="response-message-gutter"><i className={"fas " + iconName}></i></div>
<div className="response-message-text">
<ReactMarkdown children={message.text} /><br />
<div className="response-message-controls">
{
onContinueClicked != null &&
<button className="compact small" onClick={onContinueClicked}>Continue</button>

}
{
onDeleteClicked != null &&
<button className="microtool danger" onClick={onDeleteClicked}><i className="fa fa-times"></i></button>
}
</div>
<ReactMarkdown children={message.text} /><br />
</div>
</div>;
}
13 changes: 11 additions & 2 deletions frontend/src/sessioneditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { ChangeEvent, KeyboardEventHandler, useEffect, useState } from "react";
import { ModelOverview, PresetOverview, Session, TemplateOverview, isSettingsValid } from "./data";
import { navigate } from "raviger";
import TextareaAutosize from "react-textarea-autosize";
import { loadSession, newResponse, setSessionPrompt, deleteResponse, SessionMonitor, SessionMonitorState,
setSessionModel, newMessage, setSessionTemplate, setSessionPreset, continueMessage, abortResponse } from "./dataloading";
import { loadSession, newResponse, setSessionPrompt, deleteResponse, deleteResponseMessage, SessionMonitor,
SessionMonitorState, setSessionModel, newMessage, setSessionTemplate, setSessionPreset, continueMessage,
abortResponse } from "./dataloading";
import { ResponseEditor } from "./responseeditor";
import { ModelSettings } from "./modelsettings";

Expand Down Expand Up @@ -97,6 +98,13 @@ export function SessionEditor({sessionId, modelOverview, presetOverview, templat
})();
};

const onDeleteMessageClicked = (responseId: string, messageId: string) => {
(async () => {
await deleteResponseMessage((session as Session).id, responseId, messageId);
await loadSessionData();
})();
};

const onSubmitClicked = () => {
(async () => {
await newResponse(session as Session);
Expand Down Expand Up @@ -189,6 +197,7 @@ export function SessionEditor({sessionId, modelOverview, presetOverview, templat
templateOverview={templateOverview}
onAbortClicked={() => onAbortClicked(r.id)}
onDeleteClicked={() => onDeleteClicked(r.id)}
onDeleteMessageClicked={(messageId: string) => onDeleteMessageClicked(r.id, messageId)}
onReplySubmit={text => onReplySubmit(r.id, text)}
onContinueClicked={() => onContinueClicked(r.id)}
/>)
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/theme/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@
& > .response-message-text {
flex-grow: 1;
overflow: hidden;
position: relative;
}

& > .response-message-text > P {
margin-top: 0px;
margin-bottom: 0px;
}
}

.response-message-controls {
float: right;
}

0 comments on commit d7be64c

Please sign in to comment.