Add link editing, AI re-description, and footer

Links can now be edited manually (title, summary, category,
manufacturer, tags) via an inline edit form, with a "KI neu
beschreiben lassen" action that re-fetches the URL and lets the AI
regenerate all fields. Editing recomputes the search embedding.

Also adds an app footer showing author, version, and hostname.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-07-19 21:54:01 +02:00
parent cc10776734
commit a0db3e5147
5 changed files with 209 additions and 5 deletions

View File

@@ -19,6 +19,11 @@ app = FastAPI(title="LinkVault")
app.add_middleware(SessionMiddleware, secret_key=config.SECRET_KEY, max_age=60 * 60 * 24 * 14)
templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
templates.env.globals.update(
app_author=config.APP_AUTHOR,
app_version=config.APP_VERSION,
app_host=config.APP_HOST,
)
# ---------------------------------------------------------------------------
@@ -281,3 +286,130 @@ def delete_link(
db.delete(link)
db.commit()
return Response(status_code=200)
def _get_owned_link(db: Session, user: User, link_id: int) -> Link | None:
link = db.get(Link, link_id)
if link and link.user_id == user.id:
return link
return None
def _parse_tags(raw: str) -> list[str]:
return [t.strip() for t in raw.split(",") if t.strip()][:8]
@app.get("/links/{link_id}/edit", response_class=HTMLResponse)
def edit_link_form(
link_id: int,
request: Request,
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return Response(status_code=401, headers={"HX-Redirect": "/login"})
link = _get_owned_link(db, user, link_id)
if not link:
return Response(status_code=404)
return templates.TemplateResponse(
"_link_card_edit.html",
{"request": request, "link": link, "ai_enabled": config.AI_ENABLED},
)
@app.get("/links/{link_id}/view", response_class=HTMLResponse)
def view_link_card(
link_id: int,
request: Request,
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return Response(status_code=401, headers={"HX-Redirect": "/login"})
link = _get_owned_link(db, user, link_id)
if not link:
return Response(status_code=404)
return templates.TemplateResponse(
"_link_card.html", {"request": request, "link": link}
)
@app.put("/links/{link_id}", response_class=HTMLResponse)
def update_link(
link_id: int,
request: Request,
title: str = Form(""),
summary: str = Form(""),
category: str = Form(""),
manufacturer: str = Form(""),
tags: str = Form(""),
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return Response(status_code=401, headers={"HX-Redirect": "/login"})
link = _get_owned_link(db, user, link_id)
if not link:
return Response(status_code=404)
link.title = title.strip()[:300] or link.url
link.summary = summary.strip()[:1000]
link.category = category.strip()[:120]
link.manufacturer = manufacturer.strip()[:120]
link.tags = json.dumps(_parse_tags(tags), ensure_ascii=False)
embed_source = " ".join([link.title, link.summary, tags])
embedding = ai.embed(embed_source)
if embedding:
link.embedding = json.dumps(embedding)
db.commit()
db.refresh(link)
return templates.TemplateResponse(
"_link_card.html", {"request": request, "link": link}
)
@app.post("/links/{link_id}/reanalyze", response_class=HTMLResponse)
def reanalyze_link(
link_id: int,
request: Request,
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return Response(status_code=401, headers={"HX-Redirect": "/login"})
link = _get_owned_link(db, user, link_id)
if not link:
return Response(status_code=404)
title, text = scraper.fetch(link.url)
existing = facets(db, user)
meta = ai.categorize(
link.url,
title,
text,
[name for name, _ in existing["categories"] if name != link.category],
[name for name, _ in existing["manufacturers"] if name != link.manufacturer],
)
embed_source = " ".join(
[meta["title"], meta["summary"], " ".join(meta["tags"]), text[:1000]]
)
embedding = ai.embed(embed_source)
link.title = meta["title"]
link.summary = meta["summary"]
link.category = meta["category"]
link.manufacturer = meta["manufacturer"]
link.tags = json.dumps(meta["tags"], ensure_ascii=False)
if embedding:
link.embedding = json.dumps(embedding)
link.status = "ok" if text else "no_content"
db.commit()
db.refresh(link)
return templates.TemplateResponse(
"_link_card_edit.html",
{"request": request, "link": link, "ai_enabled": config.AI_ENABLED},
)