diff --git a/Endpoints/SharedEndpoint.cs b/Endpoints/SharedEndpoint.cs index 3be7cd0..cbb0acb 100644 --- a/Endpoints/SharedEndpoint.cs +++ b/Endpoints/SharedEndpoint.cs @@ -161,7 +161,38 @@ public static class SharedEndpoint { var userId = ctx.GetUserId(); var shares = await db.GetSharedMessagesByUser(userId); - await ctx.Response.WriteAsJsonAsync(shares); + + var result = new List(); + foreach (var s in shares) + { + var message = await db.GetMessage(s.MessageId); + string sessionName = null; + if (message != null) + { + var chain = await db.GetChain(message.ChainId); + if (chain != null) + { + var session = await db.GetSession(chain.SessionId); + sessionName = session?.Name; + } + } + + var preview = message?.Content ?? ""; + if (preview.Length > 150) preview = preview.Substring(0, 150) + "…"; + + result.Add(new + { + s.ShareToken, + s.MessageId, + s.IncludeContext, + s.CreatedAt, + role = message?.Role, + preview, + sessionName + }); + } + + await ctx.Response.WriteAsJsonAsync(result); } private static async Task HandleDeleteShare(HttpContext ctx, DatabaseService db) diff --git a/wwwroot/css/dashboard.css b/wwwroot/css/dashboard.css index 7dc57f9..d76b223 100644 --- a/wwwroot/css/dashboard.css +++ b/wwwroot/css/dashboard.css @@ -256,6 +256,66 @@ color: var(--text); } +/* Shared Links */ + +.shared-links-list { + display: flex; + flex-direction: column; + gap: var(--badge-spacing); +} + +.shared-link-row { + display: flex; + align-items: center; + gap: var(--box-spacing); + padding: var(--box-padding); + background: var(--f3-bg); + border: var(--border-width) solid var(--f3-border); + border-radius: var(--border-radius); +} + +.shared-link-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: calc(var(--badge-spacing) / 2); +} + +.shared-link-preview { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.shared-link-preview .fa { + opacity: var(--semi-transparent); + margin-right: calc(var(--badge-spacing) / 2); +} + +.shared-link-meta { + display: flex; + flex-wrap: wrap; + gap: var(--box-spacing); + opacity: var(--semi-transparent); + font-size: calc(var(--font-size) * 0.9); +} + +.shared-link-actions { + display: flex; + gap: var(--badge-spacing); + flex-shrink: 0; +} + +.danger-icon { + color: var(--dangerous); + opacity: var(--semi-transparent); +} + +.danger-icon:hover { + opacity: 1; +} + /* Compact cards (no image) */ .card-compact { padding: var(--box-padding); diff --git a/wwwroot/index.html b/wwwroot/index.html index e9f03b4..541687e 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -80,6 +80,12 @@
+ diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index 0a90c2a..cc4c571 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -376,6 +376,11 @@ async function apiRunCronjob(id) { // ── Shared Message API ── +async function apiListShares() { + const resp = await fetch('/api/shared'); + return resp.json(); +} + async function apiShareMessage(messageId, includeContext = false) { const resp = await fetch(`/api/messages/${messageId}/share`, { method: 'POST', diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index f84ae59..3e50e19 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -325,6 +325,51 @@ registerPage('dashboard', { // Neuer Cronjob Button document.getElementById('newCronjobBtn').addEventListener('click', () => navigateTo('cronjob')); + + // Shared Links + const shares = await apiListShares(); + const sharedSection = document.getElementById('sharedLinksSection'); + const sharedList = document.getElementById('sharedLinksList'); + if (shares.length > 0) { + sharedSection.hidden = false; + sharedList.innerHTML = ''; + for (const s of shares) { + const row = document.createElement('div'); + row.className = 'shared-link-row'; + const shareUrl = `${location.origin}/#shared/${s.shareToken}`; + const modeLabel = s.includeContext ? 'Thread' : 'Einzeln'; + const roleIcon = s.role === 'user' ? 'fa-user' : 'fa-robot'; + row.innerHTML = ` + + + `; + row.querySelector('[data-action="copy"]').addEventListener('click', async () => { + await navigator.clipboard.writeText(shareUrl); + showToast('Link kopiert!'); + }); + row.querySelector('[data-action="open"]').addEventListener('click', () => { + window.open(shareUrl, '_blank'); + }); + row.querySelector('[data-action="delete"]').addEventListener('click', async () => { + await apiDeleteShare(s.shareToken); + row.remove(); + if (sharedList.children.length === 0) sharedSection.hidden = true; + showToast('Link gelöscht'); + }); + sharedList.appendChild(row); + } + } }, destroy() { if (this._closeCardMenus) {