Changeset 0.8.0 (#39)
This commit is contained in:
@@ -113,7 +113,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Load conversation history
|
||||
messages, err := h.loadConversation(channelID, presetSystemPrompt)
|
||||
messages, err := h.loadConversation(channelID, userID, presetSystemPrompt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load conversation"})
|
||||
return
|
||||
@@ -126,7 +126,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
})
|
||||
|
||||
// Persist user message
|
||||
if err := h.persistMessage(channelID, "user", req.Content, "", 0, 0); err != nil {
|
||||
if _, err := h.persistMessage(channelID, userID, "user", req.Content, "", 0, 0, nil); err != nil {
|
||||
log.Printf("Failed to persist user message: %v", err)
|
||||
}
|
||||
|
||||
@@ -160,9 +160,9 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
}
|
||||
|
||||
if stream {
|
||||
h.streamCompletion(c, provider, providerCfg, provReq, channelID, model)
|
||||
h.streamCompletion(c, provider, providerCfg, provReq, channelID, userID, model)
|
||||
} else {
|
||||
h.syncCompletion(c, provider, providerCfg, provReq, channelID, model)
|
||||
h.syncCompletion(c, provider, providerCfg, provReq, channelID, userID, model)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (h *CompletionHandler) streamCompletion(
|
||||
provider providers.Provider,
|
||||
cfg providers.ProviderConfig,
|
||||
req providers.CompletionRequest,
|
||||
channelID, model string,
|
||||
channelID, userID, model string,
|
||||
) {
|
||||
ch, err := provider.StreamCompletion(c.Request.Context(), cfg, req)
|
||||
if err != nil {
|
||||
@@ -228,7 +228,7 @@ func (h *CompletionHandler) streamCompletion(
|
||||
|
||||
// Persist assistant response
|
||||
if fullContent != "" {
|
||||
if err := h.persistMessage(channelID, "assistant", fullContent, model, 0, 0); err != nil {
|
||||
if _, err := h.persistMessage(channelID, userID, "assistant", fullContent, model, 0, 0, nil); err != nil {
|
||||
log.Printf("Failed to persist assistant message: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (h *CompletionHandler) syncCompletion(
|
||||
provider providers.Provider,
|
||||
cfg providers.ProviderConfig,
|
||||
req providers.CompletionRequest,
|
||||
channelID, model string,
|
||||
channelID, userID, model string,
|
||||
) {
|
||||
resp, err := provider.ChatCompletion(c.Request.Context(), cfg, req)
|
||||
if err != nil {
|
||||
@@ -250,7 +250,7 @@ func (h *CompletionHandler) syncCompletion(
|
||||
}
|
||||
|
||||
// Persist assistant response
|
||||
if err := h.persistMessage(channelID, "assistant", resp.Content, model, resp.InputTokens, resp.OutputTokens); err != nil {
|
||||
if _, err := h.persistMessage(channelID, userID, "assistant", resp.Content, model, resp.InputTokens, resp.OutputTokens, nil); err != nil {
|
||||
log.Printf("Failed to persist assistant message: %v", err)
|
||||
}
|
||||
|
||||
@@ -394,7 +394,10 @@ func (h *CompletionHandler) resolveConfig(userID string, channelID string, req c
|
||||
|
||||
// ── Conversation Loader ─────────────────────
|
||||
|
||||
func (h *CompletionHandler) loadConversation(channelID string, presetSystemPrompt string) ([]providers.Message, error) {
|
||||
// loadConversation builds the message history for the LLM by walking the
|
||||
// active path through the message tree. Only the current branch is sent —
|
||||
// sibling branches are never included in context.
|
||||
func (h *CompletionHandler) loadConversation(channelID, userID, presetSystemPrompt string) ([]providers.Message, error) {
|
||||
// Load system prompt from channel
|
||||
var systemPrompt *string
|
||||
_ = database.DB.QueryRow(
|
||||
@@ -416,23 +419,20 @@ func (h *CompletionHandler) loadConversation(channelID string, presetSystemPromp
|
||||
})
|
||||
}
|
||||
|
||||
// Load message history (oldest first)
|
||||
rows, err := database.DB.Query(`
|
||||
SELECT role, content FROM messages
|
||||
WHERE channel_id = $1
|
||||
ORDER BY created_at ASC
|
||||
`, channelID)
|
||||
// Walk the active path (root → leaf) instead of loading all messages
|
||||
path, err := getActivePath(channelID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var msg providers.Message
|
||||
if err := rows.Scan(&msg.Role, &msg.Content); err != nil {
|
||||
return nil, err
|
||||
for _, m := range path {
|
||||
if m.Role == "system" {
|
||||
continue // system prompts handled above
|
||||
}
|
||||
messages = append(messages, msg)
|
||||
messages = append(messages, providers.Message{
|
||||
Role: m.Role,
|
||||
Content: m.Content,
|
||||
})
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
@@ -440,42 +440,59 @@ func (h *CompletionHandler) loadConversation(channelID string, presetSystemPromp
|
||||
|
||||
// ── Message Persistence ─────────────────────
|
||||
|
||||
func (h *CompletionHandler) persistMessage(channelID, role, content, model string, inputTokens, outputTokens int) error {
|
||||
// persistMessage inserts a message into the tree, updates the cursor, and
|
||||
// returns the new message's ID.
|
||||
//
|
||||
// Parent resolution:
|
||||
// - parentOverride (explicit parent for edit/regen operations)
|
||||
// - cursor's active_leaf_id (normal conversation flow)
|
||||
// - latest message by created_at (fallback for legacy/missing cursor)
|
||||
func (h *CompletionHandler) persistMessage(channelID, userID, role, content, model string, inputTokens, outputTokens int, parentOverride *string) (string, error) {
|
||||
var tokensUsed *int
|
||||
if inputTokens > 0 || outputTokens > 0 {
|
||||
total := inputTokens + outputTokens
|
||||
tokensUsed = &total
|
||||
}
|
||||
|
||||
// Find current leaf for parent_id
|
||||
// Determine parent
|
||||
var parentID *string
|
||||
_ = database.DB.QueryRow(
|
||||
`SELECT id FROM messages WHERE channel_id = $1 ORDER BY created_at DESC LIMIT 1`,
|
||||
channelID,
|
||||
).Scan(&parentID)
|
||||
if parentOverride != nil {
|
||||
parentID = parentOverride
|
||||
} else {
|
||||
parentID, _ = getActiveLeaf(channelID, userID)
|
||||
}
|
||||
|
||||
// Compute sibling_index
|
||||
siblingIdx := nextSiblingIndex(channelID, parentID)
|
||||
|
||||
// Determine participant
|
||||
participantType := "user"
|
||||
participantID := channelID // will be overridden below
|
||||
participantID := userID
|
||||
if role == "assistant" {
|
||||
participantType = "model"
|
||||
participantID = model
|
||||
} else {
|
||||
// Get channel owner as participant_id for user messages
|
||||
var ownerID string
|
||||
if err := database.DB.QueryRow(`SELECT user_id FROM channels WHERE id = $1`, channelID).Scan(&ownerID); err == nil {
|
||||
participantID = ownerID
|
||||
}
|
||||
}
|
||||
|
||||
_, err := database.DB.Exec(`
|
||||
INSERT INTO messages (channel_id, role, content, model, tokens_used, parent_id, participant_type, participant_id)
|
||||
VALUES ($1, $2, $3, NULLIF($4, ''), $5, $6, $7, $8)
|
||||
`, channelID, role, content, model, tokensUsed, parentID, participantType, participantID)
|
||||
// Insert with RETURNING id
|
||||
var newID string
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, model, tokens_used,
|
||||
parent_id, participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, $2, $3, NULLIF($4, ''), $5, $6, $7, $8, $9)
|
||||
RETURNING id
|
||||
`, channelID, role, content, model, tokensUsed,
|
||||
parentID, participantType, participantID, siblingIdx,
|
||||
).Scan(&newID)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Update cursor to point at new message
|
||||
if userID != "" {
|
||||
_ = updateCursor(channelID, userID, newID)
|
||||
}
|
||||
|
||||
// Touch channel updated_at
|
||||
_, _ = database.DB.Exec(`UPDATE channels SET updated_at = NOW() WHERE id = $1`, channelID)
|
||||
return nil
|
||||
return newID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user