Store and use activity_id for L2 links
- Add activity_id column to l2_shares table
- Store activity_id when publishing to L2
- Link to /activities/{activity_id} instead of /assets/{name}
- Falls back to asset link if no activity_id
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
30
database.py
30
database.py
@@ -65,12 +65,19 @@ CREATE TABLE IF NOT EXISTS l2_shares (
|
|||||||
actor_id VARCHAR(255) NOT NULL,
|
actor_id VARCHAR(255) NOT NULL,
|
||||||
l2_server VARCHAR(255) NOT NULL,
|
l2_server VARCHAR(255) NOT NULL,
|
||||||
asset_name VARCHAR(255) NOT NULL,
|
asset_name VARCHAR(255) NOT NULL,
|
||||||
|
activity_id VARCHAR(128),
|
||||||
content_type VARCHAR(50) NOT NULL,
|
content_type VARCHAR(50) NOT NULL,
|
||||||
published_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
published_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
last_synced_at TIMESTAMP WITH TIME ZONE,
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
||||||
UNIQUE(content_hash, actor_id, l2_server, content_type)
|
UNIQUE(content_hash, actor_id, l2_server, content_type)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Add activity_id column if it doesn't exist (for existing databases)
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE l2_shares ADD COLUMN IF NOT EXISTS activity_id VARCHAR(128);
|
||||||
|
EXCEPTION WHEN others THEN NULL;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- Run cache: maps content-addressable run_id to output
|
-- Run cache: maps content-addressable run_id to output
|
||||||
-- run_id is a hash of (sorted inputs + recipe), making runs deterministic
|
-- run_id is a hash of (sorted inputs + recipe), making runs deterministic
|
||||||
CREATE TABLE IF NOT EXISTS run_cache (
|
CREATE TABLE IF NOT EXISTS run_cache (
|
||||||
@@ -512,7 +519,7 @@ async def get_l2_shares(content_hash: str, actor_id: Optional[str] = None) -> Li
|
|||||||
if actor_id:
|
if actor_id:
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT id, content_hash, actor_id, l2_server, asset_name, content_type, published_at, last_synced_at
|
SELECT id, content_hash, actor_id, l2_server, asset_name, activity_id, content_type, published_at, last_synced_at
|
||||||
FROM l2_shares
|
FROM l2_shares
|
||||||
WHERE content_hash = $1 AND actor_id = $2
|
WHERE content_hash = $1 AND actor_id = $2
|
||||||
ORDER BY published_at
|
ORDER BY published_at
|
||||||
@@ -522,7 +529,7 @@ async def get_l2_shares(content_hash: str, actor_id: Optional[str] = None) -> Li
|
|||||||
else:
|
else:
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT id, content_hash, actor_id, l2_server, asset_name, content_type, published_at, last_synced_at
|
SELECT id, content_hash, actor_id, l2_server, asset_name, activity_id, content_type, published_at, last_synced_at
|
||||||
FROM l2_shares
|
FROM l2_shares
|
||||||
WHERE content_hash = $1
|
WHERE content_hash = $1
|
||||||
ORDER BY published_at
|
ORDER BY published_at
|
||||||
@@ -780,7 +787,7 @@ async def load_item_metadata(content_hash: str, actor_id: Optional[str] = None)
|
|||||||
if actor_id:
|
if actor_id:
|
||||||
shares = await conn.fetch(
|
shares = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT l2_server, asset_name, content_type, published_at, last_synced_at
|
SELECT l2_server, asset_name, activity_id, content_type, published_at, last_synced_at
|
||||||
FROM l2_shares WHERE content_hash = $1 AND actor_id = $2
|
FROM l2_shares WHERE content_hash = $1 AND actor_id = $2
|
||||||
""",
|
""",
|
||||||
content_hash, actor_id
|
content_hash, actor_id
|
||||||
@@ -788,7 +795,7 @@ async def load_item_metadata(content_hash: str, actor_id: Optional[str] = None)
|
|||||||
else:
|
else:
|
||||||
shares = await conn.fetch(
|
shares = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT l2_server, asset_name, content_type, published_at, last_synced_at
|
SELECT l2_server, asset_name, activity_id, content_type, published_at, last_synced_at
|
||||||
FROM l2_shares WHERE content_hash = $1
|
FROM l2_shares WHERE content_hash = $1
|
||||||
""",
|
""",
|
||||||
content_hash
|
content_hash
|
||||||
@@ -799,6 +806,7 @@ async def load_item_metadata(content_hash: str, actor_id: Optional[str] = None)
|
|||||||
{
|
{
|
||||||
"l2_server": s["l2_server"],
|
"l2_server": s["l2_server"],
|
||||||
"asset_name": s["asset_name"],
|
"asset_name": s["asset_name"],
|
||||||
|
"activity_id": s["activity_id"],
|
||||||
"content_type": s["content_type"],
|
"content_type": s["content_type"],
|
||||||
"published_at": s["published_at"].isoformat() if s["published_at"] else None,
|
"published_at": s["published_at"].isoformat() if s["published_at"] else None,
|
||||||
"last_synced_at": s["last_synced_at"].isoformat() if s["last_synced_at"] else None,
|
"last_synced_at": s["last_synced_at"].isoformat() if s["last_synced_at"] else None,
|
||||||
@@ -810,6 +818,7 @@ async def load_item_metadata(content_hash: str, actor_id: Optional[str] = None)
|
|||||||
result["published"] = {
|
result["published"] = {
|
||||||
"to_l2": True,
|
"to_l2": True,
|
||||||
"asset_name": shares[0]["asset_name"],
|
"asset_name": shares[0]["asset_name"],
|
||||||
|
"activity_id": shares[0]["activity_id"],
|
||||||
"l2_server": shares[0]["l2_server"],
|
"l2_server": shares[0]["l2_server"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -944,24 +953,27 @@ async def save_l2_share(
|
|||||||
actor_id: str,
|
actor_id: str,
|
||||||
l2_server: str,
|
l2_server: str,
|
||||||
asset_name: str,
|
asset_name: str,
|
||||||
content_type: str = "media"
|
content_type: str = "media",
|
||||||
|
activity_id: Optional[str] = None
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Save an L2 share and return share info."""
|
"""Save an L2 share and return share info."""
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
row = await conn.fetchrow(
|
row = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
INSERT INTO l2_shares (content_hash, actor_id, l2_server, asset_name, content_type, last_synced_at)
|
INSERT INTO l2_shares (content_hash, actor_id, l2_server, asset_name, activity_id, content_type, last_synced_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, NOW())
|
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
||||||
ON CONFLICT (content_hash, actor_id, l2_server, content_type) DO UPDATE SET
|
ON CONFLICT (content_hash, actor_id, l2_server, content_type) DO UPDATE SET
|
||||||
asset_name = EXCLUDED.asset_name,
|
asset_name = EXCLUDED.asset_name,
|
||||||
|
activity_id = COALESCE(EXCLUDED.activity_id, l2_shares.activity_id),
|
||||||
last_synced_at = NOW()
|
last_synced_at = NOW()
|
||||||
RETURNING l2_server, asset_name, content_type, published_at, last_synced_at
|
RETURNING l2_server, asset_name, activity_id, content_type, published_at, last_synced_at
|
||||||
""",
|
""",
|
||||||
content_hash, actor_id, l2_server, asset_name, content_type
|
content_hash, actor_id, l2_server, asset_name, activity_id, content_type
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"l2_server": row["l2_server"],
|
"l2_server": row["l2_server"],
|
||||||
"asset_name": row["asset_name"],
|
"asset_name": row["asset_name"],
|
||||||
|
"activity_id": row["activity_id"],
|
||||||
"content_type": row["content_type"],
|
"content_type": row["content_type"],
|
||||||
"published_at": row["published_at"].isoformat() if row["published_at"] else None,
|
"published_at": row["published_at"].isoformat() if row["published_at"] else None,
|
||||||
"last_synced_at": row["last_synced_at"].isoformat() if row["last_synced_at"] else None,
|
"last_synced_at": row["last_synced_at"].isoformat() if row["last_synced_at"] else None,
|
||||||
|
|||||||
13
server.py
13
server.py
@@ -1126,13 +1126,16 @@ async def run_detail(run_id: str, request: Request):
|
|||||||
l2_server = share.get("l2_server", "")
|
l2_server = share.get("l2_server", "")
|
||||||
l2_https = l2_server.replace("http://", "https://")
|
l2_https = l2_server.replace("http://", "https://")
|
||||||
asset_name = share.get("asset_name", "")
|
asset_name = share.get("asset_name", "")
|
||||||
|
activity_id = share.get("activity_id")
|
||||||
|
# Link to activity if available, otherwise fall back to asset
|
||||||
|
l2_link = f"{l2_https}/activities/{activity_id}" if activity_id else f"{l2_https}/assets/{asset_name}"
|
||||||
publish_html = f'''
|
publish_html = f'''
|
||||||
<div class="border-t border-dark-500 pt-6 mt-6">
|
<div class="border-t border-dark-500 pt-6 mt-6">
|
||||||
<h2 class="text-lg font-semibold text-white mb-3">Published to L2</h2>
|
<h2 class="text-lg font-semibold text-white mb-3">Published to L2</h2>
|
||||||
<div class="bg-green-900/30 border border-green-700 rounded-lg p-4">
|
<div class="bg-green-900/30 border border-green-700 rounded-lg p-4">
|
||||||
<p class="text-green-300">
|
<p class="text-green-300">
|
||||||
Published as <strong>{asset_name}</strong>
|
Published as <strong>{asset_name[:16]}...</strong>
|
||||||
<a href="{l2_https}/asset/{asset_name}" target="_blank" class="underline ml-2">View on L2</a>
|
<a href="{l2_link}" target="_blank" class="underline ml-2">View on L2</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4035,12 +4038,16 @@ async def ui_publish_run(run_id: str, request: Request):
|
|||||||
cache_path = get_cache_path(run.output_hash)
|
cache_path = get_cache_path(run.output_hash)
|
||||||
media_type = detect_media_type(cache_path) if cache_path else "image"
|
media_type = detect_media_type(cache_path) if cache_path else "image"
|
||||||
content_type = "video" if media_type == "video" else "image"
|
content_type = "video" if media_type == "video" else "image"
|
||||||
|
# Get activity_id for linking to the published run
|
||||||
|
activity = result.get("activity")
|
||||||
|
activity_id = activity.get("activity_id") if activity else None
|
||||||
await database.save_l2_share(
|
await database.save_l2_share(
|
||||||
content_hash=run.output_hash,
|
content_hash=run.output_hash,
|
||||||
actor_id=ctx.actor_id,
|
actor_id=ctx.actor_id,
|
||||||
l2_server=l2_server,
|
l2_server=l2_server,
|
||||||
asset_name=result["asset"]["name"],
|
asset_name=result["asset"]["name"],
|
||||||
content_type=content_type
|
content_type=content_type,
|
||||||
|
activity_id=activity_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pin the inputs (for provenance)
|
# Pin the inputs (for provenance)
|
||||||
|
|||||||
Reference in New Issue
Block a user