mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-08 11:56:59 +00:00
Link gallery uploads back to chat
This commit is contained in:
+21
-6
@@ -6,11 +6,11 @@ import asyncio
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Request, File, UploadFile, HTTPException
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Request, File, UploadFile, HTTPException, Form
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
from core.middleware import require_admin
|
||||
from core.database import SessionLocal, GalleryImage
|
||||
from core.database import SessionLocal, GalleryImage, Session as DbSession
|
||||
from src.auth_helpers import effective_user
|
||||
from src.constants import GENERATED_IMAGES_DIR
|
||||
from src.upload_handler import count_recent_uploads
|
||||
@@ -56,7 +56,17 @@ def setup_upload_routes(upload_handler):
|
||||
|
||||
raise HTTPException(404, "File not found")
|
||||
|
||||
def _promote_chat_image_to_gallery(meta: dict, owner: str | None) -> str | None:
|
||||
def _valid_session_id_for_owner(db, session_id: str | None, owner: str | None) -> str | None:
|
||||
if not session_id:
|
||||
return None
|
||||
sess = db.query(DbSession).filter(DbSession.id == session_id).first()
|
||||
if not sess:
|
||||
return None
|
||||
if owner and sess.owner and sess.owner != owner:
|
||||
return None
|
||||
return session_id
|
||||
|
||||
def _promote_chat_image_to_gallery(meta: dict, owner: str | None, session_id: str | None = None) -> str | None:
|
||||
"""Make chat-uploaded images visible in Gallery without changing chat storage."""
|
||||
is_image_file = getattr(upload_handler, "is_image_file", None)
|
||||
if not callable(is_image_file):
|
||||
@@ -105,6 +115,7 @@ def setup_upload_routes(upload_handler):
|
||||
prompt=meta.get("name") or "Chat upload",
|
||||
model="chat-upload",
|
||||
owner=owner,
|
||||
session_id=_valid_session_id_for_owner(db, session_id, owner),
|
||||
file_hash=file_hash,
|
||||
width=meta.get("width"),
|
||||
height=meta.get("height"),
|
||||
@@ -120,7 +131,11 @@ def setup_upload_routes(upload_handler):
|
||||
db.close()
|
||||
|
||||
@router.post("")
|
||||
async def api_upload(request: Request, files: List[UploadFile] = File(...)):
|
||||
async def api_upload(
|
||||
request: Request,
|
||||
files: List[UploadFile] = File(...),
|
||||
session_id: Optional[str] = Form(None),
|
||||
):
|
||||
"""Upload files with enhanced security and organization."""
|
||||
if not files:
|
||||
raise HTTPException(400, "No files uploaded")
|
||||
@@ -148,7 +163,7 @@ def setup_upload_routes(upload_handler):
|
||||
try:
|
||||
owner = effective_user(request)
|
||||
meta = upload_handler.save_upload(u, client_ip, owner=owner)
|
||||
gallery_id = _promote_chat_image_to_gallery(meta, owner)
|
||||
gallery_id = _promote_chat_image_to_gallery(meta, owner, session_id)
|
||||
item = {
|
||||
"id": meta["id"],
|
||||
"name": meta["name"],
|
||||
|
||||
+1
-1
@@ -686,7 +686,7 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
|
||||
|
||||
let ids = [];
|
||||
try {
|
||||
ids = await fileHandlerModule.uploadPending();
|
||||
ids = await fileHandlerModule.uploadPending({ sessionId: sessionModule.getCurrentSessionId() });
|
||||
} catch(e) {
|
||||
console.error('upload failed', e);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ export function removePending(idx) {
|
||||
/**
|
||||
* Upload all pending files to server
|
||||
*/
|
||||
export async function uploadPending() {
|
||||
export async function uploadPending(opts = {}) {
|
||||
if (pendingFiles.length === 0) return [];
|
||||
_lastUploadCancelled = false;
|
||||
|
||||
@@ -169,6 +169,7 @@ export async function uploadPending() {
|
||||
|
||||
const fd = new FormData();
|
||||
pendingFiles.forEach(f => fd.append('files', f, f.name || 'paste.png'));
|
||||
if (opts.sessionId) fd.append('session_id', opts.sessionId);
|
||||
_uploadAbortCtrl = new AbortController();
|
||||
_uploading = true;
|
||||
const timeoutId = setTimeout(() => {
|
||||
|
||||
+48
-3
@@ -8,6 +8,8 @@ import spinnerModule from './spinner.js';
|
||||
import { makeWindowDraggable } from './windowDrag.js';
|
||||
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
import { topPortalZ } from './toolWindowZOrder.js';
|
||||
import sessionModule from './sessions.js';
|
||||
import fileHandlerModule from './fileHandler.js';
|
||||
|
||||
const API_BASE = window.location.origin;
|
||||
let _open = false;
|
||||
@@ -1356,6 +1358,10 @@ function _openDetail(img) {
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
||||
Edit
|
||||
</button>
|
||||
<button class="gallery-detail-back" id="gallery-chat-photo-btn" title="${img.session_id ? 'Open source chat' : 'Start a new chat with this photo'}" aria-label="${img.session_id ? 'Open source chat' : 'Discuss photo'}" style="display:inline-flex;align-items:center;gap:4px;">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg>
|
||||
${img.session_id ? 'Open chat' : 'Discuss'}
|
||||
</button>
|
||||
<button class="gallery-detail-back gallery-detail-fav-header${img.favorite ? ' active' : ''}" id="gallery-detail-fav-header" title="${img.favorite ? 'Unfavorite' : 'Favorite'}" aria-label="Favorite" aria-pressed="${img.favorite ? 'true' : 'false'}" style="display:inline-flex;align-items:center;justify-content:center;padding:4px 8px;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="${img.favorite ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
|
||||
</button>
|
||||
@@ -1462,6 +1468,45 @@ function _openDetail(img) {
|
||||
detail.style.display = 'none';
|
||||
});
|
||||
|
||||
document.getElementById('gallery-chat-photo-btn')?.addEventListener('click', async () => {
|
||||
if (img.session_id) {
|
||||
closeGallery();
|
||||
try {
|
||||
await sessionModule.selectSession(img.session_id);
|
||||
} catch (e) {
|
||||
console.error('Open source chat failed:', e);
|
||||
uiModule.showError && uiModule.showError('Could not open source chat');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const dcRes = await fetch('/api/default-chat', { credentials: 'same-origin' });
|
||||
const dc = await dcRes.json();
|
||||
if (!dc?.endpoint_url || !dc?.model) {
|
||||
uiModule.showError && uiModule.showError('Pick a chat model first');
|
||||
return;
|
||||
}
|
||||
sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id);
|
||||
closeGallery();
|
||||
const res = await fetch(img.url, { credentials: 'same-origin' });
|
||||
if (!res.ok) throw new Error('image fetch ' + res.status);
|
||||
const blob = await res.blob();
|
||||
const name = img.filename || 'gallery-photo.jpg';
|
||||
const file = new File([blob], name, { type: blob.type || 'image/jpeg' });
|
||||
fileHandlerModule.addFiles([file]);
|
||||
const input = document.getElementById('message');
|
||||
if (input) {
|
||||
input.value = 'Let’s discuss this photo.';
|
||||
input.dispatchEvent(new Event('input'));
|
||||
input.focus();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Discuss photo failed:', e);
|
||||
uiModule.showError && uiModule.showError('Could not start photo chat');
|
||||
}
|
||||
});
|
||||
|
||||
// Clickable tag chips — both AI Tags and User Tags. Clicking a chip
|
||||
// closes the detail, sets the tag filter on the main grid, and
|
||||
// re-fetches so the user sees other photos with that tag.
|
||||
@@ -1967,9 +2012,9 @@ export function openGallery() {
|
||||
<option value="">All sources</option>
|
||||
</select>
|
||||
<select class="gallery-sort" id="gallery-sort">
|
||||
<option value="shuffle"${_sort === 'shuffle' ? ' selected' : ''}>Random</option>
|
||||
<option value="recent"${_sort === 'recent' ? ' selected' : ''}>Recent</option>
|
||||
<option value="oldest"${_sort === 'oldest' ? ' selected' : ''}>Oldest</option>
|
||||
<option value="shuffle"${_sort === 'shuffle' ? ' selected' : ''}>↭ Random order</option>
|
||||
<option value="recent"${_sort === 'recent' ? ' selected' : ''}>↓ Newest first</option>
|
||||
<option value="oldest"${_sort === 'oldest' ? ' selected' : ''}>↑ Oldest first</option>
|
||||
</select>
|
||||
<button class="gallery-select-btn gallery-toolbar-action" id="gallery-select-btn" title="Select for bulk actions"><span style="position:relative;top:1px;">Select</span></button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user