Skip to content

Commit a130017

Browse files
committed
feat: add generate image logic
1 parent 4de8a40 commit a130017

File tree

5 files changed

+208
-2
lines changed

5 files changed

+208
-2
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@astrojs/vercel": "^3.2.2",
2323
"@mapbox/rehype-prism": "^0.8.0",
2424
"@nanostores/solid": "^0.3.2",
25+
"@resvg/resvg-wasm": "^2.3.1",
2526
"@solid-primitives/clipboard": "^1.5.4",
2627
"@solid-primitives/keyboard": "^1.1.0",
2728
"@solid-primitives/scheduled": "^1.3.2",
@@ -54,6 +55,7 @@
5455
"remark-math": "^5.1.1",
5556
"remark-parse": "^10.0.1",
5657
"remark-rehype": "^10.1.0",
58+
"satori": "^0.10.1",
5759
"solid-emoji-picker": "^0.2.0",
5860
"solid-js": "1.6.12",
5961
"solid-transition-group": "^0.2.2",

pnpm-lock.yaml

Lines changed: 120 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ui/ShareModal.tsx

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useStore } from '@nanostores/solid'
2-
import { For } from 'solid-js'
2+
import { For, Show, createSignal } from 'solid-js'
3+
import satori from 'satori'
4+
import * as resvg from '@resvg/resvg-wasm'
35
import { useClipboardCopy, useI18n } from '@/hooks'
46
import { currentConversationId } from '@/stores/conversation'
57
import { getMessagesByConversationId } from '@/stores/messages'
@@ -11,11 +13,62 @@ export default () => {
1113
const { t } = useI18n()
1214
const $currentConversationId = useStore(currentConversationId)
1315
const messages = getMessagesByConversationId($currentConversationId()).filter(item => item.isSelected)
16+
const [imageUrl, setImageUrl] = createSignal('')
17+
const [loading, setLoading] = createSignal(false)
1418

1519
console.log($currentConversationId(), messages)
1620

1721
const [copied, copy] = useClipboardCopy(messages.map(item => `${item.role}: ${item.content}`).join('\n'))
1822

23+
const handleLoadImage = async() => {
24+
let _result = ''
25+
setLoading(true)
26+
try {
27+
const fontData = await fetch('https://cdn.jsdelivr.net/gh/yzh990918/static@master/20230609/Inter-Medium.388xm374fse8.ttf').then(res => res.arrayBuffer())
28+
_result = await satori(
29+
// TODO: context image dom
30+
{
31+
type: 'div',
32+
props: {
33+
children: 'hello, world',
34+
style: { color: 'black' },
35+
},
36+
},
37+
{
38+
width: 600,
39+
height: 400,
40+
fonts: [
41+
{
42+
name: 'Inter-Medium',
43+
data: fontData,
44+
style: 'normal',
45+
},
46+
],
47+
},
48+
)
49+
await resvg.initWasm(fetch('https://unpkg.com/@resvg/resvg-wasm/index_bg.wasm'))
50+
const res = new resvg.Resvg(_result, {
51+
fitTo: {
52+
mode: 'width',
53+
value: 600,
54+
},
55+
})
56+
57+
const png = res.render()
58+
const pngBuffer = png.asPng()
59+
const url = URL.createObjectURL(new Blob([pngBuffer], { type: 'image/png' }))
60+
if (url) {
61+
setLoading(false)
62+
setImageUrl(url)
63+
console.log(url)
64+
}
65+
} catch (error) {
66+
console.log(error)
67+
} finally {
68+
setLoading(false)
69+
}
70+
}
71+
1972
const tabs: TabItem[] = [
2073
{
2174
value: 'context',
@@ -41,7 +94,28 @@ export default () => {
4194
{
4295
value: 'image',
4396
label: t('conversations.share.tabs.image'),
44-
content: <div class="flex">image</div>,
97+
content: <div class="flex flex-col gap-2">
98+
{messages.length
99+
? (
100+
<div class="flex flex-col gap-2">
101+
<div class="inline-block text-left">
102+
<Show when={imageUrl().length}>
103+
<div class="button inline-block mt-0 cursor-pointer mb-2" onClick={() => { window.open(imageUrl()) }}>{t('conversations.share.image.open')}</div>
104+
</Show>
105+
<Show when={!imageUrl().length}>
106+
<div class="emerald-light-button inline-block mt-0 cursor-pointer mb-2" onClick={() => handleLoadImage()}>{loading() ? t('conversations.share.image.loading') : t('conversations.share.image.btn')}</div>
107+
</Show>
108+
</div>
109+
<Show when={loading()}>
110+
<div class="i-carbon:circle-solid text-slate-400 animate-ping mx-auto" />
111+
</Show>
112+
<Show when={imageUrl().length}>
113+
<img src={imageUrl()} alt="" />
114+
</Show>
115+
</div>
116+
)
117+
: <div class="text-center text-sm">{t('empty')}</div>}
118+
</div>,
45119
},
46120
]
47121

src/locale/lang/en.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export const en = {
4848
context: 'Share Context',
4949
image: 'Share Image',
5050
},
51+
image: {
52+
btn: 'Generate Image',
53+
open: 'Open in Tab',
54+
loading: 'Generating...',
55+
},
5156
},
5257
},
5358
empty: 'No data',

src/locale/lang/zh-cn.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export const zhCN = {
4848
context: '分享上下文',
4949
image: '分享图片',
5050
},
51+
image: {
52+
btn: '生成图片',
53+
open: '新窗口打开',
54+
loading: '生成中...',
55+
},
5156
},
5257
},
5358
empty: '暂无数据',

0 commit comments

Comments
 (0)