├── .env.example
├── src
├── vite-env.d.ts
├── main.tsx
├── components
│ ├── FuCharacter.tsx
│ ├── Lantern.tsx
│ └── Lantern.css
├── index.css
├── App.css
├── assets
│ └── react.svg
└── App.tsx
├── Demo.gif
├── postcss.config.js
├── tsconfig.json
├── tailwind.config.js
├── .gitignore
├── index.html
├── vite.config.ts
├── tsconfig.node.json
├── tsconfig.app.json
├── eslint.config.js
├── package.json
├── README.md
├── LICENSE
└── public
└── vite.svg
/.env.example:
--------------------------------------------------------------------------------
1 | VITE_CLAUDE_API_KEY=your_claude_api_key_here
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/Demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GitHubDaily/AI-Couplet/HEAD/Demo.gif
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | "./index.html",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import './index.css'
4 | import App from './App.tsx'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/src/components/FuCharacter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FuCharacter: React.FC = () => {
4 | return (
5 |
6 | 福
7 |
8 | )
9 | }
10 |
11 | export default FuCharacter
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # Environment Variables
27 | .env
28 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | proxy: {
9 | '/api': {
10 | target: 'https://api.anthropic.com',
11 | changeOrigin: true,
12 | rewrite: (path) => path.replace(/^\/api/, '')
13 | }
14 | }
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/src/components/Lantern.tsx:
--------------------------------------------------------------------------------
1 | import './Lantern.css'
2 |
3 | const Lantern = () => {
4 | return (
5 |
14 | )
15 | }
16 |
17 | export default Lantern
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["vite.config.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true
24 | },
25 | "include": ["src"]
26 | }
27 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-couplet",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "axios": "^1.6.7",
14 | "react": "^18.3.1",
15 | "react-dom": "^18.3.1"
16 | },
17 | "devDependencies": {
18 | "@eslint/js": "^9.17.0",
19 | "@types/react": "^18.3.18",
20 | "@types/react-dom": "^18.3.5",
21 | "@vitejs/plugin-react": "^4.3.4",
22 | "autoprefixer": "^10.4.20",
23 | "eslint": "^9.17.0",
24 | "eslint-plugin-react-hooks": "^5.0.0",
25 | "eslint-plugin-react-refresh": "^0.4.16",
26 | "globals": "^15.14.0",
27 | "postcss": "^8.5.1",
28 | "tailwindcss": "^3.4.17",
29 | "typescript": "~5.6.2",
30 | "typescript-eslint": "^8.18.2",
31 | "vite": "^6.0.5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## AI 春联生成器
2 |
3 | > 🎯 本项目完全由字节跳动推出的全新 AI 编程工具:[Trae](https://www.trae.ai/) 完成开发。
4 | >
5 | > 📝 关于如何从 0 到 1 开发这个项目的详细介绍 [点击这里](https://mp.weixin.qq.com/s/6_NFU1X3w1QpdJHsfr7m3A) 查看。
6 |
7 | 利用 Claude API,实现只需输入关键词,即可快速生成上下联和横批,界面简洁且喜庆。
8 |
9 | 
10 |
11 | ## 功能特性
12 |
13 | - 🤖 AI 驱动:利用 Claude API 模型生成对联
14 | - 📝 实时生成:输入关键词,快速生成上下联和横批
15 | - 🎨 优雅界面:简洁且喜庆的用户界面设计
16 | - 📱 响应式设计:完美支持移动端 和桌面端
17 | - ⚡️ 高性能:基于 Vite 构建,加载迅速,响应快速
18 |
19 | ## 技术栈
20 |
21 | - React 18
22 | - TypeScript
23 | - Vite
24 | - ESLint
25 |
26 | ## 环境配置
27 |
28 | 1. 获取 Claude API Key:
29 | - 访问 [Claude API 控制台](https://console.anthropic.com/)
30 | - 注册并登录账号
31 | - 在控制台中创建新的 API Key
32 |
33 | 2. 配置环境变量:
34 | - 将项目根目录下的 `.env.example` 文件复制并重命名为 `.env`
35 | - 在 `.env` 文件中将 `your_claude_api_key_here` 替换为你的实际 API Key
36 | ```plaintext
37 | VITE_CLAUDE_API_KEY=你的API密钥
38 | ```
39 |
40 | ## 项目启动
41 |
42 | 1. 安装依赖:
43 | ```bash
44 | npm install
45 | ```
46 |
47 | 2. 启动开发服务器:
48 | ```bash
49 | npm run dev
50 | ```
51 |
52 | 3. 构建生产版本:
53 | ```bash
54 | npm run build
55 | ```
56 |
57 | ## 开源协议
58 |
59 | MIT License
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 GitHubDaily
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
70 | @tailwind base;
71 | @tailwind components;
72 | @tailwind utilities;
73 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .writing-vertical {
9 | writing-mode: vertical-rl;
10 | text-orientation: upright;
11 | white-space: nowrap;
12 | height: auto;
13 | min-height: 400px;
14 | padding: 2rem 1rem;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | letter-spacing: 0.65rem;
19 | line-height: 2;
20 | }
21 |
22 | .logo {
23 | height: 6em;
24 | padding: 1.5em;
25 | will-change: filter;
26 | transition: filter 300ms;
27 | }
28 | .logo:hover {
29 | filter: drop-shadow(0 0 2em #646cffaa);
30 | }
31 | .logo.react:hover {
32 | filter: drop-shadow(0 0 2em #61dafbaa);
33 | }
34 |
35 | @keyframes logo-spin {
36 | from {
37 | transform: rotate(0deg);
38 | }
39 | to {
40 | transform: rotate(360deg);
41 | }
42 | }
43 |
44 | @media (prefers-reduced-motion: no-preference) {
45 | a:nth-of-type(2) .logo {
46 | animation: logo-spin infinite 20s linear;
47 | }
48 | }
49 |
50 | .card {
51 | padding: 2em;
52 | }
53 |
54 | .read-the-docs {
55 | color: #888;
56 | }
57 | .container {
58 | min-height: 100vh;
59 | display: flex;
60 | flex-direction: column;
61 | align-items: center;
62 | justify-content: center;
63 | background-color: #ffffff; /* 改为白色背景 */
64 | padding: 20px;
65 | }
66 |
67 | .couplet-container {
68 | display: flex;
69 | justify-content: space-between;
70 | width: 100%;
71 | max-width: 800px; /* 控制最大宽度 */
72 | margin-top: 30px;
73 | gap: 60px; /* 增加对联之间的间距 */
74 | }
75 |
76 | .couplet {
77 | background-color: #e74c3c;
78 | color: #fff;
79 | padding: 20px;
80 | font-size: 32px; /* 增大字体大小 */
81 | writing-mode: vertical-lr;
82 | min-height: 400px; /* 确保对联有足够高度 */
83 | display: flex;
84 | align-items: center;
85 | justify-content: center;
86 | border-radius: 8px;
87 | }
88 |
89 | .horizontal {
90 | background-color: #e74c3c;
91 | color: #fff;
92 | padding: 15px 30px;
93 | font-size: 28px; /* 横批字体稍小于对联 */
94 | margin-bottom: 20px;
95 | border-radius: 8px;
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/Lantern.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --lineColor: #ecaa2f;
3 | --bg: #f00;
4 | }
5 |
6 | .lantern {
7 | width: 89px;
8 | height: 67px;
9 | position: relative;
10 | animation: rotate 3s infinite ease-in-out;
11 | }
12 |
13 | .lantern-center {
14 | position: relative;
15 | width: 100%;
16 | height: 100%;
17 | background: var(--bg);
18 | border-radius: 120px;
19 | box-shadow: 0 0 80px -10px var(--bg);
20 | animation: rotate 3s infinite ease-in-out;
21 | transform-origin: top center;
22 | }
23 |
24 | .lantern-center::before {
25 | content: "";
26 | position: absolute;
27 | top: -3px;
28 | left: calc(50% - 18px);
29 | width: 36px;
30 | height: 5px;
31 | background: var(--lineColor);
32 | border-radius: 5px 5px 0 0;
33 | z-index: 2;
34 | }
35 |
36 | .lantern-center::after {
37 | content: "";
38 | width: 36px;
39 | height: 5px;
40 | background: var(--lineColor);
41 | border-radius: 0 0 3px 3px;
42 | position: absolute;
43 | bottom: -3px;
44 | left: calc(50% - 18px);
45 | z-index: 2;
46 | }
47 |
48 | .lantern-line {
49 | width: 100%;
50 | height: 100%;
51 | display: flex;
52 | align-items: center;
53 | justify-content: center;
54 | position: relative;
55 | }
56 |
57 | .lantern-line::before {
58 | content: "";
59 | position: absolute;
60 | top: 0;
61 | left: calc(50% - 33px);
62 | width: 66px;
63 | height: 66px;
64 | border: 2px solid var(--lineColor);
65 | border-radius: 50%;
66 | }
67 |
68 | .lantern-line::after {
69 | content: "";
70 | position: absolute;
71 | top: 0;
72 | left: calc(50% - 15px);
73 | width: 30px;
74 | height: 66px;
75 | border: 2px solid var(--lineColor);
76 | border-radius: 50%;
77 | }
78 |
79 | .lantern-head {
80 | position: absolute;
81 | left: calc(50% - 1px);
82 | top: -27px;
83 | width: 2px;
84 | height: 27px;
85 | background-color: var(--lineColor);
86 | }
87 |
88 | .lantern-foot {
89 | position: absolute;
90 | left: calc(50% - 1px);
91 | bottom: -22px;
92 | width: 2px;
93 | height: 22px;
94 | background-color: var(--lineColor);
95 | animation: rotate 3s infinite ease-in-out;
96 | }
97 |
98 | .lantern-foot::after {
99 | content: "";
100 | position: absolute;
101 | bottom: -33px;
102 | left: calc(50% - 3px);
103 | width: 6px;
104 | height: 35px;
105 | background: linear-gradient(
106 | #f00,
107 | #e36d00 3px,
108 | #fbd342 5px,
109 | #e36d00 8px,
110 | #e36d00 12px,
111 | #f00 16px,
112 | rgba(255, 0, 0, 0.8) 26px,
113 | rgba(255, 0, 0, 0.6)
114 | );
115 | border-radius: 5px 5px 0 0;
116 | }
117 |
118 | @keyframes rotate {
119 | 0%, 100% {
120 | transform: rotate(-10deg);
121 | }
122 | 50% {
123 | transform: rotate(10deg);
124 | }
125 | }
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import axios from 'axios'
3 | import './App.css'
4 | import Lantern from './components/Lantern'
5 | import FuCharacter from './components/FuCharacter'
6 |
7 | function App() {
8 | const [input, setInput] = useState('')
9 | const [loading, setLoading] = useState(false)
10 | const [error, setError] = useState('')
11 | const [couplet, setCouplet] = useState({
12 | horizontal: '福满人间',
13 | upper: '福气带喜满门开',
14 | lower: '春风送暖入家来'
15 | })
16 |
17 | const generateCouplet = async () => {
18 | if (!input.trim()) {
19 | setError('温馨提示:请输入您想要的春联主题或关键词,比如"福"、"春"、"家"等')
20 | return
21 | }
22 |
23 | setLoading(true)
24 | setError('')
25 |
26 | try {
27 | const response = await axios.post('/api/v1/messages', {
28 | model: 'claude-3-sonnet-20240229',
29 | max_tokens: 1024,
30 | messages: [{
31 | role: 'user',
32 | content: `按如下格式生成一副对联(只返回对联内容,不要其他任何解释):
33 |
34 | 横批:鸿运昌隆
35 | 上联:春风送暖入门来
36 | 下联:福气带喜满庭香
37 |
38 | 请用这个固定格式,生成一副与"${input}"相关的新春对联。注意:不要加句号或其他标点符号。`
39 | }]
40 | }, {
41 | headers: {
42 | 'Content-Type': 'application/json',
43 | 'x-api-key': import.meta.env.VITE_CLAUDE_API_KEY,
44 | 'anthropic-version': '2023-06-01',
45 | 'anthropic-dangerous-direct-browser-access': 'true'
46 | },
47 | timeout: 15000 // 增加超时时间到15秒
48 | })
49 |
50 | const content = response.data.content[0].text
51 | console.log('Claude返回内容:', content)
52 |
53 | // 使用正则表达式提取春联内容
54 | const horizontalMatch = content.match(/横批:([^\n]+)/)
55 | const upperMatch = content.match(/上联:([^\n]+)/)
56 | const lowerMatch = content.match(/下联:([^\n]+)/)
57 |
58 | if (!horizontalMatch || !upperMatch || !lowerMatch) {
59 | throw new Error('春联内容格式不正确')
60 | }
61 |
62 | const newCouplet = {
63 | horizontal: horizontalMatch[1].trim(),
64 | upper: upperMatch[1].trim(),
65 | lower: lowerMatch[1].trim()
66 | }
67 |
68 | console.log('提取的春联内容:', newCouplet)
69 |
70 | setCouplet(newCouplet)
71 | } catch (err) {
72 | console.error('Error generating couplet:', err)
73 | if (axios.isAxiosError(err)) {
74 | // 详细记录错误信息
75 | console.error('API Error Details:', {
76 | status: err.response?.status,
77 | statusText: err.response?.statusText,
78 | data: err.response?.data,
79 | headers: err.response?.headers,
80 | config: err.config
81 | })
82 |
83 | if (err.code === 'ECONNABORTED') {
84 | setError('请求超时,请检查网络连接后重试')
85 | } else if (err.response) {
86 | switch (err.response.status) {
87 | case 401:
88 | setError('API密钥无效,请联系管理员')
89 | break
90 | case 429:
91 | setError('请求过于频繁,请稍后再试')
92 | break
93 | case 500:
94 | setError('AI服务暂时不可用,请稍后重试')
95 | break
96 | default:
97 | const errorMessage = err.response.data?.error?.message ||
98 | err.response.data?.message ||
99 | err.response.statusText ||
100 | '未知错误'
101 | setError(`生成春联失败:${errorMessage}`)
102 | console.error('Detailed error response:', err.response.data)
103 | }
104 | } else if (err.request) {
105 | console.error('No response received:', err.request)
106 | setError('网络连接失败,请检查网络设置后重试')
107 | } else {
108 | console.error('Error details:', err.message)
109 | setError('生成春联失败,请稍后重试')
110 | }
111 | } else {
112 | console.error('Non-Axios error:', err)
113 | setError('生成春联时发生未知错误,请稍后重试')
114 | }
115 | } finally {
116 | setLoading(false)
117 | }
118 | }
119 |
120 | return (
121 |
122 |
AI 春联
123 |
124 |
125 |
126 |
setInput(e.target.value)}
130 | placeholder="想要什么样的春联?"
131 | className="w-full p-5 border-2 border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent text-lg"
132 | />
133 |
140 | {error &&
{error}
}
141 |
142 |
143 |
144 |
145 |
146 |
147 | {couplet.horizontal}
148 |
149 |
150 |
151 |
152 |
153 |
154 | {couplet.upper}
155 |
156 |
157 |
158 | {couplet.lower}
159 |
160 |
161 |
162 |
163 |
164 | )
165 | }
166 |
167 | export default App
168 |
--------------------------------------------------------------------------------