diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a7e3ae7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,19 @@ +{ + "permissions": { + "allow": [ + "Bash(git commit:*)", + "Bash(git checkout:*)", + "Bash(mkdir:*)", + "Bash(npm install)", + "Bash(nvm use:*)", + "Bash(npm run dev:*)", + "Bash(npm run build:*)", + "Bash(npm run preview:*)", + "Bash(pkill:*)", + "Bash(npx serve:*)", + "Bash(python3:*)", + "Bash(git add:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 25c9f88..e835948 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - react-migration jobs: build-deploy: @@ -12,19 +13,17 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 16 - ref: main - submodules: true + node-version: 23 + ref: ${{ github.ref }} fetch-depth: 0 - run: | - npm install -g yarn@1.22.19 # ← yarn 1系を使う! - yarn install --frozen-lockfile --ignore-engines + npm install - name: Build env: TZ: "Asia/Tokyo" run: | - yarn build + npm run build - name: Deploy uses: peaceiris/actions-gh-pages@v3 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..b393560 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +23 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..6824ddd --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + card.syui.ai + + + + +
+ + + \ No newline at end of file diff --git a/package.json b/package.json index 1bd91d4..aa85083 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,38 @@ { "name": "card", - "version": "0.0.1", + "version": "0.1.0", "private": true, + "type": "module", "scripts": { - "dev": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { "@google/model-viewer": "^3.4.0", - "@nuxtjs/proxy": "^2.1.0", + "@tanstack/react-query": "^5.17.19", "axios": "^1.6.8", - "core-js": "^3.6.4", "moment": "^2.29.4", - "three": "^0.162.0", - "vue": "^2.6.11", - "vue-loading-template": "^1.3.2", - "vue-meta": "^2.4.0", - "vue-template-compiler": "^2.6.14" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.1", + "three": "^0.163.0", + "zustand": "^4.4.7" }, "devDependencies": { - "@vue/cli-service": "~4.5.15" - }, - "resolutions": { - "minimatch": "^3.1.2" + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "postcss": "^8.4.32", + "tailwindcss": "^3.3.6", + "typescript": "^5.2.2", + "vite": "^5.0.8" } -} +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..9399d08 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,37 @@ +import { Routes, Route } from 'react-router-dom'; +import { useEffect } from 'react'; +import HomePage from './components/pages/HomePage'; +import DocsPage from './components/pages/DocsPage'; +import OwnerPage from './components/pages/OwnerPage'; +import UserPage from './components/pages/UserPage'; + +function App() { + useEffect(() => { + // HTTPS リダイレクト + if (location.protocol !== "https:" && window.location.host !== "localhost:8080") { + location.replace("https:" + location.href.substring(location.protocol.length)); + } + }, []); + + return ( +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/components/card/CardGrid.tsx b/src/components/card/CardGrid.tsx new file mode 100644 index 0000000..ffcda1d --- /dev/null +++ b/src/components/card/CardGrid.tsx @@ -0,0 +1,140 @@ +import { useState } from 'react'; +import type { Card, User } from '../../types'; +import { SKILL_ICONS, CARD_STATUS_COLORS } from '../../utils/constants'; + +interface CardGridProps { + cards: Card[]; + user?: User; +} + +export default function CardGrid({ cards }: CardGridProps) { + const [showInfo, setShowInfo] = useState(false); + const [sortBy, setSortBy] = useState<'time' | 'cp' | 'card'>('time'); + + const sortedCards = [...cards].sort((a, b) => { + switch (sortBy) { + case 'cp': + return b.cp - a.cp; + case 'card': + return b.card - a.card; + default: + return b.id - a.id; // newest first + } + }); + + const getSkillIcon = (skill: Card['skill']) => { + const iconClass = SKILL_ICONS[skill]; + if (skill === 'model') { + return ; + } + return ; + }; + + const getCardImage = (card: Card) => { + if (card.author === 'yui') { + return `/card/card_origin2_${card.card}.webp`; + } else if (card.author) { + return `/card/card_origin_${card.card}.webp`; + } + return `/card/card_${card.card}.webp`; + }; + + const isSpecialStatus = (status: Card['status']) => { + return status !== 'normal'; + }; + + return ( +
+ {/* Control Buttons */} +
+ + + + +
+ + {/* Cards Grid */} +
+ {sortedCards.map((card) => ( +
+ {isSpecialStatus(card.status) ? ( +
+
+
+ {`Card +
+
+
+
+ ) : ( +
+ {card.card === 43 ? ( + + {`Card + + ) : ( +
+ {`Card + {card.author && ( +
+ @{card.author} +
+ )} +
+ )} +
+ )} + +
+
+ {getSkillIcon(card.skill)} + {card.cp} +
+ + {showInfo && ( +
+
ID {card.card}
+
CID {card.id}
+
{card.skill}
+
✧ {card.status}
+
+ )} +
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/common/Navigation.tsx b/src/components/common/Navigation.tsx new file mode 100644 index 0000000..98d0354 --- /dev/null +++ b/src/components/common/Navigation.tsx @@ -0,0 +1,77 @@ +import { useLocation } from 'react-router-dom'; + +interface NavigationProps { + username?: string; +} + +export default function Navigation({ username: _username }: NavigationProps) { + const location = useLocation(); + const loc = location.pathname.split('/').slice(-1)[0]; + + const getDisplayText = () => { + switch (loc) { + case 'te': + return '@yui.syui.ai /ten'; + case 'pr': + return '@yui.syui.ai /fav 1234567'; + case 'docs': + case 'en': + return '@yui.syui.ai /help'; + case 'di': + return '@yui.syui.ai /did'; + case 'svn': + return '@yui.syui.ai /ten pay 7'; + default: + return '@yui.syui.ai /card'; + } + }; + + return ( +
+
+
+ + + + + + {getDisplayText()} + + +
+ +
+
+ + +
+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/pages/DocsPage.tsx b/src/components/pages/DocsPage.tsx new file mode 100644 index 0000000..2afa9c9 --- /dev/null +++ b/src/components/pages/DocsPage.tsx @@ -0,0 +1,115 @@ +import { useQuery } from '@tanstack/react-query'; +import Navigation from '../common/Navigation'; +import { fetchFanarts } from '../../utils/api'; + +interface DocsPageProps { + isEnglish?: boolean; + page?: 'did' | 'vr' | 'aiten' | 'cards' | 'seven' | 'fanart' | 'photo' | 'favorite'; +} + +export default function DocsPage({ isEnglish = false, page }: DocsPageProps) { + const { data: fanarts } = useQuery({ + queryKey: ['fanarts'], + queryFn: fetchFanarts, + enabled: page === 'fanart', + }); + + const renderContent = () => { + if (page === 'did') { + return ( +
+

DID

+
+

+ Decentralized identifiers (DIDs) are a new type of identifier that enables verifiable, + decentralized digital identity. A DID refers to any subject (e.g., a person, organization, + thing, data model, abstract entity, etc.) as determined by the controller of the DID. +

+
+

+ + https://www.w3.org/TR/did-core/ + +

+
+ ); + } + + if (page === 'fanart' && fanarts) { + return ( +
+

/fa <share-url> <img-url>

+
+ {fanarts.data.filter(item => !item.delete).map((item, index) => ( +
+

+ + + +

+

+ author: + {item.author} + +

+
+ ))} +
+
+ ); + } + + // Default documentation content + return ( +
+

{isEnglish ? 'Cards can be drawn once a day' : 'カードは1日に1回、引くことができます'}

+

{isEnglish ? 'Card emission rates are as follows' : 'カードの排出率は以下のとおりです'}

+ + + + + + + + + + + + + + + + + + + +
status
normalraresuper
90%9%1%
+ +

{isEnglish ? 'Battle' : '対戦について'}

+

@yui.syui.ai /card -b

+

{isEnglish ? 'Random match, one of the top 3 cards on hand will be chosen at random' : 'ランダムマッチ、手持ちの上位3枚のうち1枚がランダムで選ばれます'}

+ +

Mastodon

+

+ + + @yui@syui.ai + /card + +

+
+ ); + }; + + return ( +
+ + +
+
+ {renderContent()} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/pages/HomePage.tsx b/src/components/pages/HomePage.tsx new file mode 100644 index 0000000..dd8d631 --- /dev/null +++ b/src/components/pages/HomePage.tsx @@ -0,0 +1,112 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import Navigation from '../common/Navigation'; +import { fetchUsers } from '../../utils/api'; + +export default function HomePage() { + const [didEnable, setDidEnable] = useState(false); + + const { data: users, isLoading } = useQuery({ + queryKey: ['users'], + queryFn: () => fetchUsers(), + }); + + if (isLoading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+ + +
+
+ help + en + fav + ten + + all + seven +
+ + {users && ( +
+
+ {users.data.map((user) => ( +
+
+
+ {user.model && ( + + )} + {user.fav !== '0' && ( + + )} + {user.username === 'ai' && ( + + + + )} + +
+
+ + + + {didEnable && user.did && ( +
+ {user.did.includes('did:') ? ( + + ) : user.did.includes('http') ? ( + + ) : null} +
+ )} +
+ ))} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/pages/OwnerPage.tsx b/src/components/pages/OwnerPage.tsx new file mode 100644 index 0000000..9853879 --- /dev/null +++ b/src/components/pages/OwnerPage.tsx @@ -0,0 +1,73 @@ +import { useQuery } from '@tanstack/react-query'; +import Navigation from '../common/Navigation'; +import { fetchCardData } from '../../utils/api'; + +export default function OwnerPage() { + const { data: cardData, isLoading } = useQuery({ + queryKey: ['cardData'], + queryFn: fetchCardData, + }); + + const specialCards = [22, 25, 26, 27, 29, 33, 36, 39, 40, 41, 44, 45]; + + if (isLoading) { + return ( +
+ +
+
Loading...
+
+
+ ); + } + + return ( +
+ + +
+
+ {cardData?.data.map((card) => { + const shouldShow = card.id < 15 || specialCards.includes(card.id); + + if (!shouldShow) return null; + + const isBlackCard = [22, 27, 29, 33, 36].includes(card.id) && card.owner === 'none'; + + return ( +
+ + +
+ {`Card +
+ +

+ {card.owner ? ( + <> + owner : + {card.owner} + + + ) : ( + <>owner : none + )} +

+ +
+ ); + })} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/pages/UserPage.tsx b/src/components/pages/UserPage.tsx new file mode 100644 index 0000000..be4c92e --- /dev/null +++ b/src/components/pages/UserPage.tsx @@ -0,0 +1,50 @@ +import { useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import Navigation from '../common/Navigation'; +import CardGrid from '../card/CardGrid'; +import UserProfile from '../user/UserProfile'; +import { fetchUsers, fetchUserCards } from '../../utils/api'; + +export default function UserPage() { + const { username } = useParams<{ username: string }>(); + + const { data: users } = useQuery({ + queryKey: ['users'], + queryFn: () => fetchUsers(), + }); + + const user = users?.data.find(u => u.username === username); + + const { data: cards, isLoading: cardsLoading } = useQuery({ + queryKey: ['userCards', user?.id], + queryFn: () => fetchUserCards(user!.id), + enabled: !!user?.id, + }); + + if (!user) { + return ( +
+ +
+
User not found
+
+
+ ); + } + + return ( +
+ + +
+ + + {cardsLoading ? ( +
Loading cards...
+ ) : ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/user/UserProfile.tsx b/src/components/user/UserProfile.tsx new file mode 100644 index 0000000..5cd58a7 --- /dev/null +++ b/src/components/user/UserProfile.tsx @@ -0,0 +1,79 @@ +import type { User, Card } from '../../types'; + +interface UserProfileProps { + user: User; + cards: Card[]; +} + +export default function UserProfile({ user, cards }: UserProfileProps) { + return ( +
+

+ {user.username} + + {/* Badges */} + + + + + {cards.find(c => c.card === 65) && ( + + + + )} + + {user.aiten >= 70000000 && ( + + + + )} +

+ +
+
+ ID: {user.id} +
+
+ + {user.aiten?.toLocaleString()} +
+
+ + {cards.filter(c => c.skill === 'lost').length} +
+
+ + {cards.filter(c => c.card >= 96 && c.card <= 121).length} +
+ {user.planet && ( +
+ + {user.planet} +
+ )} +
+ + {/* Badge Images */} +
+ {cards.find(c => c.card === 18) && ( + Badge 1 + )} + {cards.find(c => c.card === 41) && ( + Badge 2 + )} + {cards.find(c => c.card === 45) && ( + Badge 3 + )} + {cards.find(c => c.card === 75) && ( + Badge 4 + )} + {cards.find(c => c.card === 94) && ( + Badge 5 + )} + {cards.filter(c => c.card >= 96 && c.card <= 121).length === 26 && ( + Badge 6 + )} +
+
+ ); +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..53ae34d --- /dev/null +++ b/src/index.css @@ -0,0 +1,60 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html { + @apply text-lg; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + } + + body { + @apply bg-light font-sans; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + overflow-wrap: anywhere; + word-break: normal; + line-break: strict; + } + + a { + @apply text-primary no-underline hover:text-secondary; + } +} + +@layer components { + .btn { + @apply px-3 py-1 rounded border border-primary bg-primary text-white hover:border-light hover:bg-secondary transition-colors; + } + + .btn-sm { + @apply px-2 py-1 text-sm rounded border border-primary bg-primary text-white hover:border-light; + } + + .card-reflection { + @apply block relative overflow-hidden; + } + + .card-reflection::after { + content: ""; + @apply absolute top-0 left-0 w-8 h-full bg-white/40 opacity-0; + transform: rotate(45deg) translateX(-180px); + animation: reflection 4s ease-in-out infinite; + } + + .card-wrapper { + @apply grid place-items-center relative; + } + + .card-status { + @apply aspect-[5/7] rounded-lg shadow-lg absolute w-full h-full; + } +} + +@keyframes reflection { + 0% { transform: scale(0) rotate(45deg); opacity: 0; } + 80% { transform: scale(0) rotate(45deg); opacity: 0.5; } + 81% { transform: scale(4) rotate(45deg); opacity: 1; } + 100% { transform: scale(50) rotate(45deg); opacity: 0; } +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..da6bbb4 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { BrowserRouter } from 'react-router-dom' +import App from './App.tsx' +import './index.css' + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +) \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..4a623d5 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,56 @@ +export interface User { + id: number; + username: string; + did: string; + aiten: number; + handle: string; + delete?: boolean; + created_at: string; + planet?: number; + model?: boolean; + bsky?: boolean; + fav?: string; + game?: boolean; + game_lv?: number; + model_attack?: number; + model_critical?: number; + model_critical_d?: number; + room?: boolean; + login?: boolean; + game_exp?: number; +} + +export interface Card { + id: number; + card: number; + cp: number; + status: 'normal' | 'yui' | 'first' | 'second' | 'third' | 'fourth' | 'fifth' | 'sixth' | 'seven'; + skill: 'critical' | 'post' | 'luck' | 'ten' | 'lost' | 'dragon' | 'nyan' | 'yui' | '3d' | 'model' | 'first'; + author?: string; + url?: string; +} + +export interface CardOwner { + id: number; + h: string; + owner: string | null; + ten?: number; + ten_skill?: boolean; + first_skill?: boolean; +} + +export interface Fanart { + img: string; + link: string; + author: string; + delete?: boolean; +} + +export interface Seven { + card: number; + count: number; + handle: string; + cp: number; +} + +export type LocationPath = 'di' | 'docs' | 'en' | 'vr' | 'te' | 'c' | 'svn' | 'fa' | 'ph' | 'pr' | 'owner' | string; \ No newline at end of file diff --git a/src/utils/api.ts b/src/utils/api.ts new file mode 100644 index 0000000..70667b4 --- /dev/null +++ b/src/utils/api.ts @@ -0,0 +1,49 @@ +import axios from 'axios'; +import type { User, Card, CardOwner, Fanart, Seven } from '../types'; + +const getApiUrl = () => { + if (window.location.host === "localhost:8080" || window.location.host === "192.168.11.12:8080") { + return "/api/"; + } + return "https://api.syui.ai/"; +}; + +export const api = axios.create({ + baseURL: getApiUrl(), +}); + +// API関数 +export const fetchUsers = async (itemsPerPage = 3000): Promise<{ data: User[] }> => { + const response = await api.get(`users?itemsPerPage=${itemsPerPage}`); + return response.data; +}; + +export const fetchUserCards = async (userId: number, itemsPerPage = 8000): Promise<{ data: Card[] }> => { + const response = await api.get(`users/${userId}/card?itemsPerPage=${itemsPerPage}`); + return response.data; +}; + +export const fetchUser = async (userId: number): Promise<{ data: User }> => { + const response = await api.get(`users/${userId}`); + return response.data; +}; + +export const fetchSevens = async (itemsPerPage = 8000): Promise<{ data: Seven[] }> => { + const response = await api.get(`sevs?itemsPerPage=${itemsPerPage}`); + return response.data; +}; + +export const fetchCardData = async (): Promise<{ data: CardOwner[] }> => { + const response = await axios.get("/json/card.json"); + return response.data; +}; + +export const fetchFanarts = async (): Promise<{ data: Fanart[] }> => { + const response = await axios.get("/json/fanart.json"); + return response.data; +}; + +export const fetchPhotos = async (): Promise<{ data: Fanart[] }> => { + const response = await axios.get("/json/photo.json"); + return response.data; +}; \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..2605bcb --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,45 @@ +export const CARD_STATUS_COLORS = { + yui: 'bg-gradient-to-br from-yellow-400 to-yellow-600', + first: 'bg-gradient-to-br from-cyan-400 to-blue-500', + second: 'bg-gradient-to-br from-gray-600 to-black', + third: 'bg-gradient-to-br from-pink-500 to-yellow-400', + fourth: 'bg-gradient-to-br from-blue-400 to-blue-600', + fifth: 'bg-gradient-to-br from-red-500 to-red-800', + sixth: 'bg-gradient-to-br from-gray-100 to-gray-800', + seven: 'bg-gradient-to-br from-yellow-400 to-yellow-700', + normal: 'bg-white' +} as const; + +export const SKILL_ICONS = { + critical: 'icon-sandar', + post: 'icon-moon', + luck: 'icon-api', + ten: 'icon-power', + lost: '●', + dragon: 'icon-home', + nyan: '▲', + yui: 'icon-ai', + '3d': '■', + model: 'fa-solid fa-cube', + first: 'icon-moji_a' +} as const; + +export const PLANET_THRESHOLDS = { + GALAXY: 1000000, + NEUTRON: 466666, + SUN: 333000, + EARTH: 1.0, + MOON: 0 +} as const; + +export const CARD_EMISSION_RATES = { + normal: 90, + rare: 9, + super: 1 +} as const; + +export const CP_RANGES = { + normal: { min: 0, max: 200 }, + rare: { min: 0, max: 450 }, + super: { min: 0, max: 800 } +} as const; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..7504a99 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,22 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: '#847e00', + secondary: '#008CCF', + accent: '#fff700', + dark: '#343434', + light: '#f1f1f1', + }, + fontFamily: { + sans: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif'], + }, + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7a7611e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..099658c --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..3dbc7bb --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + build: { + outDir: 'dist', + }, + server: { + port: 8080, + proxy: { + '/api': { + target: 'https://api.syui.ai', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + preview: { + port: 4173, + host: true, + https: false + } +}) \ No newline at end of file