Changeset 0.15.0 (#71)
This commit is contained in:
114
server/treepath/siblings.go
Normal file
114
server/treepath/siblings.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package treepath
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
)
|
||||
|
||||
// GetSiblingCount returns how many live siblings share the same parent_id.
|
||||
// Root messages (parent_id IS NULL) count all roots in the same channel.
|
||||
func GetSiblingCount(channelID string, parentID *string) int {
|
||||
var count int
|
||||
if parentID == nil {
|
||||
database.DB.QueryRow(`
|
||||
SELECT COUNT(*) FROM messages
|
||||
WHERE channel_id = $1 AND parent_id IS NULL AND deleted_at IS NULL
|
||||
`, channelID).Scan(&count)
|
||||
} else {
|
||||
database.DB.QueryRow(`
|
||||
SELECT COUNT(*) FROM messages
|
||||
WHERE parent_id = $1 AND deleted_at IS NULL
|
||||
`, *parentID).Scan(&count)
|
||||
}
|
||||
if count == 0 {
|
||||
count = 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// GetSiblings returns all live siblings of a given message (including itself),
|
||||
// ordered by sibling_index.
|
||||
func GetSiblings(messageID string) ([]SiblingInfo, int, error) {
|
||||
// First get the parent_id of the target message
|
||||
var parentID *string
|
||||
var channelID string
|
||||
err := database.DB.QueryRow(`
|
||||
SELECT parent_id, channel_id FROM messages
|
||||
WHERE id = $1 AND deleted_at IS NULL
|
||||
`, messageID).Scan(&parentID, &channelID)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("message not found: %w", err)
|
||||
}
|
||||
|
||||
var rows *sql.Rows
|
||||
if parentID == nil {
|
||||
// Root messages: siblings are other roots in the same channel
|
||||
rows, err = database.DB.Query(`
|
||||
SELECT id, role, model, sibling_index, LEFT(content, 80), created_at
|
||||
FROM messages
|
||||
WHERE channel_id = $1 AND parent_id IS NULL AND deleted_at IS NULL
|
||||
ORDER BY sibling_index, created_at
|
||||
`, channelID)
|
||||
} else {
|
||||
rows, err = database.DB.Query(`
|
||||
SELECT id, role, model, sibling_index, LEFT(content, 80), created_at
|
||||
FROM messages
|
||||
WHERE parent_id = $1 AND deleted_at IS NULL
|
||||
ORDER BY sibling_index, created_at
|
||||
`, *parentID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var siblings []SiblingInfo
|
||||
currentIdx := 0
|
||||
for i := 0; rows.Next(); i++ {
|
||||
var s SiblingInfo
|
||||
if err := rows.Scan(&s.ID, &s.Role, &s.Model, &s.SiblingIndex, &s.Preview, &s.CreatedAt); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if s.ID == messageID {
|
||||
currentIdx = i
|
||||
}
|
||||
siblings = append(siblings, s)
|
||||
}
|
||||
|
||||
return siblings, currentIdx, nil
|
||||
}
|
||||
|
||||
// FindLeafFromMessage walks down from a message to find the deepest descendant.
|
||||
// Follows the first child (lowest sibling_index) at each level.
|
||||
// Used when switching branches: clicking a mid-tree sibling navigates to its leaf.
|
||||
func FindLeafFromMessage(messageID string) (string, error) {
|
||||
var leafID string
|
||||
err := database.DB.QueryRow(`
|
||||
WITH RECURSIVE descendants AS (
|
||||
SELECT id, 0 AS depth
|
||||
FROM messages
|
||||
WHERE id = $1 AND deleted_at IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT child.id, d.depth + 1
|
||||
FROM messages child
|
||||
JOIN descendants d ON child.parent_id = d.id
|
||||
WHERE child.deleted_at IS NULL
|
||||
AND child.sibling_index = (
|
||||
SELECT MIN(sibling_index) FROM messages
|
||||
WHERE parent_id = d.id AND deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
SELECT id FROM descendants
|
||||
ORDER BY depth DESC
|
||||
LIMIT 1
|
||||
`, messageID).Scan(&leafID)
|
||||
|
||||
if err != nil {
|
||||
return messageID, nil // if anything fails, just use the message itself
|
||||
}
|
||||
return leafID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user