This commit is contained in:
42
.gitea/workflows/deploy.yaml
Normal file
42
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Deploy Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest
|
||||||
|
|
||||||
|
- name: Deploy to Server
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SERVER_HOST }}
|
||||||
|
username: ${{ secrets.SERVER_USERNAME }}
|
||||||
|
key: ${{ secrets.SERVER_KEY }}
|
||||||
|
script: |
|
||||||
|
docker pull ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest
|
||||||
|
docker stop wx-pyq || true
|
||||||
|
docker rm wx-pyq || true
|
||||||
|
docker run -d --name wx-pyq -p 80:80 ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Button, Input, Card, Toast, ErrorBlock } from 'antd-mobile';
|
import { Button, Input, Toast, ErrorBlock } from 'antd-mobile';
|
||||||
import { Copy, RefreshCw, Sparkles, Wand2, Quote, UserCircle2, Download } from 'lucide-react';
|
import { Copy, RefreshCw, Sparkles, Wand2, Quote, UserCircle2, Download, Image as ImageIcon } from 'lucide-react';
|
||||||
import { useAppStore } from '../store/useAppStore';
|
import { useAppStore } from '../store/useAppStore';
|
||||||
import { generateCopyStream } from '../services/api';
|
import { generateCopyStream } from '../services/api';
|
||||||
import { Header } from '../components/Header';
|
import { Header } from '../components/Header';
|
||||||
@@ -91,17 +91,44 @@ export const Home: React.FC = () => {
|
|||||||
const copyToClipboard = () => {
|
const copyToClipboard = () => {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
navigator.clipboard.writeText(result).then(() => {
|
navigator.clipboard.writeText(result).then(() => {
|
||||||
Toast.show({ content: '已复制到剪贴板', icon: 'success' });
|
Toast.show({ content: '已复制文案', icon: 'success' });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyPosterImage = async () => {
|
||||||
|
if (!posterRef.current || !result) return;
|
||||||
|
try {
|
||||||
|
const canvas = await html2canvas(posterRef.current, {
|
||||||
|
useCORS: true,
|
||||||
|
scale: 2,
|
||||||
|
backgroundColor: null
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.toBlob(async (blob) => {
|
||||||
|
if (!blob) return;
|
||||||
|
try {
|
||||||
|
// Attempt to write to clipboard
|
||||||
|
const item = new ClipboardItem({ 'image/png': blob });
|
||||||
|
await navigator.clipboard.write([item]);
|
||||||
|
Toast.show({ content: '海报已复制', icon: 'success' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Clipboard write failed:', err);
|
||||||
|
Toast.show({ content: '复制失败,请尝试保存', icon: 'fail' });
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Capture failed:', err);
|
||||||
|
Toast.show({ content: '生成图片失败', icon: 'fail' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveImage = async () => {
|
const handleSaveImage = async () => {
|
||||||
if (!posterRef.current || !result) return;
|
if (!posterRef.current || !result) return;
|
||||||
try {
|
try {
|
||||||
const canvas = await html2canvas(posterRef.current, {
|
const canvas = await html2canvas(posterRef.current, {
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
scale: 2,
|
scale: 2,
|
||||||
backgroundColor: null // Transparent background if needed, but poster usually has bg
|
backgroundColor: null
|
||||||
});
|
});
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `wx-moments-${Date.now()}.png`;
|
link.download = `wx-moments-${Date.now()}.png`;
|
||||||
@@ -179,7 +206,7 @@ export const Home: React.FC = () => {
|
|||||||
|
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||||
基于 DashScope 大模型 · 智能匹配场景
|
基于 DashScope 大模型 (qwen3.5-plus) · 智能匹配场景
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,7 +260,11 @@ export const Home: React.FC = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div ref={posterRef} className="relative rounded-3xl overflow-hidden shadow-2xl transition-all">
|
{/* Poster Container */}
|
||||||
|
<div
|
||||||
|
ref={posterRef}
|
||||||
|
className="relative rounded-3xl overflow-hidden shadow-2xl transition-all aspect-[3/4] md:aspect-[4/5] max-w-[500px] mx-auto bg-white dark:bg-slate-900"
|
||||||
|
>
|
||||||
{/* Background Image Layer */}
|
{/* Background Image Layer */}
|
||||||
{currentPosterUrl && (
|
{currentPosterUrl && (
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
@@ -243,33 +274,36 @@ export const Home: React.FC = () => {
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black/40 backdrop-blur-[2px]" /> {/* Overlay for readability */}
|
<div className="absolute inset-0 bg-black/40 backdrop-blur-[1px]" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card
|
<div className="relative z-10 h-full flex flex-col p-8 md:p-10">
|
||||||
className={`${currentPosterUrl ? 'bg-transparent border-0 text-white' : 'bg-white dark:bg-slate-900 border-0'} rounded-3xl overflow-hidden relative z-10`}
|
<Quote className={`w-12 h-12 transform -scale-x-100 mb-6 ${currentPosterUrl ? 'text-white/60' : 'text-indigo-100 dark:text-indigo-900/30'}`} />
|
||||||
bodyClassName="p-0"
|
|
||||||
style={{ background: currentPosterUrl ? 'transparent' : undefined }}
|
<div className={`flex-1 text-xl md:text-2xl leading-relaxed font-medium whitespace-pre-wrap font-sans overflow-y-auto custom-scrollbar ${currentPosterUrl ? 'text-white drop-shadow-md' : 'text-slate-700 dark:text-slate-200'}`}>
|
||||||
>
|
{result}
|
||||||
<div className="p-6 md:p-10 min-h-[300px] flex flex-col">
|
{loading && (
|
||||||
<div className="flex-1 relative">
|
<span className={`inline-block w-2 h-6 ml-1 animate-pulse align-middle ${currentPosterUrl ? 'bg-white' : 'bg-indigo-500'}`} />
|
||||||
<Quote className={`absolute -top-2 -left-2 w-16 h-16 transform -scale-x-100 ${currentPosterUrl ? 'text-white/30' : 'text-indigo-100 dark:text-indigo-900/30'}`} />
|
)}
|
||||||
|
<div ref={resultEndRef} />
|
||||||
<div className={`relative z-10 text-lg md:text-xl leading-relaxed font-medium whitespace-pre-wrap font-sans ${currentPosterUrl ? 'text-white drop-shadow-md' : 'text-slate-700 dark:text-slate-200'}`}>
|
</div>
|
||||||
{result}
|
|
||||||
{loading && (
|
<div className={`mt-8 pt-6 border-t ${currentPosterUrl ? 'border-white/20' : 'border-slate-100 dark:border-slate-800'}`}>
|
||||||
<span className={`inline-block w-2 h-5 ml-1 animate-pulse align-middle ${currentPosterUrl ? 'bg-white' : 'bg-indigo-500'}`} />
|
<div className="flex items-center justify-between">
|
||||||
)}
|
<div className={`text-sm ${currentPosterUrl ? 'text-white/80' : 'text-slate-400'}`}>
|
||||||
</div>
|
Generated by AI
|
||||||
<div ref={resultEndRef} />
|
</div>
|
||||||
</div>
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${currentPosterUrl ? 'bg-white/20' : 'bg-indigo-50'}`}>
|
||||||
</div>
|
<Sparkles size={14} className={currentPosterUrl ? 'text-white' : 'text-indigo-500'} />
|
||||||
</Card>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions Bar (Outside of Poster Ref) */}
|
{/* Actions Bar (Outside of Poster Ref) */}
|
||||||
<div className="mt-6 flex justify-end gap-3">
|
<div className="mt-6 flex flex-wrap justify-center md:justify-end gap-3">
|
||||||
<Button
|
<Button
|
||||||
size="middle"
|
size="middle"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -278,7 +312,7 @@ export const Home: React.FC = () => {
|
|||||||
className="bg-white/50 dark:bg-slate-800/50 backdrop-blur text-slate-600 dark:text-slate-300 hover:bg-white/80 dark:hover:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700"
|
className="bg-white/50 dark:bg-slate-800/50 backdrop-blur text-slate-600 dark:text-slate-300 hover:bg-white/80 dark:hover:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<RefreshCw size={18} className={`mr-2 ${loading ? 'animate-spin' : ''}`} />
|
<RefreshCw size={18} className={`mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||||
{loading ? '生成中' : '换一个'}
|
{loading ? '重写' : '换一个'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -294,16 +328,29 @@ export const Home: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{currentPosterUrl && result && !loading && (
|
{currentPosterUrl && result && !loading && (
|
||||||
<Button
|
<>
|
||||||
size="middle"
|
<Button
|
||||||
color="success"
|
size="middle"
|
||||||
fill="solid"
|
color="warning"
|
||||||
onClick={handleSaveImage}
|
fill="solid"
|
||||||
className="rounded-xl px-6 bg-emerald-500 hover:bg-emerald-600 border-none shadow-md shadow-emerald-500/20 flex items-center text-white active:scale-95 transition-transform"
|
onClick={copyPosterImage}
|
||||||
>
|
className="rounded-xl px-6 bg-amber-500 hover:bg-amber-600 border-none shadow-md shadow-amber-500/20 flex items-center text-white active:scale-95 transition-transform"
|
||||||
<Download size={18} className="mr-2" />
|
>
|
||||||
保存海报
|
<ImageIcon size={18} className="mr-2" />
|
||||||
</Button>
|
复制海报
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
color="success"
|
||||||
|
fill="solid"
|
||||||
|
onClick={handleSaveImage}
|
||||||
|
className="rounded-xl px-6 bg-emerald-500 hover:bg-emerald-600 border-none shadow-md shadow-emerald-500/20 flex items-center text-white active:scale-95 transition-transform"
|
||||||
|
>
|
||||||
|
<Download size={18} className="mr-2" />
|
||||||
|
保存海报
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
Reference in New Issue
Block a user