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 { Button, Input, Card, Toast, ErrorBlock } from 'antd-mobile';
|
||||
import { Copy, RefreshCw, Sparkles, Wand2, Quote, UserCircle2, Download } from 'lucide-react';
|
||||
import { Button, Input, Toast, ErrorBlock } from 'antd-mobile';
|
||||
import { Copy, RefreshCw, Sparkles, Wand2, Quote, UserCircle2, Download, Image as ImageIcon } from 'lucide-react';
|
||||
import { useAppStore } from '../store/useAppStore';
|
||||
import { generateCopyStream } from '../services/api';
|
||||
import { Header } from '../components/Header';
|
||||
@@ -91,17 +91,44 @@ export const Home: React.FC = () => {
|
||||
const copyToClipboard = () => {
|
||||
if (!result) return;
|
||||
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 () => {
|
||||
if (!posterRef.current || !result) return;
|
||||
try {
|
||||
const canvas = await html2canvas(posterRef.current, {
|
||||
useCORS: true,
|
||||
scale: 2,
|
||||
backgroundColor: null // Transparent background if needed, but poster usually has bg
|
||||
backgroundColor: null
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.download = `wx-moments-${Date.now()}.png`;
|
||||
@@ -179,7 +206,7 @@ export const Home: React.FC = () => {
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
基于 DashScope 大模型 · 智能匹配场景
|
||||
基于 DashScope 大模型 (qwen3.5-plus) · 智能匹配场景
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,7 +260,11 @@ export const Home: React.FC = () => {
|
||||
</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 */}
|
||||
{currentPosterUrl && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
@@ -243,33 +274,36 @@ export const Home: React.FC = () => {
|
||||
className="w-full h-full object-cover"
|
||||
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>
|
||||
)}
|
||||
|
||||
<Card
|
||||
className={`${currentPosterUrl ? 'bg-transparent border-0 text-white' : 'bg-white dark:bg-slate-900 border-0'} rounded-3xl overflow-hidden relative z-10`}
|
||||
bodyClassName="p-0"
|
||||
style={{ background: currentPosterUrl ? 'transparent' : undefined }}
|
||||
>
|
||||
<div className="p-6 md:p-10 min-h-[300px] flex flex-col">
|
||||
<div className="flex-1 relative">
|
||||
<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 className="relative z-10 h-full flex flex-col p-8 md:p-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'}`} />
|
||||
|
||||
<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'}`}>
|
||||
{result}
|
||||
{loading && (
|
||||
<span className={`inline-block w-2 h-5 ml-1 animate-pulse align-middle ${currentPosterUrl ? 'bg-white' : 'bg-indigo-500'}`} />
|
||||
)}
|
||||
</div>
|
||||
<div ref={resultEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<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}
|
||||
{loading && (
|
||||
<span className={`inline-block w-2 h-6 ml-1 animate-pulse align-middle ${currentPosterUrl ? 'bg-white' : 'bg-indigo-500'}`} />
|
||||
)}
|
||||
<div ref={resultEndRef} />
|
||||
</div>
|
||||
|
||||
<div className={`mt-8 pt-6 border-t ${currentPosterUrl ? 'border-white/20' : 'border-slate-100 dark:border-slate-800'}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`text-sm ${currentPosterUrl ? 'text-white/80' : 'text-slate-400'}`}>
|
||||
Generated by AI
|
||||
</div>
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${currentPosterUrl ? 'bg-white/20' : 'bg-indigo-50'}`}>
|
||||
<Sparkles size={14} className={currentPosterUrl ? 'text-white' : 'text-indigo-500'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
size="middle"
|
||||
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"
|
||||
>
|
||||
<RefreshCw size={18} className={`mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
{loading ? '生成中' : '换一个'}
|
||||
{loading ? '重写' : '换一个'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -294,16 +328,29 @@ export const Home: React.FC = () => {
|
||||
</Button>
|
||||
|
||||
{currentPosterUrl && result && !loading && (
|
||||
<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>
|
||||
<>
|
||||
<Button
|
||||
size="middle"
|
||||
color="warning"
|
||||
fill="solid"
|
||||
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"
|
||||
>
|
||||
<ImageIcon size={18} className="mr-2" />
|
||||
复制海报
|
||||
</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>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user