Skip to content

Commit 085c991

Browse files
meawopplclaude
andauthored
Give pasted images a timestamped filename (#1750)
Browsers name a pasted screenshot image.png, so every paste landed on the same path in the agent's working directory and clobbered the previous one. Rename those to portal_pasted_image_YYMMDD_HHMMSS.<ext> at the paste site, so everything downstream — the upload list, the agent-facing filename — follows. Only files whose stem is exactly "image" are renamed; a paste carrying a real filename keeps it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 1e9cf8c commit 085c991

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

frontend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ web-sys = { workspace = true, features = [
4141
# download never becomes a top-level navigation (markdown/portal_file_link.rs)
4242
"Url",
4343
"BlobPropertyBag",
44+
"FilePropertyBag",
4445
"HtmlAnchorElement",
4546
"DomRect",
4647
"HtmlElement",

frontend/src/pages/dashboard/session_view/input_bar.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,36 @@ pub struct InputBar {
179179
vim: Rc<RefCell<VimState>>,
180180
}
181181

182+
/// Browsers name a pasted screenshot `image.png`, so every paste lands on the
183+
/// same path in the agent's working directory and clobbers the previous one.
184+
/// Give those a timestamped name; anything pasted with a real filename keeps it.
185+
fn stamp_generic_paste_name(file: &web_sys::File) -> Option<web_sys::File> {
186+
let name = file.name();
187+
let (stem, ext) = match name.rsplit_once('.') {
188+
Some((stem, ext)) => (stem, ext),
189+
None => (name.as_str(), "png"),
190+
};
191+
if stem != "image" {
192+
return None;
193+
}
194+
195+
let d = js_sys::Date::new_0();
196+
let stamped = format!(
197+
"portal_pasted_image_{:02}{:02}{:02}_{:02}{:02}{:02}.{ext}",
198+
d.get_full_year() % 100,
199+
d.get_month() + 1,
200+
d.get_date(),
201+
d.get_hours(),
202+
d.get_minutes(),
203+
d.get_seconds(),
204+
);
205+
206+
let parts = js_sys::Array::of1(file.as_ref());
207+
let options = web_sys::FilePropertyBag::new();
208+
options.set_type(&file.type_());
209+
web_sys::File::new_with_blob_sequence_and_options(parts.as_ref(), &stamped, &options).ok()
210+
}
211+
182212
impl Component for InputBar {
183213
type Message = InputBarMsg;
184214
type Properties = InputBarProps;
@@ -554,6 +584,7 @@ impl Component for InputBar {
554584
.filter_map(|i: u32| items.get(i))
555585
.filter(|item: &web_sys::DataTransferItem| item.kind() == "file")
556586
.filter_map(|item: web_sys::DataTransferItem| item.get_as_file().ok().flatten())
587+
.map(|file| stamp_generic_paste_name(&file).unwrap_or(file))
557588
.collect();
558589
if !files.is_empty() {
559590
e.prevent_default();

0 commit comments

Comments
 (0)