Feat: Display release status in agent version history. (#13479)

### What problem does this PR solve?
Feat: Display release status in agent version history.

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

---------

Co-authored-by: balibabu <assassin_cike@163.com>
This commit is contained in:
balibabu
2026-03-10 14:25:27 +08:00
committed by GitHub
parent 249b78561b
commit aaf900cf16
14 changed files with 148 additions and 23 deletions

View File

@ -99,6 +99,7 @@ async def save():
user_canvas_id=req["id"],
dsl=req["dsl"],
title=UserCanvasVersionService.build_version_title(getattr(current_user, "nickname", current_user.id), req.get("title")),
release=req.get("release"),
)
replica_ok = CanvasReplicaService.replace_for_set(
canvas_id=req["id"],
@ -133,6 +134,25 @@ def get(canvas_id):
)
except ValueError as e:
return get_data_error_result(message=str(e))
# Get the last publication time (latest released version's update_time)
last_publish_time = None
versions = UserCanvasVersionService.list_by_canvas_id(canvas_id)
if versions:
released_versions = [v for v in versions if v.release]
if released_versions:
# Sort by update_time descending and get the latest
released_versions.sort(key=lambda x: x.update_time, reverse=True)
last_publish_time = released_versions[0].update_time
# Add last_publish_time to response data
if isinstance(c, dict):
c["last_publish_time"] = last_publish_time
else:
# If c is a model object, convert to dict first
c = c.to_dict()
c["last_publish_time"] = last_publish_time
return get_json_result(data=c)

View File

@ -1075,6 +1075,7 @@ class UserCanvasVersion(DataBaseModel):
title = CharField(max_length=255, null=True, help_text="Canvas title")
description = TextField(null=True, help_text="Canvas description")
release = BooleanField(null=False, help_text="is released", default=False, index=True)
dsl = JSONField(null=True, default={})
class Meta:
@ -1538,6 +1539,7 @@ def migrate_db():
alter_db_add_column(migrator, "dialog", "tenant_rerank_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
alter_db_add_column(migrator, "memory", "tenant_embd_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
alter_db_add_column(migrator, "memory", "tenant_llm_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
alter_db_add_column(migrator, "user_canvas_version", "release", BooleanField(null=False, help_text="is released", default=False, index=True))
logging.disable(logging.NOTSET)
# this is after re-enabling logging to allow logging changed user emails
migrate_add_unique_email(migrator)

View File

@ -19,7 +19,7 @@ import time
from uuid import uuid4
from agent.canvas import Canvas
from api.db import CanvasCategory, TenantPermission
from api.db.db_models import DB, CanvasTemplate, User, UserCanvas, API4Conversation
from api.db.db_models import DB, CanvasTemplate, User, UserCanvas, API4Conversation, UserCanvasVersion
from api.db.services.api_service import API4ConversationService
from api.db.services.common_service import CommonService
from common.misc_utils import get_uuid
@ -173,7 +173,23 @@ class UserCanvasService(CommonService):
count = agents.count()
if page_number and items_per_page:
agents = agents.paginate(page_number, items_per_page)
return list(agents.dicts()), count
agents_list = list(agents.dicts())
# Get latest release time for each canvas
if agents_list:
canvas_ids = [a['id'] for a in agents_list]
release_times = (
UserCanvasVersion.select(UserCanvasVersion.user_canvas_id, fn.MAX(UserCanvasVersion.create_time).alias("release_time"))
.where((UserCanvasVersion.user_canvas_id.in_(canvas_ids)) & (UserCanvasVersion.release))
.group_by(UserCanvasVersion.user_canvas_id)
)
release_time_map = {r.user_canvas_id: r.release_time for r in release_times}
for agent in agents_list:
agent['release_time'] = release_time_map.get(agent['id'])
return agents_list, count
@classmethod
@DB.connection_context()

View File

@ -45,7 +45,8 @@ class UserCanvasVersionService(CommonService):
cls.model.create_date,
cls.model.update_date,
cls.model.user_canvas_id,
cls.model.update_time]
cls.model.update_time,
cls.model.release]
).where(cls.model.user_canvas_id == user_canvas_id)
return user_canvas_version
except DoesNotExist:
@ -74,14 +75,14 @@ class UserCanvasVersionService(CommonService):
@DB.connection_context()
def delete_all_versions(cls, user_canvas_id):
try:
user_canvas_version = cls.model.select().where(cls.model.user_canvas_id == user_canvas_id).order_by(
cls.model.create_time.desc())
if user_canvas_version.count() > 20:
delete_ids = []
for i in range(20, user_canvas_version.count()):
delete_ids.append(user_canvas_version[i].id)
# Only get unpublished versions (False or None), keep all released versions
unpublished = cls.model.select().where(cls.model.user_canvas_id == user_canvas_id, (~cls.model.release) | (cls.model.release.is_null(True))).order_by(cls.model.create_time.desc())
# Only delete old unpublished versions beyond the limit
if unpublished.count() > 20:
delete_ids = [v.id for v in unpublished[20:]]
cls.delete_by_ids(delete_ids)
return True
except DoesNotExist:
return None
@ -90,12 +91,15 @@ class UserCanvasVersionService(CommonService):
@classmethod
@DB.connection_context()
def save_or_replace_latest(cls, user_canvas_id, dsl, title=None, description=None):
def save_or_replace_latest(cls, user_canvas_id, dsl, title=None, description=None, release=None):
"""
Persist a canvas snapshot into version history.
If the latest version has the same DSL content, update that version in place
instead of creating a new row.
Exception: If the latest version is released (release=True) and current save is not,
create a new version to protect the released version.
"""
try:
normalized_dsl = cls._normalize_dsl(dsl)
@ -107,11 +111,28 @@ class UserCanvasVersionService(CommonService):
)
if latest and cls._normalize_dsl(latest.dsl) == normalized_dsl:
# Protect released version: if latest is released and current is not,
# create a new version instead of updating
if latest.release and not release:
insert_data = {"user_canvas_id": user_canvas_id, "dsl": normalized_dsl}
if title is not None:
insert_data["title"] = title
if description is not None:
insert_data["description"] = description
if release is not None:
insert_data["release"] = release
cls.insert(**insert_data)
cls.delete_all_versions(user_canvas_id)
return None, True
# Normal case: update existing version
update_data = {"dsl": normalized_dsl}
if title is not None:
update_data["title"] = title
if description is not None:
update_data["description"] = description
if release is not None:
update_data["release"] = release
cls.update_by_id(latest.id, update_data)
cls.delete_all_versions(user_canvas_id)
return latest.id, False
@ -121,6 +142,8 @@ class UserCanvasVersionService(CommonService):
insert_data["title"] = title
if description is not None:
insert_data["description"] = description
if release is not None:
insert_data["release"] = release
cls.insert(**insert_data)
cls.delete_all_versions(user_canvas_id)
return None, True