mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-02 19:55:18 +02:00
Merge remote-tracking branch 'upstream/dev' into feat/whatsapp-gateway-integration
This commit is contained in:
commit
2e64d5d3de
15 changed files with 472 additions and 136 deletions
|
|
@ -212,9 +212,9 @@ API Base URL: https://open.bigmodel.cn/api/paas/v4
|
||||||
|
|
||||||
| 字段 | 值 | 说明 |
|
| 字段 | 值 | 说明 |
|
||||||
|------|-----|------|
|
|------|-----|------|
|
||||||
| **Configuration Name** | `MiniMax M2.5` | 配置名称(自定义) |
|
| **Configuration Name** | `MiniMax M3` | 配置名称(自定义) |
|
||||||
| **Provider** | `MINIMAX` | 选择 MiniMax |
|
| **Provider** | `MINIMAX` | 选择 MiniMax |
|
||||||
| **Model Name** | `MiniMax-M2.5` | 推荐模型<br>其他选项: `MiniMax-M2.5-highspeed` |
|
| **Model Name** | `MiniMax-M3` | 推荐模型<br>其他选项: `MiniMax-M2.7`、`MiniMax-M2.7-highspeed` |
|
||||||
| **API Key** | `eyJ...` | 你的 MiniMax API Key |
|
| **API Key** | `eyJ...` | 你的 MiniMax API Key |
|
||||||
| **API Base URL** | `https://api.minimax.io/v1` | MiniMax API 地址 |
|
| **API Base URL** | `https://api.minimax.io/v1` | MiniMax API 地址 |
|
||||||
| **Parameters** | `{"temperature": 1.0}` | 注意:temperature 必须在 (0.0, 1.0] 范围内,不能为 0 |
|
| **Parameters** | `{"temperature": 1.0}` | 注意:temperature 必须在 (0.0, 1.0] 范围内,不能为 0 |
|
||||||
|
|
@ -222,22 +222,23 @@ API Base URL: https://open.bigmodel.cn/api/paas/v4
|
||||||
### 示例配置
|
### 示例配置
|
||||||
|
|
||||||
```
|
```
|
||||||
Configuration Name: MiniMax M2.5
|
Configuration Name: MiniMax M3
|
||||||
Provider: MINIMAX
|
Provider: MINIMAX
|
||||||
Model Name: MiniMax-M2.5
|
Model Name: MiniMax-M3
|
||||||
API Key: eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
API Key: eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
API Base URL: https://api.minimax.io/v1
|
API Base URL: https://api.minimax.io/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
### 可用模型
|
### 可用模型
|
||||||
|
|
||||||
- **MiniMax-M2.5**: 高性能通用模型,204K 上下文窗口(推荐)
|
- **MiniMax-M3**: 旗舰模型,512K 上下文窗口(推荐)
|
||||||
- **MiniMax-M2.5-highspeed**: 高速推理版本,204K 上下文窗口
|
- **MiniMax-M2.7**: 上一代通用模型,204K 上下文窗口
|
||||||
|
- **MiniMax-M2.7-highspeed**: 上一代高速推理版本,204K 上下文窗口
|
||||||
|
|
||||||
### 注意事项
|
### 注意事项
|
||||||
|
|
||||||
- **temperature 参数**: MiniMax 要求 temperature 必须在 (0.0, 1.0] 范围内,不能设置为 0。建议使用 1.0。
|
- **temperature 参数**: MiniMax 要求 temperature 必须在 (0.0, 1.0] 范围内,不能设置为 0。建议使用 1.0。
|
||||||
- 两个模型都支持 204K 超长上下文窗口,适合处理长文本任务。
|
- M3 支持 512K 超长上下文,M2.7 系列保留 204K,适合按需求选择。
|
||||||
|
|
||||||
### 定价
|
### 定价
|
||||||
- 请访问 [MiniMax 定价页面](https://platform.minimaxi.com/document/Price) 查看最新价格
|
- 请访问 [MiniMax 定价页面](https://platform.minimaxi.com/document/Price) 查看最新价格
|
||||||
|
|
@ -315,8 +316,8 @@ docker compose logs backend | grep -i "error"
|
||||||
|---------|---------|------|
|
|---------|---------|------|
|
||||||
| **文档摘要** | Qwen-Plus, GLM-4 | 平衡性能和成本 |
|
| **文档摘要** | Qwen-Plus, GLM-4 | 平衡性能和成本 |
|
||||||
| **代码分析** | DeepSeek-Coder | 代码专用 |
|
| **代码分析** | DeepSeek-Coder | 代码专用 |
|
||||||
| **长文本处理** | Kimi 128K, MiniMax-M2.5 (204K) | 超长上下文 |
|
| **长文本处理** | Kimi 128K, MiniMax-M3 (512K) | 超长上下文 |
|
||||||
| **快速响应** | Qwen-Turbo, GLM-4-Flash, MiniMax-M2.5-highspeed | 速度优先 |
|
| **快速响应** | Qwen-Turbo, GLM-4-Flash, MiniMax-M2.7-highspeed | 速度优先 |
|
||||||
|
|
||||||
### 2. 成本优化
|
### 2. 成本优化
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,34 +25,60 @@ depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ENUM types (PostgreSQL requires types created before tables that use them)
|
# Guard every object so the migration is safe to re-run after a partial
|
||||||
|
# apply (the types/tables outlive a failed run that never advanced
|
||||||
|
# alembic_version). Types must precede the tables that reference them.
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TYPE automation_status AS ENUM (
|
DO $$
|
||||||
'active', 'paused', 'archived'
|
BEGIN
|
||||||
);
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_type WHERE typname = 'automation_status'
|
||||||
|
) THEN
|
||||||
|
CREATE TYPE automation_status AS ENUM (
|
||||||
|
'active', 'paused', 'archived'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TYPE automation_trigger_type AS ENUM (
|
DO $$
|
||||||
'schedule', 'manual'
|
BEGIN
|
||||||
);
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_type WHERE typname = 'automation_trigger_type'
|
||||||
|
) THEN
|
||||||
|
CREATE TYPE automation_trigger_type AS ENUM (
|
||||||
|
'schedule', 'manual'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TYPE automation_run_status AS ENUM (
|
DO $$
|
||||||
'pending', 'running', 'succeeded', 'failed',
|
BEGIN
|
||||||
'cancelled', 'timed_out'
|
IF NOT EXISTS (
|
||||||
);
|
SELECT 1 FROM pg_type WHERE typname = 'automation_run_status'
|
||||||
|
) THEN
|
||||||
|
CREATE TYPE automation_run_status AS ENUM (
|
||||||
|
'pending', 'running', 'succeeded', 'failed',
|
||||||
|
'cancelled', 'timed_out'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# automations — the editable, versioned automation definition
|
# automations — the editable, versioned automation definition
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE automations (
|
CREATE TABLE IF NOT EXISTS automations (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
search_space_id INTEGER NOT NULL
|
search_space_id INTEGER NOT NULL
|
||||||
REFERENCES searchspaces(id) ON DELETE CASCADE,
|
REFERENCES searchspaces(id) ON DELETE CASCADE,
|
||||||
|
|
@ -69,19 +95,25 @@ def upgrade() -> None:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automations_search_space_id ON automations(search_space_id);"
|
"CREATE INDEX IF NOT EXISTS ix_automations_search_space_id ON automations(search_space_id);"
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automations_created_by_user_id ON automations(created_by_user_id);"
|
"CREATE INDEX IF NOT EXISTS ix_automations_created_by_user_id ON automations(created_by_user_id);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automations_status ON automations(status);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automations_created_at ON automations(created_at);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automations_updated_at ON automations(updated_at);"
|
||||||
)
|
)
|
||||||
op.execute("CREATE INDEX ix_automations_status ON automations(status);")
|
|
||||||
op.execute("CREATE INDEX ix_automations_created_at ON automations(created_at);")
|
|
||||||
op.execute("CREATE INDEX ix_automations_updated_at ON automations(updated_at);")
|
|
||||||
|
|
||||||
# automation_triggers — one row per (automation, trigger-instance) pair
|
# automation_triggers — one row per (automation, trigger-instance) pair
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE automation_triggers (
|
CREATE TABLE IF NOT EXISTS automation_triggers (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
automation_id INTEGER NOT NULL
|
automation_id INTEGER NOT NULL
|
||||||
REFERENCES automations(id) ON DELETE CASCADE,
|
REFERENCES automations(id) ON DELETE CASCADE,
|
||||||
|
|
@ -96,20 +128,22 @@ def upgrade() -> None:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automation_triggers_automation_id ON automation_triggers(automation_id);"
|
"CREATE INDEX IF NOT EXISTS ix_automation_triggers_automation_id ON automation_triggers(automation_id);"
|
||||||
)
|
|
||||||
op.execute("CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);")
|
|
||||||
op.execute(
|
|
||||||
"CREATE INDEX ix_automation_triggers_enabled ON automation_triggers(enabled);"
|
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automation_triggers_created_at ON automation_triggers(created_at);"
|
"CREATE INDEX IF NOT EXISTS ix_automation_triggers_type ON automation_triggers(type);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automation_triggers_enabled ON automation_triggers(enabled);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automation_triggers_created_at ON automation_triggers(created_at);"
|
||||||
)
|
)
|
||||||
# Partial index for the schedule tick: only enabled schedule triggers
|
# Partial index for the schedule tick: only enabled schedule triggers
|
||||||
# with a scheduled next fire are ever scanned for due rows.
|
# with a scheduled next fire are ever scanned for due rows.
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE INDEX ix_automation_triggers_due
|
CREATE INDEX IF NOT EXISTS ix_automation_triggers_due
|
||||||
ON automation_triggers (next_fire_at)
|
ON automation_triggers (next_fire_at)
|
||||||
WHERE enabled = true
|
WHERE enabled = true
|
||||||
AND type = 'schedule'
|
AND type = 'schedule'
|
||||||
|
|
@ -120,7 +154,7 @@ def upgrade() -> None:
|
||||||
# automation_runs — the immutable per-fire execution record
|
# automation_runs — the immutable per-fire execution record
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE automation_runs (
|
CREATE TABLE IF NOT EXISTS automation_runs (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
automation_id INTEGER NOT NULL
|
automation_id INTEGER NOT NULL
|
||||||
REFERENCES automations(id) ON DELETE CASCADE,
|
REFERENCES automations(id) ON DELETE CASCADE,
|
||||||
|
|
@ -140,14 +174,16 @@ def upgrade() -> None:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automation_runs_automation_id ON automation_runs(automation_id);"
|
"CREATE INDEX IF NOT EXISTS ix_automation_runs_automation_id ON automation_runs(automation_id);"
|
||||||
)
|
)
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automation_runs_trigger_id ON automation_runs(trigger_id);"
|
"CREATE INDEX IF NOT EXISTS ix_automation_runs_trigger_id ON automation_runs(trigger_id);"
|
||||||
)
|
)
|
||||||
op.execute("CREATE INDEX ix_automation_runs_status ON automation_runs(status);")
|
|
||||||
op.execute(
|
op.execute(
|
||||||
"CREATE INDEX ix_automation_runs_created_at ON automation_runs(created_at);"
|
"CREATE INDEX IF NOT EXISTS ix_automation_runs_status ON automation_runs(status);"
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_automation_runs_created_at ON automation_runs(created_at);"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
"""add automation_runs to zero_publication with thin column list
|
||||||
|
|
||||||
|
Publishes ``automation_runs`` so the dashboard can replace polling with a
|
||||||
|
live run status + per-step ticker. Only the columns the list and ticker
|
||||||
|
read are exposed (``id, automation_id, trigger_id, status, step_results,
|
||||||
|
started_at, finished_at, created_at``); heavy JSONB
|
||||||
|
(``definition_snapshot``, ``inputs``, ``output``, ``artifacts``, ``error``)
|
||||||
|
stays on REST and is fetched lazily on detail expand.
|
||||||
|
|
||||||
|
Uses the canonical ``ALTER PUBLICATION ... SET TABLE`` + ``COMMENT``
|
||||||
|
bookend pattern (see migration 143) -- the shape Zero ``>=1.0`` requires
|
||||||
|
to fire its schema-change hook. Existing tables are re-emitted unchanged.
|
||||||
|
|
||||||
|
Revision ID: 148
|
||||||
|
Revises: 147
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "148"
|
||||||
|
down_revision: str | None = "147"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
PUBLICATION_NAME = "zero_publication"
|
||||||
|
|
||||||
|
# Mirrors migration 143. Kept in sync explicitly: any change to these lists
|
||||||
|
# must be re-emitted in a new resync migration with COMMENT bookends.
|
||||||
|
DOCUMENT_COLS = [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"document_type",
|
||||||
|
"search_space_id",
|
||||||
|
"folder_id",
|
||||||
|
"created_by_id",
|
||||||
|
"status",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
USER_COLS = [
|
||||||
|
"id",
|
||||||
|
"pages_limit",
|
||||||
|
"pages_used",
|
||||||
|
"premium_credit_micros_limit",
|
||||||
|
"premium_credit_micros_used",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Thin set: status + lightweight progress only. Heavy JSONB stays on REST.
|
||||||
|
AUTOMATION_RUN_COLS = [
|
||||||
|
"id",
|
||||||
|
"automation_id",
|
||||||
|
"trigger_id",
|
||||||
|
"status",
|
||||||
|
"step_results",
|
||||||
|
"started_at",
|
||||||
|
"finished_at",
|
||||||
|
"created_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _has_zero_version(conn, table: str) -> bool:
|
||||||
|
return (
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT 1 FROM information_schema.columns "
|
||||||
|
"WHERE table_name = :tbl AND column_name = '_0_version'"
|
||||||
|
),
|
||||||
|
{"tbl": table},
|
||||||
|
).fetchone()
|
||||||
|
is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_set_table_ddl(
|
||||||
|
*, documents_has_zero_ver: bool, user_has_zero_ver: bool
|
||||||
|
) -> str:
|
||||||
|
doc_cols = DOCUMENT_COLS + (['"_0_version"'] if documents_has_zero_ver else [])
|
||||||
|
user_cols = USER_COLS + (['"_0_version"'] if user_has_zero_ver else [])
|
||||||
|
doc_col_list = ", ".join(doc_cols)
|
||||||
|
user_col_list = ", ".join(user_cols)
|
||||||
|
run_col_list = ", ".join(AUTOMATION_RUN_COLS)
|
||||||
|
return (
|
||||||
|
f"ALTER PUBLICATION {PUBLICATION_NAME} SET TABLE "
|
||||||
|
f"notifications, "
|
||||||
|
f"documents ({doc_col_list}), "
|
||||||
|
f"folders, "
|
||||||
|
f"search_source_connectors, "
|
||||||
|
f"new_chat_messages, "
|
||||||
|
f"chat_comments, "
|
||||||
|
f"chat_session_state, "
|
||||||
|
f'"user" ({user_col_list}), '
|
||||||
|
f"automation_runs ({run_col_list})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
exists = conn.execute(
|
||||||
|
sa.text("SELECT 1 FROM pg_publication WHERE pubname = :name"),
|
||||||
|
{"name": PUBLICATION_NAME},
|
||||||
|
).fetchone()
|
||||||
|
if not exists:
|
||||||
|
return
|
||||||
|
|
||||||
|
documents_has_zero_ver = _has_zero_version(conn, "documents")
|
||||||
|
user_has_zero_ver = _has_zero_version(conn, "user")
|
||||||
|
|
||||||
|
# COMMENT-ALTER-COMMENT trio must be one transaction so Zero observes
|
||||||
|
# them as one schema-change event. Matches the SAVEPOINT pattern used
|
||||||
|
# in migrations 117 / 139 / 140 / 143.
|
||||||
|
tx = conn.begin_nested() if conn.in_transaction() else conn.begin()
|
||||||
|
with tx:
|
||||||
|
conn.execute(
|
||||||
|
sa.text(f"COMMENT ON PUBLICATION {PUBLICATION_NAME} IS 'pre-148-resync'")
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
_build_set_table_ddl(
|
||||||
|
documents_has_zero_ver=documents_has_zero_ver,
|
||||||
|
user_has_zero_ver=user_has_zero_ver,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
sa.text(f"COMMENT ON PUBLICATION {PUBLICATION_NAME} IS 'post-148-resync'")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Re-emit migration 143's shape (no automation_runs)."""
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
exists = conn.execute(
|
||||||
|
sa.text("SELECT 1 FROM pg_publication WHERE pubname = :name"),
|
||||||
|
{"name": PUBLICATION_NAME},
|
||||||
|
).fetchone()
|
||||||
|
if not exists:
|
||||||
|
return
|
||||||
|
|
||||||
|
documents_has_zero_ver = _has_zero_version(conn, "documents")
|
||||||
|
user_has_zero_ver = _has_zero_version(conn, "user")
|
||||||
|
|
||||||
|
doc_cols = DOCUMENT_COLS + (['"_0_version"'] if documents_has_zero_ver else [])
|
||||||
|
user_cols = USER_COLS + (['"_0_version"'] if user_has_zero_ver else [])
|
||||||
|
doc_col_list = ", ".join(doc_cols)
|
||||||
|
user_col_list = ", ".join(user_cols)
|
||||||
|
ddl = (
|
||||||
|
f"ALTER PUBLICATION {PUBLICATION_NAME} SET TABLE "
|
||||||
|
f"notifications, "
|
||||||
|
f"documents ({doc_col_list}), "
|
||||||
|
f"folders, "
|
||||||
|
f"search_source_connectors, "
|
||||||
|
f"new_chat_messages, "
|
||||||
|
f"chat_comments, "
|
||||||
|
f"chat_session_state, "
|
||||||
|
f'"user" ({user_col_list})'
|
||||||
|
)
|
||||||
|
|
||||||
|
tx = conn.begin_nested() if conn.in_transaction() else conn.begin()
|
||||||
|
with tx:
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
f"COMMENT ON PUBLICATION {PUBLICATION_NAME} IS 'pre-148-downgrade'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.execute(sa.text(ddl))
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
f"COMMENT ON PUBLICATION {PUBLICATION_NAME} IS 'post-148-downgrade'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -85,23 +85,16 @@ async def build_dependencies(
|
||||||
connector_service, firecrawl_api_key = await setup_connector_and_firecrawl(
|
connector_service, firecrawl_api_key = await setup_connector_and_firecrawl(
|
||||||
session, search_space_id=search_space_id
|
session, search_space_id=search_space_id
|
||||||
)
|
)
|
||||||
# Quick fix: use an in-memory checkpointer for automation runs.
|
# Per-task InMemorySaver: the shared Postgres checkpointer's connection
|
||||||
|
# pool binds connections to the loop that opened them, but Celery uses a
|
||||||
|
# fresh loop per task, so the next task hangs 30s on a dead-loop connection
|
||||||
|
# (`PoolTimeout`). InMemorySaver has no pool and dies with the task — fine
|
||||||
|
# while runs are one-shot (the checkpoint only spans one graph execution).
|
||||||
#
|
#
|
||||||
# The shared Postgres checkpointer caches DB connections in a
|
# TODO(checkpointer): when runs need durability (crash-resume or HITL
|
||||||
# module-level pool. Each cached connection is bound to the asyncio
|
# interrupt/resume across tasks), dispose the checkpointer pool around each
|
||||||
# loop that opened it. Celery throws away the loop after every task,
|
# Celery task in `run_async_celery_task` — as `_dispose_shared_db_engine`
|
||||||
# so the pool ends up full of connections pointing to a dead loop,
|
# already does for the SQLAlchemy pool — then use the shared checkpointer.
|
||||||
# and the next Celery task (running on a fresh loop) can't use any
|
|
||||||
# of them — it hangs 30s and fails with
|
|
||||||
# `PoolTimeout: couldn't get a connection after 30.00 sec`.
|
|
||||||
#
|
|
||||||
# InMemorySaver has no cached connections, no loop binding — each
|
|
||||||
# Celery task creates one and drops it on exit.
|
|
||||||
#
|
|
||||||
# TODO(checkpointer): proper fix is to dispose the checkpointer
|
|
||||||
# pool around each Celery task in `run_async_celery_task`, the same
|
|
||||||
# way `_dispose_shared_db_engine` already does for the SQLAlchemy
|
|
||||||
# pool. Then this site can switch back to the shared checkpointer.
|
|
||||||
checkpointer = InMemorySaver()
|
checkpointer = InMemorySaver()
|
||||||
return AgentDependencies(
|
return AgentDependencies(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
|
|
|
||||||
|
|
@ -236,17 +236,17 @@ global_llm_configs:
|
||||||
use_default_system_instructions: true
|
use_default_system_instructions: true
|
||||||
citations_enabled: true
|
citations_enabled: true
|
||||||
|
|
||||||
# Example: MiniMax M2.5 - High-performance with 204K context window
|
# Example: MiniMax M3 - High-performance with 512K context window
|
||||||
- id: -8
|
- id: -8
|
||||||
name: "Global MiniMax M2.5"
|
name: "Global MiniMax M3"
|
||||||
description: "MiniMax M2.5 with 204K context window and competitive pricing"
|
description: "MiniMax M3 with 512K context window and competitive pricing"
|
||||||
billing_tier: "free"
|
billing_tier: "free"
|
||||||
anonymous_enabled: true
|
anonymous_enabled: true
|
||||||
seo_enabled: true
|
seo_enabled: true
|
||||||
seo_slug: "minimax-m2.5"
|
seo_slug: "minimax-m3"
|
||||||
quota_reserve_tokens: 4000
|
quota_reserve_tokens: 4000
|
||||||
provider: "MINIMAX"
|
provider: "MINIMAX"
|
||||||
model_name: "MiniMax-M2.5"
|
model_name: "MiniMax-M3"
|
||||||
api_key: "your-minimax-api-key-here"
|
api_key: "your-minimax-api-key-here"
|
||||||
api_base: "https://api.minimax.io/v1"
|
api_base: "https://api.minimax.io/v1"
|
||||||
rpm: 60
|
rpm: 60
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { RunStepResult } from "@/contracts/types/automation.types";
|
import type { RunStatus, RunStepResult } from "@/contracts/types/automation.types";
|
||||||
import { useAutomationRun } from "@/hooks/use-automation-runs";
|
import { useAutomationRun } from "@/hooks/use-automation-runs";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { RunStepResultCard } from "./run-step-result-card";
|
import { RunStepResultCard } from "./run-step-result-card";
|
||||||
|
|
@ -23,44 +23,46 @@ import { RunStepResultCard } from "./run-step-result-card";
|
||||||
interface RunDetailsPanelProps {
|
interface RunDetailsPanelProps {
|
||||||
automationId: number;
|
automationId: number;
|
||||||
runId: number;
|
runId: number;
|
||||||
|
/** Live step entries from Zero; rendered while the run is in-flight and
|
||||||
|
* also kept as the authoritative source once it finishes. */
|
||||||
|
liveSteps: RunStepResult[];
|
||||||
|
/** Live run status from Zero. Used to hide diagnostic sections that
|
||||||
|
* only make sense after the run reaches a terminal state. */
|
||||||
|
liveStatus: RunStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expanded view of a single run. Fetches lazily — the parent only renders
|
* Expanded view of a single run. Steps render immediately from the live
|
||||||
* this once the row is opened, so the list view stays cheap.
|
* Zero row so the panel updates as the run progresses; the heavy REST
|
||||||
|
* payload (output, artifacts, resolved inputs, run-level error) is
|
||||||
|
* fetched lazily and merged in when it arrives.
|
||||||
*
|
*
|
||||||
* We surface the run outcome readably: a run-level error first (when
|
* Surfacing order is outcome-first: a run-level error (when present),
|
||||||
* present), then per-step cards that render the agent's markdown
|
* then per-step cards that render the agent's markdown ``final_message``
|
||||||
* ``final_message`` directly, and finally the structural artifacts/inputs.
|
* directly, and finally the structural artifacts/inputs. The full
|
||||||
* The full ``definition_snapshot`` is omitted because it usually mirrors the
|
* ``definition_snapshot`` is omitted because it usually mirrors the live
|
||||||
* live definition — surfacing it would dominate the panel without informing
|
* definition — surfacing it would dominate the panel without informing
|
||||||
* what the user is trying to learn ("did this work? what did it do?").
|
* what the user is trying to learn ("did this work? what did it do?").
|
||||||
*/
|
*/
|
||||||
export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) {
|
export function RunDetailsPanel({
|
||||||
const { data: run, isLoading, error } = useAutomationRun(automationId, runId);
|
automationId,
|
||||||
|
runId,
|
||||||
|
liveSteps,
|
||||||
|
liveStatus,
|
||||||
|
}: RunDetailsPanelProps) {
|
||||||
|
const isTerminal = liveStatus !== "pending" && liveStatus !== "running";
|
||||||
|
// Defer the REST round-trip until the run can actually carry heavy
|
||||||
|
// fields — output/artifacts/error are only written at terminal mark.
|
||||||
|
const { data: run, isLoading, error } = useAutomationRun(automationId, runId, {
|
||||||
|
enabled: isTerminal,
|
||||||
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
const runError = run?.error && Object.keys(run.error).length > 0 ? run.error : null;
|
||||||
return (
|
const hasOutput = !!run?.output && Object.keys(run.output).length > 0;
|
||||||
<div className="flex flex-col gap-3 border-t border-border/60 bg-muted/20 p-4">
|
const hasInputs = !!run && Object.keys(run.inputs ?? {}).length > 0;
|
||||||
<Skeleton className="h-3 w-32" />
|
const hasDiagnostics = !!run && (run.artifacts.length > 0 || hasInputs);
|
||||||
<Skeleton className="h-24 w-full" />
|
const heavyLoading = isTerminal && isLoading && !run;
|
||||||
</div>
|
const heavyError = isTerminal && !!error;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !run) {
|
|
||||||
return (
|
|
||||||
<div className="border-t border-border/60 bg-muted/20 p-4 text-xs text-muted-foreground">
|
|
||||||
Couldn't load run details{error?.message ? `: ${error.message}` : "."}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const runError = run.error && Object.keys(run.error).length > 0 ? run.error : null;
|
|
||||||
const hasOutput = run.output && Object.keys(run.output).length > 0;
|
|
||||||
const hasInputs = Object.keys(run.inputs ?? {}).length > 0;
|
|
||||||
const steps = run.step_results as RunStepResult[];
|
|
||||||
const hasDiagnostics = run.artifacts.length > 0 || hasInputs;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 border-t border-border/60 bg-muted/20 p-4">
|
<div className="flex flex-col gap-4 border-t border-border/60 bg-muted/20 p-4">
|
||||||
|
|
@ -72,30 +74,40 @@ export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) {
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Section icon={GitCommitHorizontal} label={`Step results · ${steps.length}`}>
|
<Section icon={GitCommitHorizontal} label={`Step results · ${liveSteps.length}`}>
|
||||||
{steps.length === 0 ? (
|
{liveSteps.length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground">No steps recorded.</p>
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{isTerminal ? "No steps recorded." : "Waiting for first step…"}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{steps.map((step, index) => (
|
{liveSteps.map((step, index) => (
|
||||||
<RunStepResultCard key={step.step_id ?? index} step={step} />
|
<RunStepResultCard key={step.step_id ?? index} step={step} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{hasDiagnostics ? <Separator className="bg-border/60" /> : null}
|
{heavyLoading ? (
|
||||||
|
<Skeleton className="h-16 w-full" />
|
||||||
{run.artifacts.length > 0 ? (
|
) : heavyError ? (
|
||||||
<Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}>
|
<p className="text-xs text-muted-foreground">
|
||||||
<JsonBlock value={run.artifacts} />
|
Couldn't load run details{error?.message ? `: ${error.message}` : "."}
|
||||||
</Section>
|
</p>
|
||||||
) : null}
|
) : hasDiagnostics ? (
|
||||||
|
<>
|
||||||
{hasInputs ? (
|
<Separator className="bg-border/60" />
|
||||||
<Section icon={Settings2} label="Resolved inputs">
|
{run && run.artifacts.length > 0 ? (
|
||||||
<JsonBlock value={run.inputs} />
|
<Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}>
|
||||||
</Section>
|
<JsonBlock value={run.artifacts} />
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
{hasInputs ? (
|
||||||
|
<Section icon={Settings2} label="Resolved inputs">
|
||||||
|
<JsonBlock value={run?.inputs} />
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { ChevronDown, ChevronRight, Hand } from "lucide-react";
|
import { ChevronDown, ChevronRight, Hand } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { RunSummary } from "@/contracts/types/automation.types";
|
import type { LiveRunSummary } from "@/hooks/use-automation-runs";
|
||||||
import { formatDuration } from "@/lib/automations/run-duration";
|
import { formatDuration } from "@/lib/automations/run-duration";
|
||||||
import { formatRelativeDate } from "@/lib/format-date";
|
import { formatRelativeDate } from "@/lib/format-date";
|
||||||
import { RunDetailsPanel } from "./run-details-panel";
|
import { RunDetailsPanel } from "./run-details-panel";
|
||||||
import { RunStatusBadge } from "./run-status-badge";
|
import { RunStatusBadge } from "./run-status-badge";
|
||||||
|
|
||||||
interface RunRowProps {
|
interface RunRowProps {
|
||||||
run: RunSummary;
|
run: LiveRunSummary;
|
||||||
automationId: number;
|
automationId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One run row. Click to expand → fetches the full run and shows the
|
* One run row. Click to expand → renders the details panel inline.
|
||||||
* details panel inline. State is local to each row so multiple panels
|
* Status and step_results come live from the parent's Zero query; the
|
||||||
* can be open at once (or none).
|
* panel itself only fetches the heavy REST fields on first expand.
|
||||||
*/
|
*/
|
||||||
export function RunRow({ run, automationId }: RunRowProps) {
|
export function RunRow({ run, automationId }: RunRowProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
@ -47,7 +47,14 @@ export function RunRow({ run, automationId }: RunRowProps) {
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && <RunDetailsPanel automationId={automationId} runId={run.id} />}
|
{open && (
|
||||||
|
<RunDetailsPanel
|
||||||
|
automationId={automationId}
|
||||||
|
runId={run.id}
|
||||||
|
liveSteps={run.step_results}
|
||||||
|
liveStatus={run.status}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ Zero syncs the following tables for real-time features:
|
||||||
| `new_chat_messages` | Live chat message sync for shared chats |
|
| `new_chat_messages` | Live chat message sync for shared chats |
|
||||||
| `chat_comments` | Real-time comment threads on AI responses |
|
| `chat_comments` | Real-time comment threads on AI responses |
|
||||||
| `chat_session_state` | Collaboration indicators (who is typing) |
|
| `chat_session_state` | Collaboration indicators (who is typing) |
|
||||||
|
| `automation_runs` | Live run status and per-step progress (thin column set; heavy fields stay on REST) |
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1528,14 +1528,20 @@ export const LLM_MODELS: LLMModel[] = [
|
||||||
|
|
||||||
// MiniMax
|
// MiniMax
|
||||||
{
|
{
|
||||||
value: "MiniMax-M2.5",
|
value: "MiniMax-M3",
|
||||||
label: "MiniMax M2.5",
|
label: "MiniMax M3",
|
||||||
|
provider: "MINIMAX",
|
||||||
|
contextWindow: "512K",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "MiniMax-M2.7",
|
||||||
|
label: "MiniMax M2.7",
|
||||||
provider: "MINIMAX",
|
provider: "MINIMAX",
|
||||||
contextWindow: "204K",
|
contextWindow: "204K",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "MiniMax-M2.5-highspeed",
|
value: "MiniMax-M2.7-highspeed",
|
||||||
label: "MiniMax M2.5 Highspeed",
|
label: "MiniMax M2.7 Highspeed",
|
||||||
provider: "MINIMAX",
|
provider: "MINIMAX",
|
||||||
contextWindow: "204K",
|
contextWindow: "204K",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -184,8 +184,8 @@ export const LLM_PROVIDERS: LLMProvider[] = [
|
||||||
{
|
{
|
||||||
value: "MINIMAX",
|
value: "MINIMAX",
|
||||||
label: "MiniMax",
|
label: "MiniMax",
|
||||||
example: "MiniMax-M2.5, MiniMax-M2.5-highspeed",
|
example: "MiniMax-M3, MiniMax-M2.7",
|
||||||
description: "High-performance models with 204K context",
|
description: "High-performance models with up to 512K context",
|
||||||
apiBase: "https://api.minimax.io/v1",
|
apiBase: "https://api.minimax.io/v1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,109 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery as useZeroQuery } from "@rocicorp/zero/react";
|
||||||
import type { Run, RunListResponse } from "@/contracts/types/automation.types";
|
import { useQuery as useReactQuery } from "@tanstack/react-query";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import type { Run, RunStepResult, RunSummary } from "@/contracts/types/automation.types";
|
||||||
import { automationsApiService } from "@/lib/apis/automations-api.service";
|
import { automationsApiService } from "@/lib/apis/automations-api.service";
|
||||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
|
import { queries } from "@/zero/queries";
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 50;
|
const DEFAULT_LIMIT = 50;
|
||||||
const DEFAULT_OFFSET = 0;
|
|
||||||
|
/**
|
||||||
|
* Thin live row sourced from Zero. Strict superset of {@link RunSummary} —
|
||||||
|
* existing consumers that only look at the summary fields keep working,
|
||||||
|
* while the run detail panel can read ``step_results`` directly for the
|
||||||
|
* live step ticker without a second REST round-trip.
|
||||||
|
*/
|
||||||
|
export interface LiveRunSummary extends RunSummary {
|
||||||
|
step_results: RunStepResult[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface UseAutomationRunsOptions {
|
export interface UseAutomationRunsOptions {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Paginated run history for one automation. Newest-first per backend. */
|
interface UseAutomationRunsResult {
|
||||||
|
data: { items: LiveRunSummary[]; total: number } | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live run history for one automation, newest-first. Sourced from Zero's
|
||||||
|
* thin ``automation_runs`` publication so status and per-step progress
|
||||||
|
* tick in real time without polling. Heavy fields (output, artifacts,
|
||||||
|
* inputs, error, definition_snapshot) are still fetched lazily via
|
||||||
|
* {@link useAutomationRun}.
|
||||||
|
*/
|
||||||
export function useAutomationRuns(
|
export function useAutomationRuns(
|
||||||
automationId: number | undefined,
|
automationId: number | undefined,
|
||||||
{ limit = DEFAULT_LIMIT, offset = DEFAULT_OFFSET, enabled = true }: UseAutomationRunsOptions = {}
|
{ limit = DEFAULT_LIMIT }: UseAutomationRunsOptions = {}
|
||||||
) {
|
): UseAutomationRunsResult {
|
||||||
return useQuery<RunListResponse, Error>({
|
const [rows, result] = useZeroQuery(
|
||||||
queryKey: cacheKeys.automations.runs(automationId ?? 0, limit, offset),
|
queries.automationRuns.byAutomation({ automationId: automationId ?? -1 })
|
||||||
queryFn: () => automationsApiService.listRuns(automationId as number, { limit, offset }),
|
);
|
||||||
enabled: enabled && !!automationId,
|
|
||||||
staleTime: 30_000,
|
const items = useMemo<LiveRunSummary[]>(() => {
|
||||||
});
|
if (!automationId) return [];
|
||||||
|
return rows.slice(0, limit).map(toLiveRunSummary);
|
||||||
|
}, [automationId, rows, limit]);
|
||||||
|
|
||||||
|
const total = automationId ? rows.length : 0;
|
||||||
|
|
||||||
|
// Pre-hydration window: nothing visible AND Zero hasn't confirmed
|
||||||
|
// completeness yet. After the first sync (even an empty set) we stop
|
||||||
|
// showing the skeleton so the empty-state copy can take over.
|
||||||
|
const isLoading = !!automationId && result.type !== "complete" && rows.length === 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: automationId ? { items, total } : undefined,
|
||||||
|
isLoading,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Single run with the full snapshot, step results, output and artifacts. */
|
/**
|
||||||
|
* Full run record (definition snapshot, inputs, output, artifacts, error).
|
||||||
|
* Stays on REST: these fields are large and largely static after the run
|
||||||
|
* finishes, so they're not worth replicating to every connected client.
|
||||||
|
*/
|
||||||
export function useAutomationRun(
|
export function useAutomationRun(
|
||||||
automationId: number | undefined,
|
automationId: number | undefined,
|
||||||
runId: number | undefined,
|
runId: number | undefined,
|
||||||
options: { enabled?: boolean } = {}
|
options: { enabled?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
const { enabled = true } = options;
|
const { enabled = true } = options;
|
||||||
return useQuery<Run, Error>({
|
return useReactQuery<Run, Error>({
|
||||||
queryKey: cacheKeys.automations.run(automationId ?? 0, runId ?? 0),
|
queryKey: cacheKeys.automations.run(automationId ?? 0, runId ?? 0),
|
||||||
queryFn: () => automationsApiService.getRun(automationId as number, runId as number),
|
queryFn: () => automationsApiService.getRun(automationId as number, runId as number),
|
||||||
enabled: enabled && !!automationId && !!runId,
|
enabled: enabled && !!automationId && !!runId,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ZeroAutomationRunRow {
|
||||||
|
id: number;
|
||||||
|
automationId: number;
|
||||||
|
triggerId?: number | null;
|
||||||
|
status: string;
|
||||||
|
stepResults: unknown;
|
||||||
|
startedAt?: number | null;
|
||||||
|
finishedAt?: number | null;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adapt a Zero camelCase row (epoch ms timestamps) to the snake_case
|
||||||
|
* ISO-string ``RunSummary`` shape the existing UI already consumes. */
|
||||||
|
function toLiveRunSummary(row: ZeroAutomationRunRow): LiveRunSummary {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
automation_id: row.automationId,
|
||||||
|
trigger_id: row.triggerId ?? null,
|
||||||
|
status: row.status as RunSummary["status"],
|
||||||
|
started_at: row.startedAt ? new Date(row.startedAt).toISOString() : null,
|
||||||
|
finished_at: row.finishedAt ? new Date(row.finishedAt).toISOString() : null,
|
||||||
|
created_at: new Date(row.createdAt).toISOString(),
|
||||||
|
step_results: Array.isArray(row.stepResults) ? (row.stepResults as RunStepResult[]) : [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
14
surfsense_web/zero/queries/automations.ts
Normal file
14
surfsense_web/zero/queries/automations.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineQuery } from "@rocicorp/zero";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zql } from "../schema/index";
|
||||||
|
|
||||||
|
// Mirrors chat byThread: client passes the parent id, the REST route still
|
||||||
|
// authorizes via `automation_id -> search_space`. No search_space_id on the
|
||||||
|
// table by design.
|
||||||
|
export const automationRunQueries = {
|
||||||
|
byAutomation: defineQuery(
|
||||||
|
z.object({ automationId: z.number() }),
|
||||||
|
({ args: { automationId } }) =>
|
||||||
|
zql.automation_runs.where("automationId", automationId).orderBy("createdAt", "desc")
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { defineQueries } from "@rocicorp/zero";
|
import { defineQueries } from "@rocicorp/zero";
|
||||||
|
import { automationRunQueries } from "./automations";
|
||||||
import { chatSessionQueries, commentQueries, messageQueries } from "./chat";
|
import { chatSessionQueries, commentQueries, messageQueries } from "./chat";
|
||||||
import { connectorQueries, documentQueries } from "./documents";
|
import { connectorQueries, documentQueries } from "./documents";
|
||||||
import { folderQueries } from "./folders";
|
import { folderQueries } from "./folders";
|
||||||
|
|
@ -14,4 +15,5 @@ export const queries = defineQueries({
|
||||||
comments: commentQueries,
|
comments: commentQueries,
|
||||||
chatSession: chatSessionQueries,
|
chatSession: chatSessionQueries,
|
||||||
user: userQueries,
|
user: userQueries,
|
||||||
|
automationRuns: automationRunQueries,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
18
surfsense_web/zero/schema/automations.ts
Normal file
18
surfsense_web/zero/schema/automations.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { json, number, string, table } from "@rocicorp/zero";
|
||||||
|
|
||||||
|
// Thin live row: status + per-step progress only. Heavy fields
|
||||||
|
// (definition_snapshot, inputs, output, artifacts, error) stay on REST
|
||||||
|
// (`GET /automations/{id}/runs/{run_id}`) and load on detail expand.
|
||||||
|
// Mirrors the publication shape in migration 148.
|
||||||
|
export const automationRunTable = table("automation_runs")
|
||||||
|
.columns({
|
||||||
|
id: number(),
|
||||||
|
automationId: number().from("automation_id"),
|
||||||
|
triggerId: number().optional().from("trigger_id"),
|
||||||
|
status: string(),
|
||||||
|
stepResults: json().from("step_results"),
|
||||||
|
startedAt: number().optional().from("started_at"),
|
||||||
|
finishedAt: number().optional().from("finished_at"),
|
||||||
|
createdAt: number().from("created_at"),
|
||||||
|
})
|
||||||
|
.primaryKey("id");
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createBuilder, createSchema, relationships } from "@rocicorp/zero";
|
import { createBuilder, createSchema, relationships } from "@rocicorp/zero";
|
||||||
|
import { automationRunTable } from "./automations";
|
||||||
import { chatCommentTable, chatSessionStateTable, newChatMessageTable } from "./chat";
|
import { chatCommentTable, chatSessionStateTable, newChatMessageTable } from "./chat";
|
||||||
import { documentTable, searchSourceConnectorTable } from "./documents";
|
import { documentTable, searchSourceConnectorTable } from "./documents";
|
||||||
import { folderTable } from "./folders";
|
import { folderTable } from "./folders";
|
||||||
|
|
@ -36,6 +37,7 @@ export const schema = createSchema({
|
||||||
chatCommentTable,
|
chatCommentTable,
|
||||||
chatSessionStateTable,
|
chatSessionStateTable,
|
||||||
userTable,
|
userTable,
|
||||||
|
automationRunTable,
|
||||||
],
|
],
|
||||||
relationships: [chatCommentRelationships, newChatMessageRelationships],
|
relationships: [chatCommentRelationships, newChatMessageRelationships],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue