├── .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 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 |
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 | ![Demo](Demo.gif) 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 | --------------------------------------------------------------------------------