Initial commit: Copilot Toolbox template project
This commit is contained in:
18
.env.development
Normal file
18
.env.development
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 网站标题
|
||||||
|
VITE_DOCUMENT_TITLE='AI办公助手'
|
||||||
|
|
||||||
|
# 服务前缀
|
||||||
|
VITE_API_BASE_AI=/langwell-api/langwell-ai-server
|
||||||
|
VITE_API_BASE_DOC=/langwell-api/langwell-doc-server
|
||||||
|
VITE_API_BASE_NOTE=/langwell-api/langwell-notes-server
|
||||||
|
VITE_API_BASE_PUB=/langwell-api/langwell-pub-server
|
||||||
|
VITE_API_BASE_INS=/langwell-api/langwell-ins-server
|
||||||
|
VITE_API_BASE_TOOL=/copilot-tool
|
||||||
|
VITE_API_BASE_LAMP=/lamp-api
|
||||||
|
VITE_DIRECTION_API = /direction-api
|
||||||
|
# 语音识别
|
||||||
|
VITE_API_BASE_VOICE=/voice-api
|
||||||
|
# AI后端请求地址
|
||||||
|
VITE_AI_API_BASE=/v1
|
||||||
|
# Xiren-Lite SDK 配置 - 开发环境使用代理
|
||||||
|
VITE_XIREN_LITE_BASE_URL=/xiren-api
|
||||||
18
.env.production
Normal file
18
.env.production
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 网站标题
|
||||||
|
VITE_DOCUMENT_TITLE='AI办公助手'
|
||||||
|
|
||||||
|
# 服务前缀
|
||||||
|
VITE_API_BASE_AI=/langwell-api/langwell-ai-server
|
||||||
|
VITE_API_BASE_DOC=/langwell-api/langwell-doc-server
|
||||||
|
VITE_API_BASE_NOTE=/langwell-api/langwell-notes-server
|
||||||
|
VITE_API_BASE_PUB=/langwell-api/langwell-pub-server
|
||||||
|
VITE_API_BASE_INS=/langwell-api/langwell-ins-server
|
||||||
|
VITE_API_BASE_TOOL=/copilot-tool
|
||||||
|
VITE_API_BASE_LAMP=/lamp-api
|
||||||
|
VITE_DIRECTION_API = /direction-api
|
||||||
|
# 语音识别
|
||||||
|
VITE_API_BASE_VOICE=/voice-api
|
||||||
|
# AI后端请求地址
|
||||||
|
VITE_AI_API_BASE=/v1
|
||||||
|
# Xiren-Lite SDK 配置 - 开发环境使用代理
|
||||||
|
VITE_XIREN_LITE_BASE_URL=/xiren-api
|
||||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.history
|
||||||
|
# Editor directories and files
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
dist.zip
|
||||||
|
copilot-toolbox.zip
|
||||||
|
copilot-toolbox/
|
||||||
|
.trae
|
||||||
87
CLAUDE.md
Normal file
87
CLAUDE.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Copilot Toolbox is a React + TypeScript + Vite frontend template for building modern AI-powered applications. Integrates Dify AI, CopilotKit with Ant Design and Tailwind CSS.
|
||||||
|
|
||||||
|
**This is a template project - all development must extend this project, not create new repositories.**
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- React 18 + TypeScript + Vite 6
|
||||||
|
- Ant Design 5.x (UI components)
|
||||||
|
- Tailwind CSS 4.x + Less (styling)
|
||||||
|
- React Router DOM 6.x (Hash mode routing)
|
||||||
|
- @copilotkit/react-core, @ag-ui/client, @shangzy/ag-ui-dify (AI integration)
|
||||||
|
|
||||||
|
## Directory Conventions
|
||||||
|
|
||||||
|
| Type | Path Pattern | Example |
|
||||||
|
|------|--------------|---------|
|
||||||
|
| Pages | `src/pages/[name]/index.tsx` | `src/pages/home/index.tsx` |
|
||||||
|
| API | `src/api/[name].ts` | `src/api/user.ts` |
|
||||||
|
| Components | `src/components/[name]/index.tsx` | `src/components/Header/index.tsx` |
|
||||||
|
| Utils | `src/utils/[name].ts` | `src/utils/cacheUtil.ts` |
|
||||||
|
|
||||||
|
- Use `@/` alias for imports (configured in vite.config.ts)
|
||||||
|
- TypeScript strict mode enabled
|
||||||
|
- Tailwind CSS for styling, Ant Design for components
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install # Install dependencies
|
||||||
|
pnpm dev # Dev server (port 5173)
|
||||||
|
pnpm build # Production build
|
||||||
|
pnpm preview # Preview production build (port 3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
All API base paths use proxy prefixes. Set in `.env.development` or `.env.production`:
|
||||||
|
|
||||||
|
```
|
||||||
|
VITE_API_BASE_AI=/langwell-api/langwell-ai-server
|
||||||
|
VITE_API_BASE_DOC=/langwell-api/langwell-doc-server
|
||||||
|
VITE_API_BASE_LAMP=/lamp-api
|
||||||
|
VITE_AI_API_BASE=/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/api/[name].ts
|
||||||
|
export function getData(params: { id: string }) {
|
||||||
|
return fetch(`${import.meta.env.VITE_API_BASE_AI}/endpoint`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then(res => res.json())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
Routes defined in `src/router/index.tsx` using lazy loading:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
path: '/page-name',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/page-name')))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding New Features
|
||||||
|
|
||||||
|
1. Create page: `src/pages/[name]/index.tsx`
|
||||||
|
2. Create API: `src/api/[name].ts`
|
||||||
|
3. Add route: `src/router/index.tsx`
|
||||||
|
4. Use `@/` alias for imports
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
- Do NOT create new repositories - extend this project
|
||||||
|
- Do NOT remove CLAUDE.md
|
||||||
|
- Use `@/` alias, not relative paths
|
||||||
|
- TypeScript strict mode cannot be disabled
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM node:24-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 配置阿里云 npm 镜像
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
|
# 复制依赖文件
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# 安装所有依赖(包括 devDependencies)
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 运行开发服务器
|
||||||
|
CMD ["npm", "run", "dev", "--", "--port", "3000"]
|
||||||
7
docker-compose.yml
Normal file
7
docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: copilot-toolbox-dev
|
||||||
|
restart: unless-stopped
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
25
index.html
Normal file
25
index.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
|
<meta http-equiv="Expires" content="0">
|
||||||
|
<title>AI办公助手</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
<script>
|
||||||
|
window.iFrameResizer = {
|
||||||
|
heightCalculationMethod: 'lowestElement'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/iframeResizer.contentWindow.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
5696
package-lock.json
generated
Normal file
5696
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package.json
Normal file
35
package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "copilot-toolbox",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host 0.0.0.0",
|
||||||
|
"build": "vite build --mode production",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ag-ui/client": "^0.0.42",
|
||||||
|
"@ant-design/icons": "^5.5.2",
|
||||||
|
"@ant-design/pro-components": "^2.8.10",
|
||||||
|
"@copilotkit/react-core": "1.10.6",
|
||||||
|
"@shangzy/ag-ui-dify": "^0.1.0",
|
||||||
|
"antd": "^5.23.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"less": "^4.2.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.28.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "4.0.12",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.1.7",
|
||||||
|
"tailwindcss": "4.0.12",
|
||||||
|
"typescript": "~5.6.3",
|
||||||
|
"vite": "^6.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
3968
pnpm-lock.yaml
generated
Normal file
3968
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1311
public/iframeResizer.contentWindow.js
Normal file
1311
public/iframeResizer.contentWindow.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
44
src/App.tsx
Normal file
44
src/App.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { RouterProvider } from 'react-router-dom'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import router from '@/router'
|
||||||
|
import { cacheClear, cacheSet } from '@/utils/cacheUtil'
|
||||||
|
import { getAgentBaseInfo, getUserInfoById } from '@/api/common'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const queryString = window.location.search
|
||||||
|
const searchParams = new URLSearchParams(queryString)
|
||||||
|
const tenantId = searchParams.get('tenantId') || ''
|
||||||
|
const token = searchParams.get('token') || ''
|
||||||
|
|
||||||
|
cacheSet('tenantId', tenantId)
|
||||||
|
cacheSet('token', token)
|
||||||
|
useEffect(() => {
|
||||||
|
const agentId = searchParams.get('agentId') || ''
|
||||||
|
|
||||||
|
if (agentId) {
|
||||||
|
getAgentBaseInfo({ id: agentId }).then(res => {
|
||||||
|
cacheSet('appKey', res.data.appKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tenantId && token) {
|
||||||
|
getUserInfoById().then(response => {
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
cacheSet('userInfo', JSON.stringify(response.data))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
cacheClear('userInfo')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
28
src/api/common.ts
Normal file
28
src/api/common.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { cacheGet } from '@/utils/cacheUtil'
|
||||||
|
|
||||||
|
export function getAgentBaseInfo(params: { id: string }) {
|
||||||
|
const Tenantid = cacheGet('tenantId')
|
||||||
|
const Token = cacheGet('token')
|
||||||
|
return fetch(`${import.meta.env.VITE_API_BASE_AI}/agent/baseInfo/info?id=${params.id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Tenantid,
|
||||||
|
Token
|
||||||
|
}
|
||||||
|
}).then(res => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
export async function getUserInfoById() {
|
||||||
|
const Tenantid = cacheGet('tenantId')
|
||||||
|
const Token = cacheGet('token')
|
||||||
|
return fetch(`${import.meta.env['VITE_API_BASE_LAMP']}/oauth/anyone/getUserInfoById`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Tenantid,
|
||||||
|
Token
|
||||||
|
}
|
||||||
|
}).then(res => res.json())
|
||||||
|
}
|
||||||
34
src/api/zh-en-translator.ts
Normal file
34
src/api/zh-en-translator.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export interface TranslationRequest {
|
||||||
|
source_content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DifyRequest {
|
||||||
|
inputs: {
|
||||||
|
prompt: string
|
||||||
|
}
|
||||||
|
query: string
|
||||||
|
response_mode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function translateChineseToEnglish(source_content: string) {
|
||||||
|
const prompt = `你是一个专业的中英翻译专家。请将下面的中文文本翻译成自然、地道、专业的英文,只返回翻译后的英文内容,不要添加任何额外的解释或格式。待翻译的中文内容是:
|
||||||
|
|
||||||
|
${source_content}`
|
||||||
|
|
||||||
|
const requestBody: DifyRequest = {
|
||||||
|
inputs: {
|
||||||
|
prompt
|
||||||
|
},
|
||||||
|
query: '1',
|
||||||
|
response_mode: 'streaming'
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch('https://copilot.sino-bridge.com/v1/chat-messages', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer app-Y6ekYkw3aoUV3jmfZdg24Adh'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
})
|
||||||
|
}
|
||||||
29
src/index.css
Normal file
29
src/index.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
.route-loading {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 120px;
|
||||||
|
height: 22px;
|
||||||
|
color: #514b82;
|
||||||
|
border: 2px solid;
|
||||||
|
border-radius: 20px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes l6 {
|
||||||
|
100% {
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-loading::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 100% 0 0;
|
||||||
|
margin: 2px;
|
||||||
|
content: '';
|
||||||
|
background: currentcolor;
|
||||||
|
border-radius: inherit;
|
||||||
|
animation: l6 2s infinite;
|
||||||
|
}
|
||||||
12
src/main.tsx
Normal file
12
src/main.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import './utils/polyfills'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { ConfigProvider } from 'antd'
|
||||||
|
import zhCN from 'antd/es/locale/zh_CN'
|
||||||
|
import App from './App'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<ConfigProvider locale={zhCN}>
|
||||||
|
<App />
|
||||||
|
</ConfigProvider>
|
||||||
|
)
|
||||||
57
src/pages/home/index.tsx
Normal file
57
src/pages/home/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Card, Row, Col, Typography, Space } from 'antd'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { RobotOutlined, TranslationOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
|
const { Title, Paragraph } = Typography
|
||||||
|
|
||||||
|
const cards = [
|
||||||
|
{
|
||||||
|
title: '测试页面 1',
|
||||||
|
description: 'Dify AI Agent 集成示例',
|
||||||
|
icon: <RobotOutlined style={{ fontSize: 32, color: '#1890ff' }} />,
|
||||||
|
link: '/test1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '测试页面 2',
|
||||||
|
description: '功能开发中...',
|
||||||
|
icon: <FileTextOutlined style={{ fontSize: 32, color: '#52c41a' }} />,
|
||||||
|
link: '/test2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '中英翻译器',
|
||||||
|
description: 'AI 驱动的翻译工具',
|
||||||
|
icon: <TranslationOutlined style={{ fontSize: 32, color: '#722ed1' }} />,
|
||||||
|
link: '/zh-en-translator'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const HomePage: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 24 }}>
|
||||||
|
<Title level={2}>欢迎使用 AI 办公助手</Title>
|
||||||
|
<Paragraph style={{ marginBottom: 32 }}>
|
||||||
|
选择下方功能开始使用,或通过顶部导航访问其他页面。
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{cards.map((card, index) => (
|
||||||
|
<Col xs={24} sm={12} md={8} key={index}>
|
||||||
|
<Link to={card.link}>
|
||||||
|
<Card hoverable style={{ height: '100%' }}>
|
||||||
|
<Space direction='vertical' align='center' style={{ width: '100%' }}>
|
||||||
|
{card.icon}
|
||||||
|
<Title level={4} style={{ marginBottom: 0 }}>{card.title}</Title>
|
||||||
|
<Paragraph type='secondary' style={{ marginBottom: 0 }}>
|
||||||
|
{card.description}
|
||||||
|
</Paragraph>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage
|
||||||
81
src/pages/test1/index.tsx
Normal file
81
src/pages/test1/index.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { AgUiEventType, DifyAgent } from '@shangzy/ag-ui-dify'
|
||||||
|
import { RunAgentInput, EventType, TextMessageContentEvent } from '@ag-ui/client'
|
||||||
|
import { cacheGet, getUserInfo } from '@/utils/cacheUtil'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
const Test1: React.FC = () => {
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const userInfo = getUserInfo()
|
||||||
|
const runAgent = () => {
|
||||||
|
const difyJson = {
|
||||||
|
conversation_id: '',
|
||||||
|
files: [],
|
||||||
|
query: '1312313',
|
||||||
|
appKey: cacheGet('appKey'),
|
||||||
|
inputs: {
|
||||||
|
Token: cacheGet('token'),
|
||||||
|
tenantid: cacheGet('tenantId')
|
||||||
|
},
|
||||||
|
user: userInfo?.id || 'anonymous'
|
||||||
|
}
|
||||||
|
const content: string = JSON.stringify(difyJson)
|
||||||
|
// 准备输入参数
|
||||||
|
const input: RunAgentInput = {
|
||||||
|
threadId: new Date().getTime().toString(),
|
||||||
|
runId: new Date().getTime().toString(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: new Date().getTime().toString(),
|
||||||
|
role: 'user',
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
],
|
||||||
|
context: [],
|
||||||
|
tools: []
|
||||||
|
}
|
||||||
|
// 订阅Agent事件
|
||||||
|
new DifyAgent({
|
||||||
|
baseUrl: '/dify',
|
||||||
|
showMetadata: true
|
||||||
|
})
|
||||||
|
.run(input)
|
||||||
|
.subscribe({
|
||||||
|
next: (event: AgUiEventType) => {
|
||||||
|
console.log('🚀 ~ ChatApp ~ aa ~ event:', event)
|
||||||
|
try {
|
||||||
|
switch (event.type) {
|
||||||
|
case EventType.RUN_STARTED:
|
||||||
|
// 可以在这里处理运行开始事件
|
||||||
|
break
|
||||||
|
|
||||||
|
case EventType.TEXT_MESSAGE_START:
|
||||||
|
// 处理消息开始事件
|
||||||
|
break
|
||||||
|
|
||||||
|
case EventType.TEXT_MESSAGE_CONTENT:
|
||||||
|
const textEvent = event as TextMessageContentEvent
|
||||||
|
setMessage(prev => prev + textEvent.delta)
|
||||||
|
break
|
||||||
|
|
||||||
|
case EventType.TEXT_MESSAGE_END:
|
||||||
|
break
|
||||||
|
|
||||||
|
case EventType.RUN_FINISHED:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('处理事件时出错:', err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {},
|
||||||
|
complete: () => {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div onClick={() => runAgent()}>发起请求</div>
|
||||||
|
<div>{message}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Test1
|
||||||
158
src/pages/test2/index.tsx
Normal file
158
src/pages/test2/index.tsx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { cacheGet, getUserInfo } from '@/utils/cacheUtil'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import { CopilotKit, useCopilotChatInternal as useCopilotChat } from '@copilotkit/react-core'
|
||||||
|
import { Flex, Mentions, Spin } from 'antd'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
const Chat: React.FC = () => {
|
||||||
|
const [currentAppKey, setCurrentAppKey] = useState<string>('')
|
||||||
|
const [newMessage, setNewMessage] = useState('')
|
||||||
|
const { messages, sendMessage, setMessages, isLoading, reloadMessages, stopGeneration } = useCopilotChat()
|
||||||
|
const callSendMessage = useCallback(
|
||||||
|
async (message: string) => {
|
||||||
|
await sendMessage({
|
||||||
|
id: new Date().getTime() + '',
|
||||||
|
role: 'user',
|
||||||
|
content: message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[sendMessage]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSendMessage = useCallback(() => {
|
||||||
|
// 提前存好本次的提问内容,重新生成的话直接从缓存中获取之前的提问内容
|
||||||
|
let question: string = newMessage || ''
|
||||||
|
const token = cacheGet('token')
|
||||||
|
const tenantId = cacheGet('tenantId')
|
||||||
|
let conversation_id = ''
|
||||||
|
if (messages[1]?.id) {
|
||||||
|
conversation_id = messages[1]?.id.split('_')[0]
|
||||||
|
}
|
||||||
|
const userInfo = getUserInfo()
|
||||||
|
const difyJson = {
|
||||||
|
inputs: {
|
||||||
|
Token: token || '',
|
||||||
|
tenantid: tenantId || '',
|
||||||
|
query: question
|
||||||
|
},
|
||||||
|
appKey: currentAppKey,
|
||||||
|
files: [],
|
||||||
|
user: userInfo?.id || 'anonymous',
|
||||||
|
query: question,
|
||||||
|
conversation_id
|
||||||
|
}
|
||||||
|
// 设置好目前状态下的聊天列表数据,包含之前已经结束的沟通内容,以及本次用户的提问,本次AI的回答占位
|
||||||
|
callSendMessage(JSON.stringify(difyJson))
|
||||||
|
setNewMessage('')
|
||||||
|
}, [callSendMessage, newMessage, currentAppKey])
|
||||||
|
|
||||||
|
const handleParse = (jsonStr: string) => {
|
||||||
|
let res = ''
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(jsonStr)
|
||||||
|
res = parsed?.query
|
||||||
|
} catch (e) {}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换行
|
||||||
|
const handleNewline = () => {
|
||||||
|
setNewMessage(prevValue => `${prevValue}\n`)
|
||||||
|
}
|
||||||
|
const handleKeyDown = async (e: any) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const res = await cacheGet('sendMessage')
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
if (newMessage) {
|
||||||
|
if (res === 'ctrlEnter') {
|
||||||
|
handleSendMessage()
|
||||||
|
} else {
|
||||||
|
handleNewline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (res === 'Enter' || !res) {
|
||||||
|
if (newMessage) {
|
||||||
|
handleSendMessage()
|
||||||
|
} else {
|
||||||
|
setNewMessage('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{messages.map((message, index) => {
|
||||||
|
const userMessageId = message.id
|
||||||
|
// 实际的会话消息
|
||||||
|
return (
|
||||||
|
<Flex key={index} vertical data-message-id={userMessageId}>
|
||||||
|
{/* 用户提问 */}
|
||||||
|
{message.role === 'user' && <Flex key={message.id}>{handleParse(message.content as string) ?? ''}</Flex>}
|
||||||
|
|
||||||
|
{message.role === 'assistant' && (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 'calc(100% - 20px)',
|
||||||
|
marginLeft: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex>
|
||||||
|
{isLoading && !message.content && index === messages.length - 1 && (
|
||||||
|
<Flex>
|
||||||
|
<Spin indicator={<LoadingOutlined />} />
|
||||||
|
<span>检索中</span>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{message.content ?? ''}
|
||||||
|
</Flex>
|
||||||
|
{message?.generativeUI?.()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<div className='p-4'>
|
||||||
|
<Mentions
|
||||||
|
autoFocus
|
||||||
|
open={false}
|
||||||
|
placeholder='请输入内容'
|
||||||
|
rows={4}
|
||||||
|
value={newMessage}
|
||||||
|
maxLength={10000}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
options={[]}
|
||||||
|
onInput={e => {
|
||||||
|
const value = (e.target as HTMLInputElement).value
|
||||||
|
// 检查内容是否只包含空格或回车符
|
||||||
|
if (/^\s*$/.test(value)) {
|
||||||
|
setNewMessage('') // 如果只包含空格或回车符,清空输入框
|
||||||
|
} else {
|
||||||
|
setNewMessage(value) // 否则更新输入内容
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Test2: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<CopilotKit
|
||||||
|
runtimeUrl='/agui-api/copilotkit/dify'
|
||||||
|
showDevConsole={false}
|
||||||
|
// publicApiKey={'ck_pub_cc922145a5da9b8513bc10df473cd6f7'}
|
||||||
|
agent='agentic_chat_metadata'
|
||||||
|
>
|
||||||
|
<Chat />
|
||||||
|
</CopilotKit>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Test2
|
||||||
216
src/pages/zh-en-translator/index.tsx
Normal file
216
src/pages/zh-en-translator/index.tsx
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Button, Card, Form, Input, message, Space } from 'antd'
|
||||||
|
import { PageHeader } from '@ant-design/pro-components'
|
||||||
|
import { translateChineseToEnglish } from '@/api/zh-en-translator'
|
||||||
|
|
||||||
|
const ZhEnTranslator = () => {
|
||||||
|
const [form] = Form.useForm()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [translationResult, setTranslationResult] = useState('')
|
||||||
|
const [currentInput, setCurrentInput] = useState('')
|
||||||
|
const maxChars = 5000
|
||||||
|
|
||||||
|
const handleTranslate = async () => {
|
||||||
|
try {
|
||||||
|
const source_content = form.getFieldValue('source_content') || ''
|
||||||
|
if (!source_content.trim()) {
|
||||||
|
message.warning('请输入要翻译的中文内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTranslationResult('')
|
||||||
|
|
||||||
|
const response = await translateChineseToEnglish(source_content)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`翻译请求失败: ${response.status} ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.body) {
|
||||||
|
throw new Error('响应体为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
let buffer = ''
|
||||||
|
let fullContent = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
const lines = buffer.split('\n')
|
||||||
|
buffer = lines.pop() || ''
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
|
||||||
|
if (trimmedLine === 'data: [DONE]') {
|
||||||
|
message.success('翻译完成')
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedLine.startsWith('data: ')) {
|
||||||
|
try {
|
||||||
|
const data = trimmedLine.slice(6)
|
||||||
|
const parsed = JSON.parse(data)
|
||||||
|
|
||||||
|
if (parsed.event === 'message' && parsed.answer) {
|
||||||
|
fullContent += parsed.answer
|
||||||
|
setTranslationResult(fullContent)
|
||||||
|
} else if (parsed.event === 'error') {
|
||||||
|
throw new Error(parsed.message || 'Dify API 返回错误')
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
console.warn('跳过无法解析的行:', trimmedLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullContent) {
|
||||||
|
message.success('翻译完成')
|
||||||
|
} else {
|
||||||
|
throw new Error('未收到翻译结果')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('翻译错误:', error)
|
||||||
|
message.error(error instanceof Error ? error.message : '翻译失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
form.resetFields()
|
||||||
|
setTranslationResult('')
|
||||||
|
setCurrentInput('')
|
||||||
|
message.info('已清空内容')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (!translationResult) {
|
||||||
|
message.warning('没有可复制的内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(translationResult)
|
||||||
|
message.success('复制成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败:', error)
|
||||||
|
message.error('复制失败,请手动复制')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px' }}>
|
||||||
|
<PageHeader
|
||||||
|
title="中文转英文翻译助手"
|
||||||
|
subTitle="专业的中文到英文文本翻译工具,支持流式输出,翻译结果地道自然"
|
||||||
|
style={{ marginBottom: '30px' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||||
|
<Card title="参数输入区">
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
name="source_content"
|
||||||
|
label={
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
||||||
|
<span>中文原文</span>
|
||||||
|
<span style={{ color: currentInput.length > maxChars ? '#ff4d4f' : '#999' }}>
|
||||||
|
{currentInput.length} / {maxChars}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入要翻译的中文内容' },
|
||||||
|
{ max: maxChars, message: `输入内容不能超过${maxChars}个字符` }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="请输入需要翻译的中文内容(最多5000个字符)"
|
||||||
|
rows={8}
|
||||||
|
value={currentInput}
|
||||||
|
onChange={(e) => setCurrentInput(e.target.value)}
|
||||||
|
showCount
|
||||||
|
maxLength={maxChars}
|
||||||
|
style={{ fontSize: '14px' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'center' }} size="middle">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
onClick={handleTranslate}
|
||||||
|
loading={loading}
|
||||||
|
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||||||
|
>
|
||||||
|
翻译
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
onClick={handleClear}
|
||||||
|
disabled={loading}
|
||||||
|
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title="翻译结果"
|
||||||
|
extra={
|
||||||
|
translationResult && (
|
||||||
|
<Button onClick={handleCopy} size="small">
|
||||||
|
复制
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minHeight: '150px',
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: '15px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '4px',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: '1.6'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
|
||||||
|
正在翻译中,请稍候...
|
||||||
|
</div>
|
||||||
|
) : translationResult || (
|
||||||
|
<div style={{ textAlign: 'center', color: '#999' }}>
|
||||||
|
翻译结果将在这里显示
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ZhEnTranslator
|
||||||
59
src/router/index.tsx
Normal file
59
src/router/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Spin } from 'antd'
|
||||||
|
import { FC, ReactNode, Suspense, lazy } from 'react'
|
||||||
|
import { Navigate, RouteObject } from 'react-router-dom'
|
||||||
|
import { createHashRouter } from 'react-router-dom'
|
||||||
|
|
||||||
|
const Loading = () => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size='large' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const LazyLoad = (Component: FC): ReactNode => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<Component />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const router: RouteObject[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/home')))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/test1',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/test1')))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/test2',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/test2')))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/zh-en-translator',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/zh-en-translator')))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
element: <>404</>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
element: <Navigate to='/404' />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default createHashRouter(router)
|
||||||
43
src/utils/cacheUtil.ts
Normal file
43
src/utils/cacheUtil.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export function cacheGet(key: string): string {
|
||||||
|
return localStorage.getItem(key) || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cacheSet(key: string, value: string) {
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cacheClear(key: string) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cacheDeleteAll() {
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id?: string
|
||||||
|
avatar?: string
|
||||||
|
gender?: string
|
||||||
|
nickName?: string
|
||||||
|
username?: string
|
||||||
|
position?: string
|
||||||
|
deptName?: string
|
||||||
|
mobile?: string
|
||||||
|
bizMail?: string
|
||||||
|
email?: string
|
||||||
|
corpName?: string
|
||||||
|
effectivePoint?: string
|
||||||
|
totalPoint?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserInfo(): UserInfo | null {
|
||||||
|
const res = localStorage.getItem('userInfo')
|
||||||
|
if (!res) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return JSON.parse(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getToken() {
|
||||||
|
return localStorage.getItem('token')
|
||||||
|
}
|
||||||
8
src/utils/polyfills.ts
Normal file
8
src/utils/polyfills.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
if (!Object.hasOwn) {
|
||||||
|
Object.defineProperty(Object, 'hasOwn', {
|
||||||
|
value: function(obj: any, prop: string): boolean {
|
||||||
|
return Object.prototype.hasOwnProperty.call(obj, prop)
|
||||||
|
},
|
||||||
|
configurable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
2
src/vite-env.d.ts
vendored
Normal file
2
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
declare module '@tailwindcss/vite'
|
||||||
5
tailwind.config.js
Normal file
5
tailwind.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
|
darkMode: 'class',
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
29
tsconfig.app.json
Normal file
29
tsconfig.app.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "src/api/.ts"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
28
tsconfig.node.json
Normal file
28
tsconfig.node.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
33
vite.config.ts
Normal file
33
vite.config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig(() => {
|
||||||
|
return {
|
||||||
|
base: './',
|
||||||
|
plugins: [tailwindcss(), react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['dayjs/locale/zh-cn']
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: true as true,
|
||||||
|
cors: true,
|
||||||
|
open: true,
|
||||||
|
port: 5173
|
||||||
|
},
|
||||||
|
// 生产预览服务器配置 (vite preview) - 仅用于本地测试
|
||||||
|
preview: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 3000,
|
||||||
|
cors: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user