feat(product-suite): add global generation history
This commit is contained in:
@@ -128,6 +128,29 @@ class ImageStudioGenerationRound:
|
||||
is_legacy: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ImageStudioHistoryRound:
|
||||
"""A persisted generation round with enough project context for global history."""
|
||||
|
||||
project_id: int
|
||||
account_alias: str
|
||||
account_name: Optional[str]
|
||||
item_id: str
|
||||
binding_state: str
|
||||
generation_round_key: Optional[str]
|
||||
created_at: Optional[str]
|
||||
updated_at: Optional[str]
|
||||
job_count: int
|
||||
slot_count: int
|
||||
succeeded_count: int
|
||||
failed_count: int
|
||||
cancelled_count: int
|
||||
active_count: int
|
||||
retry_count: int
|
||||
is_current: bool
|
||||
is_legacy: bool
|
||||
|
||||
|
||||
class ImageStudioError(RuntimeError):
|
||||
"""Raised when the AI image studio service cannot complete an operation."""
|
||||
|
||||
@@ -1296,6 +1319,147 @@ def list_generation_rounds(project_id, *, limit=None, offset=0, path=None, conn=
|
||||
return rounds
|
||||
|
||||
|
||||
def list_global_generation_rounds(
|
||||
*,
|
||||
account_query="",
|
||||
item_query="",
|
||||
project_id=None,
|
||||
limit=None,
|
||||
offset=0,
|
||||
path=None,
|
||||
conn=None,
|
||||
):
|
||||
"""List persisted generation rounds across active projects without loading assets."""
|
||||
|
||||
try:
|
||||
offset = max(0, int(offset or 0))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise db.DbError("全局历史偏移量无效") from exc
|
||||
if limit is not None:
|
||||
try:
|
||||
limit = int(limit)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise db.DbError("全局历史数量无效") from exc
|
||||
if limit <= 0:
|
||||
return []
|
||||
|
||||
clauses = ["projects.deleted_at IS NULL"]
|
||||
params = []
|
||||
if project_id is not None:
|
||||
try:
|
||||
clauses.append("projects.id = ?")
|
||||
params.append(int(project_id))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise db.DbError("当前商品项目无效") from exc
|
||||
account_text = str(account_query or "").strip()
|
||||
if account_text:
|
||||
pattern = "%%%s%%" % account_text
|
||||
clauses.append(
|
||||
"(projects.account_alias LIKE ? OR COALESCE(projects.account_name, '') LIKE ?)"
|
||||
)
|
||||
params.extend([pattern, pattern])
|
||||
item_text = str(item_query or "").strip()
|
||||
if item_text:
|
||||
clauses.append("projects.item_id LIKE ?")
|
||||
params.append("%%%s%%" % item_text)
|
||||
|
||||
sql = """
|
||||
WITH latest_round_jobs AS (
|
||||
SELECT project_id,
|
||||
generation_round_key,
|
||||
generation_slot_index,
|
||||
MAX(id) AS latest_id
|
||||
FROM image_studio_jobs
|
||||
WHERE generation_round_key IS NOT NULL
|
||||
GROUP BY project_id, generation_round_key, generation_slot_index
|
||||
),
|
||||
effective_jobs AS (
|
||||
SELECT jobs.*
|
||||
FROM image_studio_jobs AS jobs
|
||||
LEFT JOIN latest_round_jobs AS latest
|
||||
ON latest.latest_id = jobs.id
|
||||
WHERE jobs.generation_round_key IS NULL OR latest.latest_id IS NOT NULL
|
||||
),
|
||||
round_attempts AS (
|
||||
SELECT project_id,
|
||||
generation_round_key,
|
||||
COUNT(*) AS attempt_count
|
||||
FROM image_studio_jobs
|
||||
GROUP BY project_id, generation_round_key
|
||||
)
|
||||
SELECT projects.id AS project_id,
|
||||
projects.account_alias AS account_alias,
|
||||
projects.account_name AS account_name,
|
||||
projects.item_id AS item_id,
|
||||
projects.binding_state AS binding_state,
|
||||
jobs.generation_round_key AS generation_round_key,
|
||||
MIN(jobs.created_at) AS created_at,
|
||||
MAX(jobs.updated_at) AS updated_at,
|
||||
COUNT(*) AS job_count,
|
||||
COUNT(DISTINCT jobs.generation_slot_index) AS slot_count,
|
||||
MAX(attempts.attempt_count) AS attempt_count,
|
||||
SUM(CASE WHEN jobs.status = 'succeeded' THEN 1 ELSE 0 END) AS succeeded_count,
|
||||
SUM(CASE WHEN jobs.status IN ('failed', 'expired') THEN 1 ELSE 0 END) AS failed_count,
|
||||
SUM(CASE WHEN jobs.status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
|
||||
SUM(CASE WHEN jobs.status IN ('pending', 'submitted', 'running') THEN 1 ELSE 0 END) AS active_count,
|
||||
CASE
|
||||
WHEN jobs.generation_round_key IS NOT NULL
|
||||
AND jobs.generation_round_key = projects.current_generation_round_key
|
||||
THEN 1 ELSE 0
|
||||
END AS is_current,
|
||||
CASE WHEN jobs.generation_round_key IS NULL THEN 1 ELSE 0 END AS is_legacy
|
||||
FROM image_studio_projects AS projects
|
||||
INNER JOIN effective_jobs AS jobs ON jobs.project_id = projects.id
|
||||
INNER JOIN round_attempts AS attempts
|
||||
ON attempts.project_id = jobs.project_id
|
||||
AND attempts.generation_round_key IS jobs.generation_round_key
|
||||
WHERE %s
|
||||
GROUP BY projects.id, jobs.generation_round_key
|
||||
ORDER BY MIN(jobs.created_at) DESC,
|
||||
MAX(jobs.id) DESC
|
||||
""" % " AND ".join(clauses)
|
||||
if limit is not None:
|
||||
sql += " LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
elif offset:
|
||||
sql += " LIMIT -1 OFFSET ?"
|
||||
params.append(offset)
|
||||
with _connection(conn, path) as database:
|
||||
rows = database.execute(sql, params).fetchall()
|
||||
|
||||
rounds = []
|
||||
for row in rows:
|
||||
round_key = row["generation_round_key"]
|
||||
job_count = int(row["job_count"] or 0)
|
||||
slot_count = int(row["slot_count"] or 0)
|
||||
rounds.append(
|
||||
ImageStudioHistoryRound(
|
||||
project_id=int(row["project_id"]),
|
||||
account_alias=str(row["account_alias"] or ""),
|
||||
account_name=row["account_name"],
|
||||
item_id=str(row["item_id"] or ""),
|
||||
binding_state=str(row["binding_state"] or ""),
|
||||
generation_round_key=round_key,
|
||||
created_at=row["created_at"],
|
||||
updated_at=row["updated_at"],
|
||||
job_count=job_count,
|
||||
slot_count=slot_count,
|
||||
succeeded_count=int(row["succeeded_count"] or 0),
|
||||
failed_count=int(row["failed_count"] or 0),
|
||||
cancelled_count=int(row["cancelled_count"] or 0),
|
||||
active_count=int(row["active_count"] or 0),
|
||||
retry_count=(
|
||||
max(0, int(row["attempt_count"] or 0) - job_count)
|
||||
if round_key
|
||||
else 0
|
||||
),
|
||||
is_current=bool(row["is_current"]),
|
||||
is_legacy=bool(row["is_legacy"]),
|
||||
)
|
||||
)
|
||||
return rounds
|
||||
|
||||
|
||||
def list_generation_round_current_jobs(project_id, generation_round_key, path=None, conn=None):
|
||||
project_id = int(project_id)
|
||||
if generation_round_key is None:
|
||||
|
||||
Reference in New Issue
Block a user