Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions api/chat_message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/gorilla/mux"
"github.com/samber/lo"
"github.com/swuecho/chat_backend/models"
"github.com/swuecho/chat_backend/sqlc_queries"
)

Expand All @@ -32,6 +33,7 @@ func (h *ChatMessageHandler) Register(router *mux.Router) {
router.HandleFunc("/uuid/chat_messages/{uuid}", h.GetChatMessageByUUID).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/{uuid}", h.UpdateChatMessageByUUID).Methods(http.MethodPut)
router.HandleFunc("/uuid/chat_messages/{uuid}", h.DeleteChatMessageByUUID).Methods(http.MethodDelete)
router.HandleFunc("/uuid/chat_messages/{uuid}/generate-suggestions", h.GenerateMoreSuggestions).Methods(http.MethodPost)
router.HandleFunc("/uuid/chat_messages/chat_sessions/{uuid}", h.GetChatHistoryBySessionUUID).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/chat_sessions/{uuid}", h.DeleteChatMessagesBySesionUUID).Methods(http.MethodDelete)
}
Expand Down Expand Up @@ -231,3 +233,112 @@ func (h *ChatMessageHandler) DeleteChatMessagesBySesionUUID(w http.ResponseWrite
}
w.WriteHeader(http.StatusOK)
}

// GenerateMoreSuggestions generates additional suggested questions for a message
func (h *ChatMessageHandler) GenerateMoreSuggestions(w http.ResponseWriter, r *http.Request) {
messageUUID := mux.Vars(r)["uuid"]

// Get the existing message
message, err := h.service.q.GetChatMessageByUUID(r.Context(), messageUUID)
if err != nil {
http.Error(w, "Message not found", http.StatusNotFound)
return
}

// Only allow suggestions for assistant messages
if message.Role != "assistant" {
http.Error(w, "Suggestions can only be generated for assistant messages", http.StatusBadRequest)
return
}

// Get the session to check if explore mode is enabled
session, err := h.service.q.GetChatSessionByUUID(r.Context(), message.ChatSessionUuid)
if err != nil {
http.Error(w, "Session not found", http.StatusNotFound)
return
}

// Check if explore mode is enabled
if !session.ExploreMode {
http.Error(w, "Suggestions are only available in explore mode", http.StatusBadRequest)
return
}

// Get conversation context - last 6 messages
contextMessages, err := h.service.q.GetLatestMessagesBySessionUUID(r.Context(),
sqlc_queries.GetLatestMessagesBySessionUUIDParams{
ChatSessionUuid: session.Uuid,
Limit: 6,
})
if err != nil {
http.Error(w, "Failed to get conversation context", http.StatusInternalServerError)
return
}

// Convert to models.Message format for suggestion generation
var msgs []models.Message
for _, msg := range contextMessages {
msgs = append(msgs, models.Message{
Role: msg.Role,
Content: msg.Content,
})
}

// Create a new ChatService to access suggestion generation methods
chatService := NewChatService(h.service.q)

// Generate new suggested questions
newSuggestions := chatService.generateSuggestedQuestions(message.Content, msgs)
if len(newSuggestions) == 0 {
http.Error(w, "Failed to generate suggestions", http.StatusInternalServerError)
return
}

// Parse existing suggestions
var existingSuggestions []string
if len(message.SuggestedQuestions) > 0 {
if err := json.Unmarshal(message.SuggestedQuestions, &existingSuggestions); err != nil {
// If unmarshal fails, treat as empty array
existingSuggestions = []string{}
}
}

// Combine existing and new suggestions (avoiding duplicates)
allSuggestions := append(existingSuggestions, newSuggestions...)

// Remove duplicates
seenSuggestions := make(map[string]bool)
var uniqueSuggestions []string
for _, suggestion := range allSuggestions {
if !seenSuggestions[suggestion] {
seenSuggestions[suggestion] = true
uniqueSuggestions = append(uniqueSuggestions, suggestion)
}
}

// Update the message with new suggestions
suggestionsJSON, err := json.Marshal(uniqueSuggestions)
if err != nil {
http.Error(w, "Failed to serialize suggestions", http.StatusInternalServerError)
return
}

_, err = h.service.q.UpdateChatMessageSuggestions(r.Context(),
sqlc_queries.UpdateChatMessageSuggestionsParams{
Uuid: messageUUID,
SuggestedQuestions: suggestionsJSON,
})
if err != nil {
http.Error(w, "Failed to update message with suggestions", http.StatusInternalServerError)
return
}

// Return the new suggestions to the client
response := map[string]interface{}{
"newSuggestions": newSuggestions,
"allSuggestions": uniqueSuggestions,
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
5 changes: 5 additions & 0 deletions api/sqlc/queries/chat_message.sql
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ UPDATE chat_message
SET content = $2, updated_at = now(), token_count = $3
WHERE uuid = $1 ;

-- name: UpdateChatMessageSuggestions :one
UPDATE chat_message
SET suggested_questions = $2, updated_at = now()
WHERE uuid = $1
RETURNING *;

-- name: DeleteChatMessagesBySesionUUID :exec
UPDATE chat_message
Expand Down
40 changes: 40 additions & 0 deletions api/sqlc_queries/chat_message.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions web/src/api/chat_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ export const getChatMessagesBySessionUUID = async (uuid: string) => {
throw error
}
}

export const generateMoreSuggestions = async (messageUuid: string) => {
try {
const response = await request.post(`/uuid/chat_messages/${messageUuid}/generate-suggestions`)
return response.data
}
catch (error) {
console.error(error)
throw error
}
}
113 changes: 111 additions & 2 deletions web/src/store/modules/message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import {
getChatMessagesBySessionUUID,
clearSessionChatMessages,
generateMoreSuggestions,
} from '@/api'
import { useSessionStore } from '../session'

Expand Down Expand Up @@ -60,7 +61,30 @@ export const useMessageStore = defineStore('message-store', {

try {
const messageData = await getChatMessagesBySessionUUID(sessionUuid)
this.chat[sessionUuid] = messageData

// Initialize batching structure for messages with suggested questions
const processedMessageData = messageData.map((message: Chat.Message) => {
if (message.suggestedQuestions && message.suggestedQuestions.length > 0) {
// If batches don't exist, create the first batch from existing questions
if (!message.suggestedQuestionsBatches || message.suggestedQuestionsBatches.length === 0) {
// Split suggestions into batches of 3 (assuming original suggestions come in groups of 3)
const batches: string[][] = []
for (let i = 0; i < message.suggestedQuestions.length; i += 3) {
batches.push(message.suggestedQuestions.slice(i, i + 3))
}

return {
...message,
suggestedQuestionsBatches: batches,
currentSuggestedQuestionsBatch: batches.length - 1, // Show the last batch (most recent)
suggestedQuestions: batches[batches.length - 1] || message.suggestedQuestions, // Show last batch
}
}
}
return message
})

this.chat[sessionUuid] = processedMessageData

// Update active session if needed
const sessionStore = useSessionStore()
Expand All @@ -71,7 +95,7 @@ export const useMessageStore = defineStore('message-store', {
}
}

return messageData
return processedMessageData
} catch (error) {
console.error(`Failed to sync messages for session ${sessionUuid}:`, error)
throw error
Expand Down Expand Up @@ -221,5 +245,90 @@ export const useMessageStore = defineStore('message-store', {
const messages = this.chat[sessionUuid] || []
return messages.filter(msg => msg.isPrompt)
},

// Generate more suggested questions for a message
async generateMoreSuggestedQuestions(sessionUuid: string, messageUuid: string) {
try {
// Set generating state for the message
this.updateMessage(sessionUuid, messageUuid, { suggestedQuestionsGenerating: true })

const response = await generateMoreSuggestions(messageUuid)
const { newSuggestions, allSuggestions } = response

// Get existing message
const messages = this.chat[sessionUuid] || []
const messageIndex = messages.findIndex(msg => msg.uuid === messageUuid)

if (messageIndex !== -1) {
const message = messages[messageIndex]

// Initialize batches if they don't exist
let suggestedQuestionsBatches = message.suggestedQuestionsBatches || []

// If this is the first time, create the first batch from existing questions
if (suggestedQuestionsBatches.length === 0 && message.suggestedQuestions) {
suggestedQuestionsBatches.push(message.suggestedQuestions)
}

// Add the new suggestions as a new batch
suggestedQuestionsBatches.push(newSuggestions)

// Update the message with new data - show the new batch, not all suggestions
this.updateMessage(sessionUuid, messageUuid, {
suggestedQuestions: newSuggestions, // Show only the new batch
suggestedQuestionsBatches,
currentSuggestedQuestionsBatch: suggestedQuestionsBatches.length - 1, // Set to the new batch
suggestedQuestionsGenerating: false,
})
}

return response
} catch (error) {
// Clear generating state on error
this.updateMessage(sessionUuid, messageUuid, { suggestedQuestionsGenerating: false })
console.error('Failed to generate more suggestions:', error)
throw error
}
},

// Navigate to previous suggestions batch
previousSuggestedQuestionsBatch(sessionUuid: string, messageUuid: string) {
const messages = this.chat[sessionUuid] || []
const messageIndex = messages.findIndex(msg => msg.uuid === messageUuid)

if (messageIndex !== -1) {
const message = messages[messageIndex]
const batches = message.suggestedQuestionsBatches || []
const currentBatch = message.currentSuggestedQuestionsBatch || 0

if (currentBatch > 0 && batches.length > 0) {
const newBatchIndex = currentBatch - 1
this.updateMessage(sessionUuid, messageUuid, {
suggestedQuestions: batches[newBatchIndex],
currentSuggestedQuestionsBatch: newBatchIndex,
})
}
}
},

// Navigate to next suggestions batch
nextSuggestedQuestionsBatch(sessionUuid: string, messageUuid: string) {
const messages = this.chat[sessionUuid] || []
const messageIndex = messages.findIndex(msg => msg.uuid === messageUuid)

if (messageIndex !== -1) {
const message = messages[messageIndex]
const batches = message.suggestedQuestionsBatches || []
const currentBatch = message.currentSuggestedQuestionsBatch || 0

if (currentBatch < batches.length - 1) {
const newBatchIndex = currentBatch + 1
this.updateMessage(sessionUuid, messageUuid, {
suggestedQuestions: batches[newBatchIndex],
currentSuggestedQuestionsBatch: newBatchIndex,
})
}
}
},
},
})
3 changes: 3 additions & 0 deletions web/src/typings/chat.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ declare namespace Chat {
artifacts?: Artifact[]
suggestedQuestions?: string[]
suggestedQuestionsLoading?: boolean
suggestedQuestionsBatches?: string[][]
currentSuggestedQuestionsBatch?: number
suggestedQuestionsGenerating?: boolean
}

interface Session {
Expand Down
Loading