feat: add new folder
This commit is contained in:
20
gig-poc/apps/web/Dockerfile
Normal file
20
gig-poc/apps/web/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM docker.m.daocloud.io/library/node:22-alpine AS builder
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY apps/web/package.json /app/package.json
|
||||
COPY apps/web/tsconfig.json /app/tsconfig.json
|
||||
COPY apps/web/tsconfig.app.json /app/tsconfig.app.json
|
||||
COPY apps/web/vite.config.ts /app/vite.config.ts
|
||||
COPY apps/web/index.html /app/index.html
|
||||
COPY apps/web/src /app/src
|
||||
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM docker.m.daocloud.io/library/nginx:1.27-alpine
|
||||
|
||||
COPY infrastructure/nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
12
gig-poc/apps/web/index.html
Normal file
12
gig-poc/apps/web/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Gig POC Console</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
gig-poc/apps/web/package.json
Normal file
23
gig-poc/apps/web/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "gig-poc-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0 --port 5173",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0 --port 4173"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "6.30.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.20",
|
||||
"@types/react-dom": "18.3.6",
|
||||
"@vitejs/plugin-react": "4.3.4",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "5.4.18"
|
||||
}
|
||||
}
|
||||
41
gig-poc/apps/web/src/App.tsx
Normal file
41
gig-poc/apps/web/src/App.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { NavLink, Route, Routes } from "react-router-dom";
|
||||
import { JobPage } from "./pages/JobPage";
|
||||
import { WorkerPage } from "./pages/WorkerPage";
|
||||
import { DataBrowserPage } from "./pages/DataBrowserPage";
|
||||
import { StatusPage } from "./pages/StatusPage";
|
||||
|
||||
const navItems = [
|
||||
{ to: "/", label: "岗位测试" },
|
||||
{ to: "/workers", label: "工人测试" },
|
||||
{ to: "/browse", label: "数据浏览" },
|
||||
{ to: "/status", label: "系统状态" }
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="layout-shell">
|
||||
<aside className="side-nav">
|
||||
<div>
|
||||
<p className="eyebrow">Gig POC</p>
|
||||
<h1>灵活用工匹配控制台</h1>
|
||||
<p className="side-copy">围绕“岗位理解、工人理解、LightRAG 检索、匹配排序、推荐解释”构建的最小演示台。</p>
|
||||
</div>
|
||||
<nav className="nav-list">
|
||||
{navItems.map((item) => (
|
||||
<NavLink key={item.to} to={item.to} end={item.to === "/"} className="nav-link">
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
<main className="content-area">
|
||||
<Routes>
|
||||
<Route path="/" element={<JobPage />} />
|
||||
<Route path="/workers" element={<WorkerPage />} />
|
||||
<Route path="/browse" element={<DataBrowserPage />} />
|
||||
<Route path="/status" element={<StatusPage />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
gig-poc/apps/web/src/api/client.ts
Normal file
33
gig-poc/apps/web/src/api/client.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
const API_BASE = import.meta.env.VITE_API_BASE ?? "/api";
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${API_BASE}${path}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(init?.headers ?? {})
|
||||
},
|
||||
...init
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(text || `Request failed: ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
health: () => request("/health"),
|
||||
extractJob: (text: string) => request("/poc/extract/job", { method: "POST", body: JSON.stringify({ text }) }),
|
||||
extractWorker: (text: string) => request("/poc/extract/worker", { method: "POST", body: JSON.stringify({ text }) }),
|
||||
ingestJob: (job: unknown) => request("/poc/ingest/job", { method: "POST", body: JSON.stringify({ job }) }),
|
||||
ingestWorker: (worker: unknown) => request("/poc/ingest/worker", { method: "POST", body: JSON.stringify({ worker }) }),
|
||||
bootstrap: () => request("/poc/ingest/bootstrap", { method: "POST" }),
|
||||
matchWorkers: (job: unknown, top_n = 10) =>
|
||||
request("/poc/match/workers", { method: "POST", body: JSON.stringify({ job, top_n }) }),
|
||||
matchJobs: (worker: unknown, top_n = 10) =>
|
||||
request("/poc/match/jobs", { method: "POST", body: JSON.stringify({ worker, top_n }) }),
|
||||
jobs: () => request("/poc/jobs"),
|
||||
workers: () => request("/poc/workers"),
|
||||
job: (jobId: string) => request(`/poc/jobs/${jobId}`),
|
||||
worker: (workerId: string) => request(`/poc/workers/${workerId}`)
|
||||
};
|
||||
15
gig-poc/apps/web/src/components/JsonPanel.tsx
Normal file
15
gig-poc/apps/web/src/components/JsonPanel.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
type JsonPanelProps = {
|
||||
title: string;
|
||||
data: unknown;
|
||||
};
|
||||
|
||||
export function JsonPanel({ title, data }: JsonPanelProps) {
|
||||
return (
|
||||
<section className="panel">
|
||||
<div className="panel-head">
|
||||
<h3>{title}</h3>
|
||||
</div>
|
||||
<pre className="code-block">{JSON.stringify(data, null, 2)}</pre>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
34
gig-poc/apps/web/src/components/MatchList.tsx
Normal file
34
gig-poc/apps/web/src/components/MatchList.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
type MatchItem = {
|
||||
match_id: string;
|
||||
target_id: string;
|
||||
match_score: number;
|
||||
reasons: string[];
|
||||
breakdown: Record<string, number>;
|
||||
};
|
||||
|
||||
export function MatchList({ title, items }: { title: string; items: MatchItem[] }) {
|
||||
return (
|
||||
<section className="panel">
|
||||
<div className="panel-head">
|
||||
<h3>{title}</h3>
|
||||
<span className="badge">{items.length} 条</span>
|
||||
</div>
|
||||
<div className="match-grid">
|
||||
{items.map((item) => (
|
||||
<article key={item.match_id} className="match-card">
|
||||
<div className="match-topline">
|
||||
<strong>{item.target_id}</strong>
|
||||
<span>{item.match_score}</span>
|
||||
</div>
|
||||
<div className="reason-list">
|
||||
{item.reasons.map((reason) => (
|
||||
<p key={reason}>{reason}</p>
|
||||
))}
|
||||
</div>
|
||||
<pre className="mini-code">{JSON.stringify(item.breakdown, null, 2)}</pre>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
13
gig-poc/apps/web/src/main.tsx
Normal file
13
gig-poc/apps/web/src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import "./styles/global.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
28
gig-poc/apps/web/src/pages/DataBrowserPage.tsx
Normal file
28
gig-poc/apps/web/src/pages/DataBrowserPage.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import { JsonPanel } from "../components/JsonPanel";
|
||||
|
||||
export function DataBrowserPage() {
|
||||
const [jobs, setJobs] = useState<any[]>([]);
|
||||
const [workers, setWorkers] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
const [jobResult, workerResult] = await Promise.all([api.jobs(), api.workers()]);
|
||||
setJobs(jobResult.items.slice(0, 12));
|
||||
setWorkers(workerResult.items.slice(0, 12));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="page-grid">
|
||||
<section className="hero-card">
|
||||
<p className="eyebrow">Page C</p>
|
||||
<h2>数据浏览页</h2>
|
||||
<p>浏览当前已入库的岗位与工人样本,方便验证 bootstrap 与前后端查询链路。</p>
|
||||
</section>
|
||||
<JsonPanel title="Jobs" data={jobs} />
|
||||
<JsonPanel title="Workers" data={workers} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
gig-poc/apps/web/src/pages/JobPage.tsx
Normal file
53
gig-poc/apps/web/src/pages/JobPage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import { JsonPanel } from "../components/JsonPanel";
|
||||
import { MatchList } from "../components/MatchList";
|
||||
|
||||
const DEFAULT_TEXT = "明天下午南山会展中心需要2个签到协助,5小时,150/人,女生优先,需要会签到、引导和登记。";
|
||||
|
||||
export function JobPage() {
|
||||
const [text, setText] = useState(DEFAULT_TEXT);
|
||||
const [jobCard, setJobCard] = useState<unknown>(null);
|
||||
const [matches, setMatches] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleExtract = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await api.extractJob(text);
|
||||
setJobCard(result.data);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIngestAndMatch = async () => {
|
||||
if (!jobCard) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.ingestJob(jobCard);
|
||||
const result = await api.matchWorkers(jobCard, 10);
|
||||
setMatches(result.items);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-grid">
|
||||
<section className="hero-card">
|
||||
<p className="eyebrow">Page A</p>
|
||||
<h2>岗位测试页</h2>
|
||||
<textarea value={text} onChange={(event) => setText(event.target.value)} rows={8} />
|
||||
<div className="button-row">
|
||||
<button onClick={handleExtract} disabled={loading}>抽取岗位</button>
|
||||
<button className="ghost" onClick={handleIngestAndMatch} disabled={loading || !jobCard}>入库并匹配工人</button>
|
||||
</div>
|
||||
</section>
|
||||
{jobCard ? <JsonPanel title="JobCard" data={jobCard} /> : null}
|
||||
{matches.length ? <MatchList title="匹配工人结果" items={matches} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
gig-poc/apps/web/src/pages/StatusPage.tsx
Normal file
43
gig-poc/apps/web/src/pages/StatusPage.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import { JsonPanel } from "../components/JsonPanel";
|
||||
|
||||
export function StatusPage() {
|
||||
const [health, setHealth] = useState<unknown>(null);
|
||||
const [bootstrapResult, setBootstrapResult] = useState<unknown>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleHealth = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setHealth(await api.health());
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBootstrap = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setBootstrapResult(await api.bootstrap());
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-grid">
|
||||
<section className="hero-card">
|
||||
<p className="eyebrow">Page D</p>
|
||||
<h2>系统状态页</h2>
|
||||
<p>检查 API、PostgreSQL、Qdrant 健康状态,并执行样本数据 bootstrap。</p>
|
||||
<div className="button-row">
|
||||
<button onClick={handleHealth} disabled={loading}>检查健康状态</button>
|
||||
<button className="ghost" onClick={handleBootstrap} disabled={loading}>导入样本数据</button>
|
||||
</div>
|
||||
</section>
|
||||
{health ? <JsonPanel title="Health" data={health} /> : null}
|
||||
{bootstrapResult ? <JsonPanel title="Bootstrap" data={bootstrapResult} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
gig-poc/apps/web/src/pages/WorkerPage.tsx
Normal file
53
gig-poc/apps/web/src/pages/WorkerPage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import { JsonPanel } from "../components/JsonPanel";
|
||||
import { MatchList } from "../components/MatchList";
|
||||
|
||||
const DEFAULT_TEXT = "我做过商场促销和活动签到,也能做登记和引导,周末都能接,福田南山都方便。";
|
||||
|
||||
export function WorkerPage() {
|
||||
const [text, setText] = useState(DEFAULT_TEXT);
|
||||
const [workerCard, setWorkerCard] = useState<unknown>(null);
|
||||
const [matches, setMatches] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleExtract = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await api.extractWorker(text);
|
||||
setWorkerCard(result.data);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIngestAndMatch = async () => {
|
||||
if (!workerCard) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.ingestWorker(workerCard);
|
||||
const result = await api.matchJobs(workerCard, 10);
|
||||
setMatches(result.items);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-grid">
|
||||
<section className="hero-card">
|
||||
<p className="eyebrow">Page B</p>
|
||||
<h2>工人测试页</h2>
|
||||
<textarea value={text} onChange={(event) => setText(event.target.value)} rows={8} />
|
||||
<div className="button-row">
|
||||
<button onClick={handleExtract} disabled={loading}>抽取工人画像</button>
|
||||
<button className="ghost" onClick={handleIngestAndMatch} disabled={loading || !workerCard}>入库并匹配岗位</button>
|
||||
</div>
|
||||
</section>
|
||||
{workerCard ? <JsonPanel title="WorkerCard" data={workerCard} /> : null}
|
||||
{matches.length ? <MatchList title="匹配岗位结果" items={matches} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
176
gig-poc/apps/web/src/styles/global.css
Normal file
176
gig-poc/apps/web/src/styles/global.css
Normal file
@@ -0,0 +1,176 @@
|
||||
:root {
|
||||
font-family: "PingFang SC", "Noto Sans SC", sans-serif;
|
||||
color: #0f172a;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(34, 197, 94, 0.18), transparent 25%),
|
||||
radial-gradient(circle at 80% 20%, rgba(14, 165, 233, 0.16), transparent 22%),
|
||||
linear-gradient(135deg, #f8fafc 0%, #eefbf3 45%, #f3f7fb 100%);
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
button,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.layout-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
padding: 32px 24px;
|
||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0.96) 0%, rgba(21, 44, 79, 0.92) 100%);
|
||||
color: #e2e8f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.side-copy {
|
||||
color: #bfd6ea;
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #dbeafe;
|
||||
text-decoration: none;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
transition: transform 160ms ease, background 160ms ease;
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.nav-link:hover {
|
||||
background: rgba(56, 189, 248, 0.18);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.page-grid {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hero-card,
|
||||
.panel,
|
||||
.match-card {
|
||||
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.hero-card,
|
||||
.panel {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.panel-head,
|
||||
.match-topline,
|
||||
.button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
margin: 12px 0 0;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 18px;
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 12px 18px;
|
||||
background: linear-gradient(135deg, #0f766e 0%, #0284c7 100%);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.ghost {
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border-radius: 999px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.code-block,
|
||||
.mini-code {
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
background: #0f172a;
|
||||
color: #dbeafe;
|
||||
border-radius: 18px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.match-grid {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}
|
||||
|
||||
.match-card {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.reason-list p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.layout-shell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.side-copy {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
20
gig-poc/apps/web/tsconfig.app.json
Normal file
20
gig-poc/apps/web/tsconfig.app.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
6
gig-poc/apps/web/tsconfig.json
Normal file
6
gig-poc/apps/web/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" }
|
||||
]
|
||||
}
|
||||
10
gig-poc/apps/web/vite.config.ts
Normal file
10
gig-poc/apps/web/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
host: "0.0.0.0"
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user