Improve background panel: collapse behavior, image upload, and single-file support #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The background panel has several issues: it takes up space when empty, image uploads do not work, and files must be inside a subfolder to be recognized as context. These friction points make the background system difficult to use.
Goals
Implementation Spec
Objective
Fix five usability issues in the background panel: collapse behavior, image upload rejection, and root-level file support.
Requirements
content/background/without requiring a subfolder.md/.txtfiles in the AI generation contextFiles to Modify
crates/hero_slides_lib/src/deck.rscrates/hero_slides_lib/src/discovery.rsfind_background_root_files(), updateread_background_content(), updatehas_backgroundcrates/hero_slides_lib/src/lib.rscrates/hero_slides_server/src/rpc.rsroot_filesin listFolders, handle empty folder paramcrates/hero_slides_ui/static/js/dashboard.jscrates/hero_slides_ui/templates/index.htmlImplementation Plan
Step 1: Fix image upload backend rejection
Files:
crates/hero_slides_lib/src/deck.rsdeck_save_background_file()(line 193) frommd|pdf|txtto also includepng|jpg|jpeg|webp|gifStep 2: Add root-level file discovery and include in AI context
Files:
crates/hero_slides_lib/src/discovery.rs,crates/hero_slides_lib/src/lib.rsfind_background_root_files()function to scancontent/background/for files (not dirs)read_background_content()to also read root-level.md/.txtfileshas_backgroundindeck_info_from_path()to consider root fileslib.rsStep 3: Update deck generation to include root files in context
Files:
crates/hero_slides_lib/src/deck.rsdeck_generate()(L564) andslide_generate()(L714) to check for root files when buildingbackground_contextStep 4: Expose root files through RPC layer
Files:
crates/hero_slides_lib/src/deck.rs,crates/hero_slides_server/src/rpc.rsdeck_list_background_root_files()wrapperhandle_bg_list_foldersto returnroot_filesin responsedeck_save_background_fileto handle empty folder nameStep 5: Update frontend -- root files, collapse behavior, BG label
Files:
crates/hero_slides_ui/static/js/dashboard.js,crates/hero_slides_ui/templates/index.htmlbgPanelOpen = false, auto-open when content exists inloadBgPanel()root_filesfrombg.listFoldersresponseAcceptance Criteria
.pngor.jpgvia the UI succeeds and file appears in the list.mdfile placed directly incontent/background/appears in the UI as a root filecargo buildandcargo testpassNotes
.md/.txtare read). Images are for visual reference in the file browser only.bg.moveFilehandler also needs to support empty folder for moving files to/from root.Updated Implementation Spec — scope expanded
Including images as visual input to AI generation (both content and image generation).
Updated Requirements
In addition to the original goals (collapse behavior, image upload fix, root-file support):
png,jpg,jpeg,webp,gif) must be passed as multimodal input to the AI slide content generationUpdated Files to Modify
crates/hero_slides_lib/src/deck.rsdeck_generate/slide_generatecrates/hero_slides_lib/src/discovery.rsfind_background_root_files(); updateread_background_content(); updatehas_background; newcollect_background_images()helpercrates/hero_slides_lib/src/generator.rsimagesparam ingenerate_slide_content,instruct_slide_content,generate_slide; multimodal image generation viachat_with_raw_modelcrates/hero_slides_lib/src/lib.rscrates/hero_slides_server/src/rpc.rsroot_files; handle empty folder paramcrates/hero_slides_ui/static/js/dashboard.jscrates/hero_slides_ui/templates/index.htmlUpdated Implementation Plan
Step 1: Fix image upload backend rejection
Files:
crates/hero_slides_lib/src/deck.rsdeck_save_background_file()to include imagesStep 2: Root-level file discovery and text context
Files:
crates/hero_slides_lib/src/discovery.rs,crates/hero_slides_lib/src/lib.rsfind_background_root_files()read_background_content()to include root.md/.txtfileshas_backgroundto consider root filesStep 3: Pass images to AI content generation
Files:
crates/hero_slides_lib/src/generator.rs,crates/hero_slides_lib/src/discovery.rscollect_background_images(deck_path) -> Vec<(mime, base64)>helpergenerate_slide_content()andinstruct_slide_content()to accept images parameterMessage::user_with_images()when images are present (API already available inherolib_ai)Gpt5_4,Gemini3ProPreview) already support visionStep 4: Pass images to slide image generation
Files:
crates/hero_slides_lib/src/generator.rsgenerate_slide()to acceptbackground_imagesgenerate_image_to_fileand usechat_with_raw_modelwithgoogle/gemini-3.1-flash-image-previewand multimodal messages, mirroring the existing PDF extraction pattern indeck.rs:308-327Step 5: Update deck generation entry points
Files:
crates/hero_slides_lib/src/deck.rsdeck_generate()(L564) andslide_generate()(L714) to collect and pass imagesStep 6: Expose root files through RPC
Files:
crates/hero_slides_lib/src/deck.rs,crates/hero_slides_server/src/rpc.rsdeck_list_background_root_files()wrapperhandle_bg_list_foldersto returnroot_filesdeck_save_background_fileStep 7: Frontend — root files, collapse behavior, BG label
Files:
crates/hero_slides_ui/static/js/dashboard.js,crates/hero_slides_ui/templates/index.htmlbgPanelOpen = falsedefault; auto-open when content existsAcceptance Criteria
.png/.jpgvia the UI succeeds.md/.txtincontent/background/appears in UI and AI text contextcargo buildandcargo testpassNotes
herolib_ai::Message::user_with_images(text, &[(mime, base64)])already exists and is used byextract_pdf_with_aichat_with_raw_modelfor multimodal image generation follows the same pattern as PDF extractionImplementation Spec — Issue #7
Objective
Fix five usability issues in the background panel: image upload rejection, root-level file invisibility, AI context gaps, and collapse behavior.
Scope
This spec covers all goals from the issue including the expanded multimodal scope from comment #2, with one exception: passing background images as visual input to slide image generation is deferred — the current
herolib_aiAPI does not expose a path to send input images alongside image-output modality requests without triggering anEmptyResponseerror insend_request. All other goals are implemented.Files to Modify
crates/hero_slides_lib/src/deck.rscrates/hero_slides_lib/src/discovery.rsfind_background_root_files(); newread_background_root_content(); newcollect_background_images(); updatehas_background(L271); updatedeck_info_from_path()crates/hero_slides_lib/src/generator.rsbackground_imagesparam togenerate_slide_contentandinstruct_slide_content; useMessage::user_with_imageswhen images presentcrates/hero_slides_lib/src/lib.rscrates/hero_slides_server/src/rpc.rsroot_filesinhandle_bg_list_folders; allow empty folder string in upload/list/delete/read handlerscrates/hero_slides_ui/static/js/dashboard.jsImplementation Plan
Step 1: Fix image upload backend rejection
File:
crates/hero_slides_lib/src/deck.rsext != "md" && ext != "pdf" && ext != "txt"to also allowpng,jpg,jpeg,webp,gifdeck_delete_background_file,deck_move_background_file): whenfolder_nameis empty, construct path directly undercontent/background/(skip the folder segment injoin())deck_save_background_file): whenfolder_nameis empty, save directly tocontent/background/; PDF extraction path stays subfolder-only (only applydeck_extract_pdf_to_mdwhenfolder_nameis non-empty)deck_read_background_file): whenfolder_nameis empty, read fromcontent/background/rootDependencies: none
Step 2: Root-level file discovery and AI text context
File:
crates/hero_slides_lib/src/discovery.rsAdd
find_background_root_files(deck_path: &Path) -> Vec<BackgroundFile>:content/background/for immediate children that are files (not dirs)md,pdf,txt,png,jpg,jpeg,webp,gifAdd
read_background_root_content(deck_path: &Path) -> String:.mdand.txtfiles fromcontent/background/root (skip PDFs, images)## (root)\n\nAdd
collect_background_images(deck_path: &Path) -> Vec<(String, String)>:png,jpg,jpeg,webp,gif) from:content/background/rootcontent/background/image/png,image/jpeg,image/webp,image/gif)Vec<(mime_type, base64_data)>Update
deck_info_from_path()line 271:has_backgroundshould be true iffind_background_folders()is non-empty ORfind_background_root_files()is non-emptyDependencies: none (independent of Step 1)
Step 3: Include root files and images in AI generation context
File:
crates/hero_slides_lib/src/deck.rsIn
deck_generate()(lines 584–590):bg_foldersandbackground_context, also callread_background_root_content()and prepend it tobackground_contextif non-emptycollect_background_images()and store asbackground_images: Vec<(String, String)>&background_imagestogenerate_slide()In
slide_generate()(lines 735–740): same additionsDependencies: Step 2
Step 4: Multimodal images in text content generation
File:
crates/hero_slides_lib/src/generator.rsUpdate
generate_slide_content()(line 138):background_images: &[(String, String)]background_imagesis non-empty, build message asMessage::user_with_images(&user_msg, background_images)instead ofMessage::user(&user_msg)Update
instruct_slide_content()(line 189):background_images: &[(String, String)]Update callers in
rpc.rsthat call these functions — pass&[]initially (images from deck context not wired through RPC yet; can be extended later)Update callers in
deck.rsto pass the collectedbackground_imagesNote:
generate_slide()(image generation) does NOT get images passed — deferred as noted in Scope.Dependencies: Step 3
Step 5: RPC layer — root files and empty folder support
File:
crates/hero_slides_server/src/rpc.rsIn
handle_bg_list_folders()(line 672):hero_slides_lib::find_background_root_files(&dir)(re-export needed)root_files: [{ name, extension, size_bytes }]array to the JSON response alongsidefoldersIn
handle_bg_list_files()(line 698):folderparam optional (default empty string)deck_list_background_files(&dir, &folder)— Step 1 makes this work with empty folderIn
handle_bg_upload_file()(line 825):folderparam optional (default empty string)In
handle_bg_delete_file()(line 768):folderparam optional (default empty string)In
handle_bg_read_file()(line 862):folderparam optional (default empty string)In
handle_bg_move_file()(line 807):src_folderanddst_folderparams optional (default empty string)Dependencies: Step 1, Step 2
Step 6: Frontend — collapse behavior, root files, root upload
File:
crates/hero_slides_ui/static/js/dashboard.jsCollapse by default: change line 17
let bgPanelOpen = truetolet bgPanelOpen = falseAuto-open when content exists: in
loadSlidesForDeck()(around line 401), resetbgPanelOpen = falsebefore callingloadBgPanel()so switching decks re-evaluates. InloadBgPanel()after fetching data, iffolders.length > 0 || rootFiles.length > 0, setbgPanelOpen = trueand update DOM visibility.Root files state: add
let bgRootFiles = []near line 18Store root files: in
loadBgPanel(), extractresult.root_files || []and store inbgRootFilesRender root files: update
renderBgTree()to renderbgRootFilesat the top, before folders. Use a virtual root section with a simple flat list (no folder row, no expand). File key format:file:/:filename(root folder is/).Root upload:
bgTriggerUpload(): remove the "Create a folder first" guard; whenbgFolders.length === 0, setbgUploadTargetFolder = ''(root) and trigger file inputbgHandleFileInput(): allowfolder = ''as valid root targetbgUploadFiles(files, folder): passfolderas-is tobg.uploadFile(empty string is valid)Verify toggle: the
bg-panel-togglediv already has only the folder icon — no "BG" text — no HTML change neededDependencies: Step 5
Acceptance Criteria
.pngor.jpgvia the UI succeeds and the file appears in the file list.mdfile placed directly incontent/background/appears in the UI root section.md/.txtfiles are included in AI generation text contextcargo buildandcargo testpassNotes
Message::user_with_images(text, &[(mime, base64)])is already used inextract_pdf_with_ai— same pattern appliesroot_filesin the RPC response)Spec amendment — image generation step added
The "passing background images to slide image generation" step is included in scope. The approach: when background images are present, build a
ChatCompletionRequestmanually withMessage::user_with_images+with_modalities(["image","text"])+ image config, callclient.chat_raw(Provider::OpenRouter, request), and extract the image fromchoices[0].message.images. Falls back to the text-only path if no images are present or on error.Test Results
cargo buildandcargo testboth pass cleanly. One pre-existing warning about unused imports inhero_slides_ui/src/routes.rs(unrelated to this change).Implementation Summary
All goals from the issue (including the expanded multimodal scope) are implemented.
Changes
crates/hero_slides_lib/src/deck.rsdeck_save_background_filenow includespng,jpg,jpeg,webp,gifsave,delete,move,read,list) accept an emptyfolder_nameto operate at thecontent/background/rootcrates/hero_slides_lib/src/discovery.rsfind_background_files: handles empty folder name (lists root files)find_background_root_files(): lists files directly incontent/background/read_background_root_content(): reads.md/.txtfiles from the background root for text contextcollect_background_images(): collects all image files from root and subfolders as(mime, base64)tupleshas_backgroundindeck_info_from_pathnow returnstruewhen root files existcrates/hero_slides_lib/src/generator.rsgenerate_slide_contentandinstruct_slide_content: newbackground_imagesparameter; usesMessage::user_with_imageswhen images are presentgenerate_slide: newbackground_imagesparameter; when images are present, usesChatCompletionRequest+chat_rawwithwith_modalities(["image","text"])for multimodal image generation; falls back togenerate_image_to_filewhen emptycrates/hero_slides_lib/src/lib.rscrates/hero_slides_rhai/src/deck_module.rsandcrates/hero_slides_server/src/agent.rsgenerate_slide_contentandinstruct_slide_contentto pass collected background imagescrates/hero_slides_server/src/rpc.rsbg.listFoldersresponse now includesroot_filesarraylistFiles,uploadFile,deleteFile,readFile,moveFile) now accept empty folder string for root operationscrates/hero_slides_ui/static/js/dashboard.jsbgPanelOpen = false); auto-opens when deck has folders or root filesDeferred
None — all original and expanded goals are implemented.
Pull request opened: #20
This PR implements all the changes discussed in this issue.