diff --git a/frontend/src/views/ImagesView.vue b/frontend/src/views/ImagesView.vue
index ca371f5..046d70a 100644
--- a/frontend/src/views/ImagesView.vue
+++ b/frontend/src/views/ImagesView.vue
@@ -47,6 +47,32 @@ async function copyLink(name) {
flash(await copyText(absUrl(name)) ? '链接已复制' : '复制失败')
}
+async function copyImage(url) {
+ try {
+ const blob = await (await fetch(url)).blob()
+ const pngBlob = blob.type === 'image/png'
+ ? blob
+ : await new Promise((resolve, reject) => {
+ createImageBitmap(blob).then(async (bitmap) => {
+ const canvas = document.createElement('canvas')
+ canvas.width = bitmap.width
+ canvas.height = bitmap.height
+ const ctx = canvas.getContext('2d')
+ if (!ctx) { reject(new Error('no canvas ctx')); return }
+ ctx.drawImage(bitmap, 0, 0)
+ canvas.toBlob((out) => {
+ if (out) resolve(out)
+ else reject(new Error('png convert failed'))
+ }, 'image/png')
+ }).catch(reject)
+ })
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })])
+ flash('图片已复制')
+ } catch {
+ flash('复制失败')
+ }
+}
+
async function copyPrompt(f) {
if (!f.prompt) return
flash(await copyText(f.prompt) ? '指令已复制' : '复制失败')
@@ -168,6 +194,10 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
+
diff --git a/frontend/src/views/PlaygroundView.vue b/frontend/src/views/PlaygroundView.vue
index e85c1d5..328fc5b 100644
--- a/frontend/src/views/PlaygroundView.vue
+++ b/frontend/src/views/PlaygroundView.vue
@@ -272,6 +272,32 @@ async function copyPrompt(item) {
flash(await copyText(text) ? '指令已复制' : '复制失败')
}
+async function copyImage(url) {
+ try {
+ const blob = await (await fetch(url)).blob()
+ const pngBlob = blob.type === 'image/png'
+ ? blob
+ : await new Promise((resolve, reject) => {
+ createImageBitmap(blob).then((bitmap) => {
+ const canvas = document.createElement('canvas')
+ canvas.width = bitmap.width
+ canvas.height = bitmap.height
+ const ctx = canvas.getContext('2d')
+ if (!ctx) { reject(new Error('no canvas ctx')); return }
+ ctx.drawImage(bitmap, 0, 0)
+ canvas.toBlob((out) => {
+ if (out) resolve(out)
+ else reject(new Error('png convert failed'))
+ }, 'image/png')
+ }).catch(reject)
+ })
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })])
+ flash('图片已复制')
+ } catch {
+ flash('复制失败')
+ }
+}
+
// ---- generate (concurrent — no lock) ----
// 生图 can request 1–4 images at once: each is an independent task/charge.
const count = ref(1)
@@ -686,6 +712,10 @@ onUnmounted(() => {
+
diff --git a/frontend/src/views/UserLogsView.vue b/frontend/src/views/UserLogsView.vue
index 99074b6..f4a04df 100644
--- a/frontend/src/views/UserLogsView.vue
+++ b/frontend/src/views/UserLogsView.vue
@@ -88,6 +88,33 @@ async function copyLink(name) {
setTimeout(() => (toast.value = ''), 1500)
}
+async function copyImage(url) {
+ try {
+ const blob = await (await fetch(url)).blob()
+ const pngBlob = blob.type === 'image/png'
+ ? blob
+ : await new Promise((resolve, reject) => {
+ createImageBitmap(blob).then((bitmap) => {
+ const canvas = document.createElement('canvas')
+ canvas.width = bitmap.width
+ canvas.height = bitmap.height
+ const ctx = canvas.getContext('2d')
+ if (!ctx) { reject(new Error('no canvas ctx')); return }
+ ctx.drawImage(bitmap, 0, 0)
+ canvas.toBlob((out) => {
+ if (out) resolve(out)
+ else reject(new Error('png convert failed'))
+ }, 'image/png')
+ }).catch(reject)
+ })
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })])
+ toast.value = '图片已复制'
+ } catch {
+ toast.value = '复制失败'
+ }
+ setTimeout(() => (toast.value = ''), 1500)
+}
+
async function copyPrompt(e) {
if (!e.prompt) return
const ok = await copyText(e.prompt)
@@ -199,6 +226,10 @@ onUnmounted(() => {