Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
YunYouJun authored Feb 9, 2024
2 parents 591936f + cfb5271 commit 64aef26
Show file tree
Hide file tree
Showing 23 changed files with 1,869 additions and 1,959 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
# AI Couplets - AI 对联/春联
# AI SFC (Spring Festival Couplets) - AI 春联

## Todo
Powered by [DeepSeek 开发平台](https://platform.deepseek.com/).

- [ ] 分享春联
- 字体:[MaShanZheng | Google Fonts](https://fonts.google.com/specimen/Ma+Shan+Zheng)

## Desc

- 横批从右往左,上联在右,下联在左。
- 横批从左往右,上联在左,下联在右。

## Dev

### Config API Key

```bash
cp .env.example .env

# .env
# you can get free tokens from https://platform.deepseek.com/
OPENAI_API_KEY=your_deepseek_api_key
```

```bash
pnpm i
pnpm dev
Expand All @@ -14,3 +29,4 @@ pnpm dev
## FAQ

- [Error when opening nuxt3 web link in China's QQ application](https://github.com/nuxt/nuxt/issues/24229)
- `ofetch` in QQ browser: [unjs/ofetch#294](https://github.com/unjs/ofetch/issues/294)
29 changes: 22 additions & 7 deletions components/AiPrompt.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
<script lang="ts" setup>
import consola from 'consola'
import { useMagicKeys } from '@vueuse/core'
import { config } from '~/config'
import { apiGenerate } from '~/utils'
const app = useAppStore()
/**
* generate sfc 春联
* not use ofetch see https://github.com/unjs/ofetch/issues/294
*/
async function generate() {
if (app.loading)
return
app.loading = true
const data = await $fetch('/api/generate', {
query: {
prompt: app.prompt,
},
const data = await apiGenerate({
prompt: app.prompt,
})
consola.info(data)
app.setCoupletsData(data)
app.loading = false
}
// Ctrl + Enter / Cmd + Enter to generate
const { Ctrl_enter, Cmd_enter } = useMagicKeys()
watch(() => [Cmd_enter.value, Ctrl_enter.value], (v) => {
if (v)
generate()
})
</script>

<template>
Expand All @@ -23,13 +37,14 @@ async function generate() {
placeholder="想要什么样的春联?"
class="w-full rounded-lg p-4 shadow dark:bg-dark-800 outline-none!"
border="~ gray focus:(yellow-500)"
maxlength="200"
:maxlength="config.inputMaxLength"
/>

<button
class="font-zmx w-full btn" text="black 2xl"
:class="{ 'btn-disabled': app.loading }"
flex items-center justify-center
:disabled="app.loading"
@click="generate"
>
{{ app.loading ? '生成中...' : '生成春联' }}
Expand Down
8 changes: 8 additions & 0 deletions components/BaseFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div text="gray4" m-5 flex="~ col" justify-center>
<SimpleCopyright />
<div mt-1 text-sm>
Powered by <a href="https://chat.deepseek.com/" style="color:rgb(10, 122, 255)" target="_blank">DeepSeek</a>
</div>
</div>
</template>
20 changes: 20 additions & 0 deletions components/CurrentVersion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts" setup>
import pkg from '~/package.json'
const commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)
const now = import.meta.env.VITE_APP_BUILD_TIME
const buildDate = (new Date(Number.parseInt(now))).toLocaleDateString()
</script>

<template>
<div v-if="commitSha && buildDate" mb-4 text-sm>
<span>
当前版本 v{{ pkg.version }}({{ buildDate }}):
</span>
<span>
<a border="b-1 dashed" :href="`https://github.com/YunYouJun/ai-sfc/commit/${commitSha}`" target="_blank" alt="ai-sfc | GitHub Commit">
{{ commitSha }}
</a>
</span>
</div>
</template>
6 changes: 0 additions & 6 deletions components/Footer.vue

This file was deleted.

12 changes: 12 additions & 0 deletions components/SfcButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts" setup>
defineProps<{
icon: string
}>()
</script>

<template>
<button flex items-center justify-center class="w-full btn" text="black">
<div :class="icon" mr-1 />
<slot />
</button>
</template>
26 changes: 26 additions & 0 deletions components/SimpleCopyright.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts" setup>
import pkg from '~/package.json'
</script>

<template>
<div text="center sm" my-3>
<CurrentVersion />
<div flex="~" items-center justify-center gap="2">
<a
:href="pkg.repository.url" target="_blank"
class="inline-flex items-center justify-center"
>
<div i-ri-github-line mr-1 />
<span>Code</span>
</a>
by
<a
href="https://space.bilibili.com/1579790" target="_blank"
class="inline-flex items-center justify-center"
>
<div i-ri-bilibili-line mr-1 class="text-pink-400" />
<span>云游君</span>
</a>
</div>
</div>
</template>
90 changes: 66 additions & 24 deletions components/SpringFestivalCouplets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ defineProps<{
const app = useAppStore()
const sfcContainer = ref<HTMLElement | null>(null)
/**
* Download image
*/
async function download() {
const container = document.getElementById('spring-festival-container')
if (!container)
if (!sfcContainer.value)
return
// const url = await screenShotToBase64(container)
const url = await toPng(container, {
const url = await toPng(sfcContainer.value, {
includeQueryParams: true,
})
Expand All @@ -34,12 +35,11 @@ async function download() {
* Copy image to clipboard
*/
async function copyImg() {
const container = document.getElementById('girid-container')
if (!container)
if (!sfcContainer.value)
return
// const url = await screenShotToBase64(container)
const blob = await toBlob(container, {
const blob = await toBlob(sfcContainer.value, {
includeQueryParams: true,
})
Expand All @@ -49,7 +49,7 @@ async function copyImg() {
const { copy, copied } = useClipboard()
async function shareLink() {
const txt = `${window.location.origin}${import.meta.env.BASE_URL.replace('_nuxt/', '')}?couplets=${encodeURIComponent(JSON.stringify(app.coupletsData))}&prompt=${encodeURIComponent(app.prompt)}`
const txt = `${window.location.origin}/?couplets=${encodeURIComponent(JSON.stringify(app.coupletsData))}&prompt=${encodeURIComponent(app.prompt)}`
await copy(txt)
if (copied.value) {
Expand All @@ -60,30 +60,52 @@ async function shareLink() {
</script>

<template>
<div id="spring-festival-container" flex="col" class="font-zmx spring-festival-container">
<div class="font-zmx m-auto bg-#ff0000 p-2" w="50" text="4xl black">
{{ coupletsData['横批'] }}
<div
id="spring-festival-container" ref="sfcContainer" flex="col"
class="font-zmx spring-festival-container"
>
<div
class="font-zmx m-auto bg-#ff0000 p-2" w="50" text="4xl black"
:class="{
rtl: !app.options.inverseCouplets,
}"
>
<Transition name="fade" mode="out-in">
<span v-if="app.visible">{{ coupletsData['横批'] }}</span>
</Transition>
</div>
<div flex class="mt-4 items-center justify-between">
<div
flex
class="mt-4 items-center justify-between"
:class="{
'flex-row-reverse': !app.options.inverseCouplets,
}"
>
<div class="spring-festival-couplet">
{{ coupletsData['上联'] }}
<Transition name="fade" mode="out-in">
<span v-if="app.visible">{{ coupletsData['上联'] }}</span>
</Transition>
</div>

<div
relative class="spring-festival-fu-container transition duration-400"
:class="{
'rotate-180': app.inverseFu,
'rotate-180': app.options.inverseFu,
}"
>
<div class="spring-festival-fu" />
<!-- not inset-0 for compatibility -->
<span class="fu-char absolute bottom-0 left-0 right-0 top-0">
{{ coupletsData['总结'].slice(0, 1) }}
<Transition name="fade" mode="out-in">
<span v-if="app.visible">{{ coupletsData['总结'].slice(0, 1) }}</span>
</Transition>
</span>
</div>

<div class="spring-festival-couplet">
{{ coupletsData['下联'] }}
<Transition name="fade" mode="out-in">
<span v-if="app.visible">{{ coupletsData['下联'] }}</span>
</Transition>
</div>
</div>
</div>
Expand All @@ -94,7 +116,22 @@ async function shareLink() {
</label>
<SwitchRoot
id="airplane-mode"
v-model:checked="app.inverseFu"
v-model:checked="app.options.inverseFu"
class="relative h-[25px] w-[42px] flex cursor-default rounded-full bg-red-500 shadow-sm data-[state=checked]:bg-yellow-500 focus-within:outline-yellow focus-within:outline"
>
<SwitchThumb
class="my-auto block h-[21px] w-[21px] translate-x-0.5 rounded-full bg-white shadow-sm transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]"
/>
</SwitchRoot>
</div>

<div class="font-zmx flex items-center justify-center gap-2" text="2xl" m="4">
<label class="select-none pr-[15px] leading-none" for="airplane-mode">
翻转春联
</label>
<SwitchRoot
id="airplane-mode"
v-model:checked="app.options.inverseCouplets"
class="relative h-[25px] w-[42px] flex cursor-default rounded-full bg-red-500 shadow-sm data-[state=checked]:bg-yellow-500 focus-within:outline-yellow focus-within:outline"
>
<SwitchThumb
Expand All @@ -104,18 +141,18 @@ async function shareLink() {
</div>

<div class="font-zmx mt-4 flex" text="black" gap="2">
<button class="w-full btn" text="black" @click="download">
<SfcButton icon="i-ri-download-line" @click="download">
下载图片
</button>
<button class="w-full btn" text="black" @click="copyImg">
</SfcButton>
<SfcButton icon="i-ri-clipboard-line" @click="copyImg">
拷贝图片
</button>
</SfcButton>
</div>

<div class="font-zmx mt-4 flex" text="black" gap="2">
<button flex items-center justify-center class="w-full btn" text="black" @click="shareLink">
分享春联链接 <div class="ml-1" i-ri-link />
</button>
<div class="font-zmx mt-2 flex" text="black" gap="2">
<SfcButton icon="i-ri-link" @click="shareLink">
分享春联链接
</SfcButton>
</div>
</template>

Expand All @@ -124,6 +161,11 @@ async function shareLink() {
--ac-fu-font-size: 5rem;
}
.rtl {
direction: rtl;
unicode-bidi: bidi-override;
}
.spring-festival-fu {
// square
width: var(--ac-fu-font-size);
Expand Down
26 changes: 20 additions & 6 deletions composables/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const ns = 'ai-sfc'
export const useAppStore = defineStore('app', () => {
const loading = ref(false)
const prompt = useStorage(`${ns}:prompt`, '')
/**
* toggle for fade transition
*/
const visible = ref(true)

const coupletsData = useStorage<SprintFestivalCouplets>(`${ns}:couplets-data`, {
上联: '这里是上联',
Expand All @@ -15,10 +19,16 @@ export const useAppStore = defineStore('app', () => {
总结: '福',
})

/**
* 是否反转福字
*/
const inverseFu = useStorage(`${ns}:inverse-fu`, true)
const options = useStorage(`${ns}:options`, {
/**
* 是否翻转对联
*/
inverseCouplets: true,
/**
* 是否反转福字
*/
inverseFu: true,
})

const route = useRoute()
onMounted(() => {
Expand All @@ -29,14 +39,18 @@ export const useAppStore = defineStore('app', () => {
})

return {
visible,
loading,
prompt,
inverseFu,
options,

coupletsData,

setCoupletsData(data: SprintFestivalCouplets) {
async setCoupletsData(data: SprintFestivalCouplets) {
visible.value = false
coupletsData.value = data
await nextTick()
visible.value = true
},
}
})
Expand Down
3 changes: 3 additions & 0 deletions config/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export const suggestedCoupletsFilename = 'AI 春联.png'
export const config = {
inputMaxLength: 200,
}
Loading

0 comments on commit 64aef26

Please sign in to comment.