mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
ea719f7ca7
* refactor: memory provider * fix: migrations * feat: divide collection from different built-in memory * feat: add `MEMORY.md` and `PROFILES.md` * use .env for docker compose. fix #142 (#143) * feat(web): add brand icons for search providers (#144) Add custom FontAwesome icon definitions for all 9 search providers: - Yandex: uses existing faYandex from FA free brands - Tavily, Jina, Exa, Bocha, Serper: custom icons from brand SVGs - DuckDuckGo, SearXNG, Sogou: custom icons from Simple Icons Icons are registered with a custom 'fac' prefix and rendered as monochrome (currentColor) via FontAwesome's standard rendering. * fix: resolve multiple UI bugs (#147) * feat: add email service with multi-adapter support (#146) * feat: add email service with multi-adapter support Implement a full-stack email service with global provider management, per-bot bindings with granular read/write permissions, outbox audit storage, and MCP tool integration for direct mailbox access. Backend: - Email providers: CRUD with dynamic config schema (generic SMTP/IMAP, Mailgun) - Generic adapter: go-mail (SMTP) + go-imap/v2 (IMAP IDLE real-time push via UnilateralDataHandler + UID-based tracking + periodic check fallback) - Mailgun adapter: mailgun-go/v5 with dual inbound mode (webhook + poll) - Bot email bindings: per-bot provider binding with independent r/w permissions - Outbox: outbound email audit log with status tracking - Trigger: inbound emails push notification to bot_inbox (from/subject only, LLM reads full content on demand via MCP tools) - MailboxReader interface: on-demand IMAP queries for listing/reading emails - MCP tools: email_accounts, email_send, email_list (paginated mailbox), email_read (by UID) — all with multi-binding and provider_id selection - Webhook: /email/mailgun/webhook/:config_id (JWT-skipped, signature-verified) - DB migration: 0019_add_email (email_providers, bot_email_bindings, email_outbox) Frontend: - Email Providers page: /email-providers with MasterDetailSidebarLayout - Dynamic config form rendered from ordered provider meta schema with i18n keys - Bot detail: Email tab with bindings management + outbox audit table - Sidebar navigation entry - Full i18n support (en + zh) - Auto-generated SDK from Swagger Closes #17 * feat(email): trigger bot conversation immediately on inbound email Instead of only storing an inbox item and waiting for the next chat, the email trigger now proactively invokes the conversation resolver so the bot processes new emails right away — aligned with the schedule/heartbeat trigger pattern. * fix: lint --------- Co-authored-by: Acbox <acbox0328@gmail.com> * chore: update AGENTS.md * feat: files preview * feat(web): improve MCP details page * refactor(skills): import skill with pure markdown string * merge main into refactor/memory * fix: migration * refactor: temp delete qdrant and bm25 index * fix: clean merge code * fix: update memory handler --------- Co-authored-by: Leohearts <leohearts@leohearts.com> Co-authored-by: Menci <mencici@msn.com> Co-authored-by: Quincy <69751197+dqygit@users.noreply.github.com> Co-authored-by: BBQ <35603386+HoneyBBQ@users.noreply.github.com> Co-authored-by: Ran <16112591+chen-ran@users.noreply.github.com>
682 lines
20 KiB
Go
682 lines
20 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.30.0
|
|
// source: conversations.sql
|
|
|
|
package sqlc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const addChatParticipant = `-- name: AddChatParticipant :one
|
|
|
|
INSERT INTO bot_members (bot_id, user_id, role)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (bot_id, user_id) DO UPDATE SET role = EXCLUDED.role
|
|
RETURNING bot_id AS chat_id, user_id, role, created_at AS joined_at
|
|
`
|
|
|
|
type AddChatParticipantParams struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
Role string `json:"role"`
|
|
}
|
|
|
|
type AddChatParticipantRow struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
Role string `json:"role"`
|
|
JoinedAt pgtype.Timestamptz `json:"joined_at"`
|
|
}
|
|
|
|
// chat_participants
|
|
func (q *Queries) AddChatParticipant(ctx context.Context, arg AddChatParticipantParams) (AddChatParticipantRow, error) {
|
|
row := q.db.QueryRow(ctx, addChatParticipant, arg.ChatID, arg.UserID, arg.Role)
|
|
var i AddChatParticipantRow
|
|
err := row.Scan(
|
|
&i.ChatID,
|
|
&i.UserID,
|
|
&i.Role,
|
|
&i.JoinedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const copyParticipantsToChat = `-- name: CopyParticipantsToChat :exec
|
|
INSERT INTO bot_members (bot_id, user_id, role)
|
|
SELECT $1, bm.user_id, bm.role
|
|
FROM bot_members bm
|
|
WHERE bm.bot_id = $2
|
|
ON CONFLICT (bot_id, user_id) DO NOTHING
|
|
`
|
|
|
|
type CopyParticipantsToChatParams struct {
|
|
ChatID2 pgtype.UUID `json:"chat_id_2"`
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
}
|
|
|
|
func (q *Queries) CopyParticipantsToChat(ctx context.Context, arg CopyParticipantsToChatParams) error {
|
|
_, err := q.db.Exec(ctx, copyParticipantsToChat, arg.ChatID2, arg.ChatID)
|
|
return err
|
|
}
|
|
|
|
const createChat = `-- name: CreateChat :one
|
|
SELECT
|
|
b.id AS id,
|
|
b.id AS bot_id,
|
|
(COALESCE(NULLIF($1::text, ''), CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END))::text AS kind,
|
|
CASE WHEN $1 = 'thread' THEN $2::uuid ELSE NULL::uuid END AS parent_chat_id,
|
|
COALESCE(NULLIF($3::text, ''), b.display_name) AS title,
|
|
COALESCE($4::uuid, b.owner_user_id) AS created_by_user_id,
|
|
COALESCE($5::jsonb, b.metadata) AS metadata,
|
|
chat_models.model_id AS model_id,
|
|
b.created_at,
|
|
b.updated_at
|
|
FROM bots b
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $6
|
|
LIMIT 1
|
|
`
|
|
|
|
type CreateChatParams struct {
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title string `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
}
|
|
|
|
type CreateChatRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) CreateChat(ctx context.Context, arg CreateChatParams) (CreateChatRow, error) {
|
|
row := q.db.QueryRow(ctx, createChat,
|
|
arg.Kind,
|
|
arg.ParentChatID,
|
|
arg.Title,
|
|
arg.CreatedByUserID,
|
|
arg.Metadata,
|
|
arg.BotID,
|
|
)
|
|
var i CreateChatRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteChat = `-- name: DeleteChat :exec
|
|
WITH deleted_messages AS (
|
|
DELETE FROM bot_history_messages
|
|
WHERE bot_id = $1
|
|
)
|
|
DELETE FROM bot_channel_routes bcr
|
|
WHERE bcr.bot_id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteChat(ctx context.Context, chatID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deleteChat, chatID)
|
|
return err
|
|
}
|
|
|
|
const getChatByID = `-- name: GetChatByID :one
|
|
SELECT
|
|
b.id AS id,
|
|
b.id AS bot_id,
|
|
CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
|
|
NULL::uuid AS parent_chat_id,
|
|
b.display_name AS title,
|
|
b.owner_user_id AS created_by_user_id,
|
|
b.metadata AS metadata,
|
|
chat_models.model_id AS model_id,
|
|
b.created_at,
|
|
b.updated_at
|
|
FROM bots b
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $1
|
|
`
|
|
|
|
type GetChatByIDRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) GetChatByID(ctx context.Context, id pgtype.UUID) (GetChatByIDRow, error) {
|
|
row := q.db.QueryRow(ctx, getChatByID, id)
|
|
var i GetChatByIDRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getChatParticipant = `-- name: GetChatParticipant :one
|
|
WITH owner_participant AS (
|
|
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
|
FROM bots b
|
|
WHERE b.id = $1 AND b.owner_user_id = $2
|
|
),
|
|
member_participant AS (
|
|
SELECT bm.bot_id AS chat_id, bm.user_id, bm.role, bm.created_at AS joined_at
|
|
FROM bot_members bm
|
|
WHERE bm.bot_id = $1 AND bm.user_id = $2
|
|
)
|
|
SELECT chat_id, user_id, role, joined_at
|
|
FROM (
|
|
SELECT chat_id, user_id, role, joined_at FROM owner_participant
|
|
UNION ALL
|
|
SELECT chat_id, user_id, role, joined_at FROM member_participant
|
|
) p
|
|
ORDER BY CASE WHEN role = 'owner' THEN 0 ELSE 1 END
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetChatParticipantParams struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
}
|
|
|
|
type GetChatParticipantRow struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
Role string `json:"role"`
|
|
JoinedAt pgtype.Timestamptz `json:"joined_at"`
|
|
}
|
|
|
|
func (q *Queries) GetChatParticipant(ctx context.Context, arg GetChatParticipantParams) (GetChatParticipantRow, error) {
|
|
row := q.db.QueryRow(ctx, getChatParticipant, arg.ChatID, arg.UserID)
|
|
var i GetChatParticipantRow
|
|
err := row.Scan(
|
|
&i.ChatID,
|
|
&i.UserID,
|
|
&i.Role,
|
|
&i.JoinedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getChatReadAccessByUser = `-- name: GetChatReadAccessByUser :one
|
|
SELECT
|
|
'participant'::text AS access_mode,
|
|
(CASE
|
|
WHEN b.owner_user_id = $1 THEN 'owner'
|
|
ELSE COALESCE(bm.role, ''::text)
|
|
END)::text AS participant_role,
|
|
NULL::timestamptz AS last_observed_at
|
|
FROM bots b
|
|
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = $1
|
|
WHERE b.id = $2
|
|
AND (b.owner_user_id = $1 OR bm.user_id IS NOT NULL)
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetChatReadAccessByUserParams struct {
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
}
|
|
|
|
type GetChatReadAccessByUserRow struct {
|
|
AccessMode string `json:"access_mode"`
|
|
ParticipantRole string `json:"participant_role"`
|
|
LastObservedAt pgtype.Timestamptz `json:"last_observed_at"`
|
|
}
|
|
|
|
func (q *Queries) GetChatReadAccessByUser(ctx context.Context, arg GetChatReadAccessByUserParams) (GetChatReadAccessByUserRow, error) {
|
|
row := q.db.QueryRow(ctx, getChatReadAccessByUser, arg.UserID, arg.ChatID)
|
|
var i GetChatReadAccessByUserRow
|
|
err := row.Scan(&i.AccessMode, &i.ParticipantRole, &i.LastObservedAt)
|
|
return i, err
|
|
}
|
|
|
|
const getChatSettings = `-- name: GetChatSettings :one
|
|
SELECT
|
|
b.id AS chat_id,
|
|
chat_models.id AS model_id,
|
|
b.updated_at
|
|
FROM bots b
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $1
|
|
`
|
|
|
|
type GetChatSettingsRow struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
ModelID pgtype.UUID `json:"model_id"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) GetChatSettings(ctx context.Context, id pgtype.UUID) (GetChatSettingsRow, error) {
|
|
row := q.db.QueryRow(ctx, getChatSettings, id)
|
|
var i GetChatSettingsRow
|
|
err := row.Scan(&i.ChatID, &i.ModelID, &i.UpdatedAt)
|
|
return i, err
|
|
}
|
|
|
|
const listChatParticipants = `-- name: ListChatParticipants :many
|
|
WITH owner_participant AS (
|
|
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
|
FROM bots b
|
|
WHERE b.id = $1
|
|
),
|
|
member_participant AS (
|
|
SELECT bm.bot_id AS chat_id, bm.user_id, bm.role, bm.created_at AS joined_at
|
|
FROM bot_members bm
|
|
WHERE bm.bot_id = $1
|
|
AND bm.user_id <> (SELECT owner_user_id FROM bots WHERE id = $1)
|
|
)
|
|
SELECT chat_id, user_id, role, joined_at
|
|
FROM (
|
|
SELECT chat_id, user_id, role, joined_at FROM owner_participant
|
|
UNION ALL
|
|
SELECT chat_id, user_id, role, joined_at FROM member_participant
|
|
) p
|
|
ORDER BY joined_at ASC
|
|
`
|
|
|
|
type ListChatParticipantsRow struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
Role string `json:"role"`
|
|
JoinedAt pgtype.Timestamptz `json:"joined_at"`
|
|
}
|
|
|
|
func (q *Queries) ListChatParticipants(ctx context.Context, chatID pgtype.UUID) ([]ListChatParticipantsRow, error) {
|
|
rows, err := q.db.Query(ctx, listChatParticipants, chatID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListChatParticipantsRow
|
|
for rows.Next() {
|
|
var i ListChatParticipantsRow
|
|
if err := rows.Scan(
|
|
&i.ChatID,
|
|
&i.UserID,
|
|
&i.Role,
|
|
&i.JoinedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listChatsByBotAndUser = `-- name: ListChatsByBotAndUser :many
|
|
SELECT
|
|
b.id AS id,
|
|
b.id AS bot_id,
|
|
CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
|
|
NULL::uuid AS parent_chat_id,
|
|
b.display_name AS title,
|
|
b.owner_user_id AS created_by_user_id,
|
|
b.metadata AS metadata,
|
|
chat_models.model_id AS model_id,
|
|
b.created_at,
|
|
b.updated_at
|
|
FROM bots b
|
|
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = $1
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $2
|
|
AND (b.owner_user_id = $1 OR bm.user_id IS NOT NULL)
|
|
ORDER BY b.updated_at DESC
|
|
`
|
|
|
|
type ListChatsByBotAndUserParams struct {
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
}
|
|
|
|
type ListChatsByBotAndUserRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) ListChatsByBotAndUser(ctx context.Context, arg ListChatsByBotAndUserParams) ([]ListChatsByBotAndUserRow, error) {
|
|
rows, err := q.db.Query(ctx, listChatsByBotAndUser, arg.UserID, arg.BotID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListChatsByBotAndUserRow
|
|
for rows.Next() {
|
|
var i ListChatsByBotAndUserRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listThreadsByParent = `-- name: ListThreadsByParent :many
|
|
SELECT
|
|
b.id AS id,
|
|
b.id AS bot_id,
|
|
CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
|
|
NULL::uuid AS parent_chat_id,
|
|
b.display_name AS title,
|
|
b.owner_user_id AS created_by_user_id,
|
|
b.metadata AS metadata,
|
|
chat_models.model_id AS model_id,
|
|
b.created_at,
|
|
b.updated_at
|
|
FROM bots b
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $1
|
|
ORDER BY b.created_at DESC
|
|
`
|
|
|
|
type ListThreadsByParentRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) ListThreadsByParent(ctx context.Context, id pgtype.UUID) ([]ListThreadsByParentRow, error) {
|
|
rows, err := q.db.Query(ctx, listThreadsByParent, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListThreadsByParentRow
|
|
for rows.Next() {
|
|
var i ListThreadsByParentRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listVisibleChatsByBotAndUser = `-- name: ListVisibleChatsByBotAndUser :many
|
|
SELECT
|
|
b.id AS id,
|
|
b.id AS bot_id,
|
|
CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
|
|
NULL::uuid AS parent_chat_id,
|
|
b.display_name AS title,
|
|
b.owner_user_id AS created_by_user_id,
|
|
b.metadata AS metadata,
|
|
chat_models.model_id AS model_id,
|
|
b.created_at,
|
|
b.updated_at,
|
|
'participant'::text AS access_mode,
|
|
(CASE
|
|
WHEN b.owner_user_id = $1 THEN 'owner'
|
|
ELSE COALESCE(bm.role, ''::text)
|
|
END)::text AS participant_role,
|
|
NULL::timestamptz AS last_observed_at
|
|
FROM bots b
|
|
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = $1
|
|
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
|
WHERE b.id = $2
|
|
AND (b.owner_user_id = $1 OR bm.user_id IS NOT NULL)
|
|
ORDER BY b.updated_at DESC
|
|
`
|
|
|
|
type ListVisibleChatsByBotAndUserParams struct {
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
}
|
|
|
|
type ListVisibleChatsByBotAndUserRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
AccessMode string `json:"access_mode"`
|
|
ParticipantRole string `json:"participant_role"`
|
|
LastObservedAt pgtype.Timestamptz `json:"last_observed_at"`
|
|
}
|
|
|
|
func (q *Queries) ListVisibleChatsByBotAndUser(ctx context.Context, arg ListVisibleChatsByBotAndUserParams) ([]ListVisibleChatsByBotAndUserRow, error) {
|
|
rows, err := q.db.Query(ctx, listVisibleChatsByBotAndUser, arg.UserID, arg.BotID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListVisibleChatsByBotAndUserRow
|
|
for rows.Next() {
|
|
var i ListVisibleChatsByBotAndUserRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AccessMode,
|
|
&i.ParticipantRole,
|
|
&i.LastObservedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const removeChatParticipant = `-- name: RemoveChatParticipant :exec
|
|
DELETE FROM bot_members
|
|
WHERE bot_id = $1
|
|
AND user_id = $2
|
|
AND user_id <> (SELECT owner_user_id FROM bots WHERE id = $1)
|
|
`
|
|
|
|
type RemoveChatParticipantParams struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
UserID pgtype.UUID `json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) RemoveChatParticipant(ctx context.Context, arg RemoveChatParticipantParams) error {
|
|
_, err := q.db.Exec(ctx, removeChatParticipant, arg.ChatID, arg.UserID)
|
|
return err
|
|
}
|
|
|
|
const touchChat = `-- name: TouchChat :exec
|
|
UPDATE bots
|
|
SET updated_at = now()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) TouchChat(ctx context.Context, chatID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, touchChat, chatID)
|
|
return err
|
|
}
|
|
|
|
const updateChatTitle = `-- name: UpdateChatTitle :one
|
|
WITH updated AS (
|
|
UPDATE bots
|
|
SET display_name = $1,
|
|
updated_at = now()
|
|
WHERE bots.id = $2
|
|
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, reasoning_enabled, reasoning_effort, max_inbox_items, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, heartbeat_model_id, metadata, created_at, updated_at
|
|
)
|
|
SELECT
|
|
updated.id AS id,
|
|
updated.id AS bot_id,
|
|
CASE WHEN updated.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
|
|
NULL::uuid AS parent_chat_id,
|
|
updated.display_name AS title,
|
|
updated.owner_user_id AS created_by_user_id,
|
|
updated.metadata,
|
|
chat_models.model_id AS model_id,
|
|
updated.created_at,
|
|
updated.updated_at
|
|
FROM updated
|
|
LEFT JOIN models chat_models ON chat_models.id = updated.chat_model_id
|
|
`
|
|
|
|
type UpdateChatTitleParams struct {
|
|
Title pgtype.Text `json:"title"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
}
|
|
|
|
type UpdateChatTitleRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
BotID pgtype.UUID `json:"bot_id"`
|
|
Kind string `json:"kind"`
|
|
ParentChatID pgtype.UUID `json:"parent_chat_id"`
|
|
Title pgtype.Text `json:"title"`
|
|
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
|
|
Metadata []byte `json:"metadata"`
|
|
ModelID pgtype.Text `json:"model_id"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) UpdateChatTitle(ctx context.Context, arg UpdateChatTitleParams) (UpdateChatTitleRow, error) {
|
|
row := q.db.QueryRow(ctx, updateChatTitle, arg.Title, arg.BotID)
|
|
var i UpdateChatTitleRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.BotID,
|
|
&i.Kind,
|
|
&i.ParentChatID,
|
|
&i.Title,
|
|
&i.CreatedByUserID,
|
|
&i.Metadata,
|
|
&i.ModelID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertChatSettings = `-- name: UpsertChatSettings :one
|
|
|
|
WITH
|
|
updated AS (
|
|
UPDATE bots
|
|
SET chat_model_id = COALESCE($1::uuid, bots.chat_model_id),
|
|
updated_at = now()
|
|
WHERE bots.id = $2
|
|
RETURNING bots.id, bots.chat_model_id, bots.updated_at
|
|
)
|
|
SELECT
|
|
updated.id AS chat_id,
|
|
chat_models.id AS model_id,
|
|
updated.updated_at
|
|
FROM updated
|
|
LEFT JOIN models chat_models ON chat_models.id = updated.chat_model_id
|
|
`
|
|
|
|
type UpsertChatSettingsParams struct {
|
|
ChatModelID pgtype.UUID `json:"chat_model_id"`
|
|
ID pgtype.UUID `json:"id"`
|
|
}
|
|
|
|
type UpsertChatSettingsRow struct {
|
|
ChatID pgtype.UUID `json:"chat_id"`
|
|
ModelID pgtype.UUID `json:"model_id"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
// chat_settings
|
|
func (q *Queries) UpsertChatSettings(ctx context.Context, arg UpsertChatSettingsParams) (UpsertChatSettingsRow, error) {
|
|
row := q.db.QueryRow(ctx, upsertChatSettings, arg.ChatModelID, arg.ID)
|
|
var i UpsertChatSettingsRow
|
|
err := row.Scan(&i.ChatID, &i.ModelID, &i.UpdatedAt)
|
|
return i, err
|
|
}
|