conversation.go
package assist
import (
"encoding/json"
"log"
"net/http"
"github.com/readysite/readysite/website/models"
)
// GetConversation finds conversation from path param or query param.
// Checks path parameter first (/admin/chat/{id}), then query param (/admin?chat=id).
func GetConversation(r *http.Request) *models.Conversation {
id := r.PathValue("id")
if id == "" {
id = r.URL.Query().Get("chat")
}
if id == "" {
return nil
}
conv, err := models.Conversations.Get(id)
if err != nil {
log.Printf("[ASSIST] GetConversation: not found id=%s err=%v", id, err)
return nil
}
return conv
}
// GetConversationByID finds conversation by ID.
func GetConversationByID(id string) *models.Conversation {
if id == "" {
return nil
}
conv, err := models.Conversations.Get(id)
if err != nil {
log.Printf("[ASSIST] GetConversationByID: not found id=%s err=%v", id, err)
return nil
}
return conv
}
// GetUserConversations returns all conversations for a user.
func GetUserConversations(userID string) []*models.Conversation {
conversations, _ := models.Conversations.Search("WHERE UserID = ? ORDER BY UpdatedAt DESC", userID)
return conversations
}
// GetAllConversations returns all conversations ordered by most recent.
func GetAllConversations() []*models.Conversation {
conversations, _ := models.Conversations.Search("ORDER BY UpdatedAt DESC")
return conversations
}
// GetMessages returns all messages for a conversation.
func GetMessages(conv *models.Conversation) []*models.Message {
if conv == nil {
return nil
}
messages, _ := conv.Messages()
return messages
}
// StreamingMessageID returns ID of pending/streaming message (if any).
func StreamingMessageID(conv *models.Conversation) string {
if conv == nil {
return ""
}
msg, err := models.Messages.First(
"WHERE ConversationID = ? AND Status IN ('pending', 'streaming') ORDER BY CreatedAt DESC",
conv.ID)
if err != nil || msg == nil {
return ""
}
log.Printf("[ASSIST] StreamingMessageID: found msg=%s status=%s conv=%s", msg.ID, msg.Status, conv.ID)
return msg.ID
}
// ConvCanUndo returns true if the last AI action can be undone.
func ConvCanUndo(conv *models.Conversation) bool {
if conv == nil {
return false
}
messages, _ := conv.Messages()
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Role == RoleAssistant {
mutations, _ := messages[i].Mutations()
for _, m := range mutations {
if CanUndo(m) {
return true
}
}
break
}
}
return false
}
// ConvCanRedo returns true if there's an undone action that can be redone.
func ConvCanRedo(conv *models.Conversation) bool {
if conv == nil {
return false
}
messages, _ := conv.Messages()
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Role == RoleAssistant {
mutations, _ := messages[i].Mutations()
for _, m := range mutations {
if CanRedo(m) {
return true
}
}
break
}
}
return false
}
// GetCurrentConversation returns the most recent conversation for a user.
func GetCurrentConversation(r *http.Request, userID string) *models.Conversation {
// Check path param first (for /admin/chat/{id}/panel route)
chatID := r.PathValue("id")
if chatID != "" {
conv, err := models.Conversations.Get(chatID)
if err == nil {
log.Printf("[ASSIST] GetCurrentConversation: found by path id=%s", chatID)
return conv
}
}
// Get most recent for user
if userID != "" {
conv, err := models.Conversations.First("WHERE UserID = ? ORDER BY UpdatedAt DESC", userID)
if err == nil && conv != nil {
log.Printf("[ASSIST] GetCurrentConversation: found most recent id=%s title=%s for user=%s", conv.ID, conv.Title, userID)
return conv
}
log.Printf("[ASSIST] GetCurrentConversation: no conversation found for user=%s", userID)
}
return nil
}
// GetConversationModel returns the model override for a conversation, if any.
func GetConversationModel(conv *models.Conversation) string {
if conv == nil || conv.Context == "" {
return ""
}
var ctx ConversationContext
if err := json.Unmarshal([]byte(conv.Context), &ctx); err != nil {
return ""
}
return ctx.Model
}
// SetConversationModel sets the model override for a conversation.
func SetConversationModel(conv *models.Conversation, model string) error {
if conv == nil {
return nil
}
var ctx ConversationContext
if conv.Context != "" {
json.Unmarshal([]byte(conv.Context), &ctx)
}
ctx.Model = model
data, err := json.Marshal(ctx)
if err != nil {
return err
}
conv.Context = string(data)
return models.Conversations.Update(conv)
}