├── .eslintrc.json ├── .github └── workflows │ └── docker.yml ├── .gitignore ├── .prettierignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── apple-icon.png ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── server.ts ├── components.json ├── components ├── coin.tsx ├── divination.tsx ├── footer.tsx ├── header.tsx ├── hexagram.tsx ├── mode-toggle.tsx ├── question.tsx ├── result-ai.tsx ├── result.tsx ├── svg.tsx ├── ui │ ├── button.tsx │ ├── dropdown-menu.tsx │ └── textarea.tsx └── umami.tsx ├── docs └── screenshots.jpg ├── lib ├── animate.ts ├── constant.ts ├── data │ ├── gua-index.json │ ├── gua-list.json │ └── today.json └── utils.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public └── img │ ├── head.webp │ ├── tail.webp │ └── yin-yang.webp ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker Images 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v3 16 | with: 17 | platforms: amd64,arm64 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v3 20 | with: 21 | platforms: linux/amd64,linux/arm64 22 | - name: Login to Docker Hub 23 | uses: docker/login-action@v3 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_TOKEN }} 27 | - name: Docker meta 28 | id: meta 29 | uses: docker/metadata-action@v5 30 | with: 31 | images: sunls24/divination 32 | tags: | 33 | type=raw,value=latest 34 | type=ref,event=tag 35 | - name: Build and push 36 | uses: docker/build-push-action@v6 37 | with: 38 | context: . 39 | push: true 40 | platforms: linux/amd64,linux/arm64 41 | tags: ${{ steps.meta.outputs.tags }} 42 | labels: ${{ steps.meta.outputs.labels }} 43 | cache-from: type=gha 44 | cache-to: type=gha,mode=max 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .idea 38 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS base 2 | 3 | # Install dependencies only when needed 4 | FROM base AS deps 5 | RUN apk add --no-cache libc6-compat 6 | WORKDIR /app 7 | 8 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 9 | RUN \ 10 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 11 | elif [ -f package-lock.json ]; then npm ci; \ 12 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ 13 | else echo "Lockfile not found." && exit 1; \ 14 | fi 15 | 16 | # Rebuild the source code only when needed 17 | FROM base AS builder 18 | WORKDIR /app 19 | COPY --from=deps /app/node_modules ./node_modules 20 | COPY . . 21 | 22 | ENV NEXT_TELEMETRY_DISABLED 1 23 | RUN \ 24 | if [ -f yarn.lock ]; then yarn run build; \ 25 | elif [ -f package-lock.json ]; then npm run build; \ 26 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ 27 | else echo "Lockfile not found." && exit 1; \ 28 | fi 29 | 30 | # Production image, copy all the files and run next 31 | FROM base AS runner 32 | WORKDIR /app 33 | 34 | ENV NODE_ENV production 35 | ENV NEXT_TELEMETRY_DISABLED 1 36 | 37 | RUN addgroup --system --gid 1001 nodejs 38 | RUN adduser --system --uid 1001 nextjs 39 | 40 | COPY --from=builder /app/public ./public 41 | 42 | RUN mkdir .next 43 | RUN chown nextjs:nodejs .next 44 | 45 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 46 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 47 | 48 | USER nextjs 49 | 50 | EXPOSE 3000 51 | ENV PORT 3000 52 | CMD HOSTNAME="127.0.0.1" node server.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 sunls24 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧙 概述 2 | 3 | **AI 算卦:** 通过进行六次硬币的随机卜筮,生成卦象,并使用 AI 对卦象进行分析。 4 | 5 | ## ⚙️ 设置 6 | 7 | #### 环境变量 8 | 9 | - `OPENAI_API_KEY`:不必多说,懂的都懂 10 | - `OPENAI_BASE_URL`:自定义 API 接口地址,默认:`https://api.openai.com/v1` 11 | - `OPENAI_MODEL`:自定义 OpenAI 模型,默认:`gpt-3.5-turbo` 12 | 13 | ## 🚀 本地运行 14 | 15 | 1. 克隆仓库: 16 | 17 | ```sh 18 | git clone https://github.com/sunls24/divination 19 | ``` 20 | 21 | 2. 安装依赖项: 22 | 23 | ```bash 24 | pnpm install 25 | ``` 26 | 27 | 3. 本地运行: 28 | 29 | ```bash 30 | # 设置环境变量 OPENAI_API_KEY=sk-xxx 31 | touch .env.local 32 | # 本地运行 33 | pnpm run dev 34 | ``` 35 | 36 | ## ☁️ 使用 Vercel 部署 37 | 38 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsunls23%2Fdivination&env=OPENAI_API_KEY) 39 | 40 | --- 41 | 42 | ![screenshots](./docs/screenshots.jpg) 43 | -------------------------------------------------------------------------------- /app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/divination/4ba380775a74f8ef7238bc9e584f538a447f0d0a/app/apple-icon.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/divination/4ba380775a74f8ef7238bc9e584f538a447f0d0a/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 60 9% 98%; 8 | --foreground: 0 0% 32%; 9 | 10 | --card: 60 9% 98%; 11 | --card-foreground: 0 0% 32%; 12 | 13 | --popover: 60 9% 98%; 14 | --popover-foreground: 0 0% 32%; 15 | 16 | --primary: 0 0% 32%; 17 | --primary-foreground: 60 9.1% 97.8%; 18 | 19 | --secondary: 60 5% 96%; 20 | --secondary-foreground: 0 0% 32%; 21 | 22 | --muted: 60 4.8% 95.9%; 23 | --muted-foreground: 25 5.3% 44.7%; 24 | 25 | --accent: 20 5.9% 94%; 26 | --accent-foreground: 24 9.8% 10%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 60 9.1% 97.8%; 30 | 31 | --border: 20 5.9% 90%; 32 | --input: 24 6% 85%; 33 | --ring: 0 0% 32%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 24 10% 10%; 40 | --foreground: 60 9.1% 97.8%; 41 | 42 | --card: 24 10% 10%; 43 | --card-foreground: 60 9.1% 97.8%; 44 | 45 | --popover: 24 10% 10%; 46 | --popover-foreground: 60 9.1% 97.8%; 47 | 48 | --primary: 60 9.1% 97.8%; 49 | --primary-foreground: 0 0% 32%; 50 | 51 | --secondary: 0 0% 20%; 52 | --secondary-foreground: 60 9.1% 97.8%; 53 | 54 | --muted: 24 10% 10%; 55 | --muted-foreground: 24 5.4% 66%; 56 | 57 | --accent: 0 0% 20%; 58 | --accent-foreground: 60 9.1% 97.8%; 59 | 60 | --destructive: 0 74% 42%; 61 | --destructive-foreground: 60 9.1% 97.8%; 62 | 63 | --border: 30 6% 25%; 64 | --input: 33 5% 32%; 65 | --ring: 20 6% 90%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | 74 | body { 75 | @apply gap flex flex-col bg-background text-foreground; 76 | } 77 | 78 | body { 79 | font-family: 'LXGW WenKai Screen'; 80 | font-weight: normal; 81 | } 82 | 83 | body, 84 | html { 85 | @apply h-full; 86 | } 87 | 88 | footer { 89 | padding-bottom: max(env(safe-area-inset-bottom), 8px); 90 | } 91 | } 92 | 93 | @layer utilities { 94 | .gap { 95 | @apply gap-4 sm:gap-6; 96 | } 97 | } 98 | 99 | @layer utilities { 100 | ::-webkit-scrollbar { 101 | --bar-width: 5px; 102 | width: var(--bar-width); 103 | height: var(--bar-width); 104 | } 105 | 106 | ::-webkit-scrollbar-thumb { 107 | @apply rounded-md bg-border; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata, Viewport } from "next"; 3 | import React from "react"; 4 | import Umami from "@/components/umami"; 5 | import { ThemeProvider } from "next-themes"; 6 | 7 | export const metadata: Metadata = { 8 | title: "AI 算卦 - 在线卜卦 GPT4 解读", 9 | description: 10 | "AI 算卦 - 通过进行六次硬币的随机卜筮,生成卦象,并使用 AI 对卦象进行分析|AI 算命、在线算命、在线算卦、周易易经64卦", 11 | appleWebApp: { 12 | title: "AI 算卦", 13 | }, 14 | }; 15 | 16 | export const viewport: Viewport = { 17 | viewportFit: "cover", 18 | width: "device-width", 19 | initialScale: 1, 20 | maximumScale: 1, 21 | themeColor: [ 22 | { media: "(prefers-color-scheme: light)", color: "#f5f5f4" }, 23 | { media: "(prefers-color-scheme: dark)", color: "#333333" }, 24 | ], 25 | }; 26 | 27 | export default function RootLayout({ 28 | children, 29 | }: { 30 | children: React.ReactNode; 31 | }) { 32 | return ( 33 | 34 | 35 | 39 | 40 | 41 | 47 | {children} 48 | 49 | 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Header from "@/components/header"; 2 | import Divination from "@/components/divination"; 3 | import Footer from "@/components/footer"; 4 | 5 | export default function Home() { 6 | return ( 7 | <> 8 |
9 | 10 |