readysite / website / internal / assist / conversation.go
4.5 KB
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)
}
← Back