Skip to content

Commit 2a7d83d

Browse files
author
shijiashuai
committed
```
feat: 添加 GitHub Actions CI 工作流并优化 ESLint 配置与代码规范 新增 `.github/workflows/ci.yml` 文件,配置前后端 CI 流水线:前端执行 lint 和测试,后端执行依赖安装和编译检查。在 `server/app/main.py` 中添加 CORS 中间件,支持通过环境变量 `CORS_ALLOW_ORIGINS` 配置允许的跨域来源,默认包含本地开发端口。 重构 `eslint.config.js`,从 `typescript-eslint` 迁移到扁平化配置格式,使用 `@typescript-eslint/parser
1 parent ada0904 commit 2a7d83d

16 files changed

Lines changed: 364 additions & 166 deletions

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- master
8+
pull_request:
9+
10+
jobs:
11+
frontend:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: "18"
21+
cache: npm
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Lint
27+
run: npm run lint
28+
29+
- name: Test
30+
run: npm run test:run
31+
32+
backend:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v4
37+
38+
- name: Setup Python
39+
uses: actions/setup-python@v5
40+
with:
41+
python-version: "3.11"
42+
cache: pip
43+
cache-dependency-path: server/requirements.txt
44+
45+
- name: Install backend dependencies
46+
run: python -m pip install -r server/requirements.txt
47+
48+
- name: Compile backend
49+
run: python -m compileall server/app

eslint.config.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,53 @@ import js from '@eslint/js'
22
import globals from 'globals'
33
import reactHooks from 'eslint-plugin-react-hooks'
44
import reactRefresh from 'eslint-plugin-react-refresh'
5-
import tseslint from 'typescript-eslint'
5+
import tsParser from '@typescript-eslint/parser'
6+
import tsPlugin from '@typescript-eslint/eslint-plugin'
67

7-
export default tseslint.config(
8-
{ ignores: ['dist'] },
8+
export default [
9+
{ ignores: ['dist', 'dist-*', 'coverage'] },
10+
js.configs.recommended,
911
{
10-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
1112
files: ['**/*.{ts,tsx}'],
1213
languageOptions: {
13-
ecmaVersion: 2020,
14+
parser: tsParser,
15+
parserOptions: {
16+
ecmaVersion: 2020,
17+
sourceType: 'module',
18+
ecmaFeatures: {
19+
jsx: true,
20+
},
21+
},
1422
globals: globals.browser,
1523
},
1624
plugins: {
25+
'@typescript-eslint': tsPlugin,
1726
'react-hooks': reactHooks,
1827
'react-refresh': reactRefresh,
1928
},
2029
rules: {
30+
...tsPlugin.configs.recommended.rules,
2131
...reactHooks.configs.recommended.rules,
32+
'no-undef': 'off',
33+
'no-empty': ['error', { allowEmptyCatch: true }],
34+
'@typescript-eslint/no-unused-vars': [
35+
'error',
36+
{
37+
argsIgnorePattern: '^_',
38+
varsIgnorePattern: '^_',
39+
caughtErrorsIgnorePattern: '^_',
40+
},
41+
],
2242
'react-refresh/only-export-components': [
2343
'warn',
2444
{ allowConstantExport: true },
2545
],
2646
},
2747
},
28-
)
48+
{
49+
files: ['src/__tests__/**/*.{ts,tsx}', '**/*.test.{ts,tsx}'],
50+
rules: {
51+
'@typescript-eslint/no-explicit-any': 'off',
52+
},
53+
},
54+
]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc && vite build",
9-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
9+
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
1010
"preview": "vite preview",
1111
"build:mobile": "tsc && vite build --mode mobile",
1212
"build:desktop": "tsc && vite build --mode desktop",

server/app/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
1+
import os
2+
13
from fastapi import FastAPI
4+
from fastapi.middleware.cors import CORSMiddleware
25
from app.api.chat import router as chat_router
36

47
app = FastAPI(title="Digital Human Service")
58

9+
origins_env = os.getenv("CORS_ALLOW_ORIGINS", "")
10+
if origins_env:
11+
allowed_origins = [origin.strip() for origin in origins_env.split(",") if origin.strip()]
12+
else:
13+
allowed_origins = [
14+
"http://localhost:5173",
15+
"http://127.0.0.1:5173",
16+
"http://localhost:3000",
17+
"http://127.0.0.1:3000",
18+
]
19+
20+
app.add_middleware(
21+
CORSMiddleware,
22+
allow_origins=allowed_origins,
23+
allow_credentials=True,
24+
allow_methods=["*"],
25+
allow_headers=["*"],
26+
)
27+
628

729
@app.get("/health")
830
async def health() -> dict:

src/__tests__/digitalHuman.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ describe('DigitalHumanStore', () => {
233233
});
234234

235235
it('handles play action', () => {
236-
const { play, isPlaying } = useDigitalHumanStore.getState();
236+
const { play } = useDigitalHumanStore.getState();
237237
play();
238238
expect(useDigitalHumanStore.getState().isPlaying).toBe(true);
239239
});
@@ -247,7 +247,7 @@ describe('DigitalHumanStore', () => {
247247
});
248248

249249
it('handles reset action', () => {
250-
const { play, reset, isPlaying } = useDigitalHumanStore.getState();
250+
const { play, reset } = useDigitalHumanStore.getState();
251251
play();
252252
reset();
253253
expect(useDigitalHumanStore.getState().isPlaying).toBe(false);
@@ -257,7 +257,7 @@ describe('DigitalHumanStore', () => {
257257
});
258258

259259
it('handles recording toggle', () => {
260-
const { startRecording, isRecording } = useDigitalHumanStore.getState();
260+
const { startRecording } = useDigitalHumanStore.getState();
261261
startRecording();
262262
expect(useDigitalHumanStore.getState().isRecording).toBe(true);
263263
});
@@ -410,7 +410,7 @@ describe('Performance Tests', () => {
410410
});
411411

412412
it('handles rapid state changes efficiently', () => {
413-
const { play, pause, play: playAgain } = useDigitalHumanStore.getState();
413+
const { play, pause } = useDigitalHumanStore.getState();
414414

415415
const startTime = performance.now();
416416
for (let i = 0; i < 100; i++) {

src/components/BehaviorControlPanel.tsx

Lines changed: 72 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import { Activity, Brain, Zap, Target, Clock, TrendingUp } from 'lucide-react';
33

44
interface BehaviorState {
@@ -9,9 +9,11 @@ interface BehaviorState {
99
goal: string;
1010
}
1111

12+
type BehaviorParameters = Record<string, unknown>;
13+
1214
interface BehaviorControlPanelProps {
1315
currentBehavior: string;
14-
onBehaviorChange: (behavior: string, parameters: any) => void;
16+
onBehaviorChange: (behavior: string, parameters: BehaviorParameters) => void;
1517
}
1618

1719
export default function BehaviorControlPanel({ currentBehavior, onBehaviorChange }: BehaviorControlPanelProps) {
@@ -24,73 +26,66 @@ export default function BehaviorControlPanel({ currentBehavior, onBehaviorChange
2426
});
2527

2628
const [isAutoMode, setIsAutoMode] = useState(false);
27-
const [decisionInterval, setDecisionInterval] = useState(3000);
28-
const [learningRate, setLearningRate] = useState(0.1);
29-
30-
const behaviors = [
31-
{
32-
name: 'idle',
33-
label: 'Idle',
34-
icon: <Clock size={20} />,
35-
color: 'text-gray-400',
36-
description: 'Basic standby loop',
37-
parameters: { idleTime: 5000, breathing: true }
38-
},
39-
{
40-
name: 'greeting',
41-
label: 'Greet',
42-
icon: <Target size={20} />,
43-
color: 'text-green-400',
44-
description: 'Friendly wave & smile',
45-
parameters: { wave: true, smile: true, duration: 3000 }
46-
},
47-
{
48-
name: 'listening',
49-
label: 'Listen',
50-
icon: <Brain size={20} />,
51-
color: 'text-blue-400',
52-
description: 'Active attention focus',
53-
parameters: { headNod: true, eyeContact: true, attention: 0.9 }
54-
},
55-
{
56-
name: 'thinking',
57-
label: 'Think',
58-
icon: <Activity size={20} />,
59-
color: 'text-yellow-400',
60-
description: 'Processing animation',
61-
parameters: { headTilt: true, pause: true, processing: true }
62-
},
63-
{
64-
name: 'speaking',
65-
label: 'Speak',
66-
icon: <TrendingUp size={20} />,
67-
color: 'text-purple-400',
68-
description: 'Active conversation',
69-
parameters: { mouthMove: true, gestures: true, emphasis: 0.8 }
70-
},
71-
{
72-
name: 'excited',
73-
label: 'Excite',
74-
icon: <Zap size={20} />,
75-
color: 'text-orange-400',
76-
description: 'High energy state',
77-
parameters: { energy: 0.9, movement: true, animation: 'bounce' }
78-
}
79-
];
80-
81-
// Auto Decision Mock
82-
useEffect(() => {
83-
if (!isAutoMode) return;
84-
const interval = setInterval(() => {
85-
makeAutoDecision();
86-
}, decisionInterval);
87-
return () => clearInterval(interval);
88-
}, [isAutoMode, decisionInterval]);
29+
const decisionInterval = 3000;
30+
31+
const behaviors = useMemo(
32+
() => [
33+
{
34+
name: 'idle',
35+
label: 'Idle',
36+
icon: <Clock size={20} />,
37+
color: 'text-gray-400',
38+
description: 'Basic standby loop',
39+
parameters: { idleTime: 5000, breathing: true },
40+
},
41+
{
42+
name: 'greeting',
43+
label: 'Greet',
44+
icon: <Target size={20} />,
45+
color: 'text-green-400',
46+
description: 'Friendly wave & smile',
47+
parameters: { wave: true, smile: true, duration: 3000 },
48+
},
49+
{
50+
name: 'listening',
51+
label: 'Listen',
52+
icon: <Brain size={20} />,
53+
color: 'text-blue-400',
54+
description: 'Active attention focus',
55+
parameters: { headNod: true, eyeContact: true, attention: 0.9 },
56+
},
57+
{
58+
name: 'thinking',
59+
label: 'Think',
60+
icon: <Activity size={20} />,
61+
color: 'text-yellow-400',
62+
description: 'Processing animation',
63+
parameters: { headTilt: true, pause: true, processing: true },
64+
},
65+
{
66+
name: 'speaking',
67+
label: 'Speak',
68+
icon: <TrendingUp size={20} />,
69+
color: 'text-purple-400',
70+
description: 'Active conversation',
71+
parameters: { mouthMove: true, gestures: true, emphasis: 0.8 },
72+
},
73+
{
74+
name: 'excited',
75+
label: 'Excite',
76+
icon: <Zap size={20} />,
77+
color: 'text-orange-400',
78+
description: 'High energy state',
79+
parameters: { energy: 0.9, movement: true, animation: 'bounce' },
80+
},
81+
],
82+
[]
83+
);
8984

90-
const makeAutoDecision = () => {
85+
const makeAutoDecision = useCallback(() => {
9186
const now = new Date();
9287
let newBehavior = 'idle';
93-
let newParameters = {};
88+
let newParameters: BehaviorParameters = {};
9489
let newConfidence = 0.7;
9590

9691
if (Math.random() > 0.7) {
@@ -113,9 +108,18 @@ export default function BehaviorControlPanel({ currentBehavior, onBehaviorChange
113108
});
114109

115110
onBehaviorChange(newBehavior, newParameters);
116-
};
111+
}, [behaviors, onBehaviorChange]);
112+
113+
// Auto Decision Mock
114+
useEffect(() => {
115+
if (!isAutoMode) return;
116+
const interval = setInterval(() => {
117+
makeAutoDecision();
118+
}, decisionInterval);
119+
return () => clearInterval(interval);
120+
}, [decisionInterval, isAutoMode, makeAutoDecision]);
117121

118-
const handleBehaviorClick = (behaviorName: string, parameters: any) => {
122+
const handleBehaviorClick = (behaviorName: string, parameters: BehaviorParameters) => {
119123
const behavior = behaviors.find(b => b.name === behaviorName);
120124
if (!behavior) return;
121125

0 commit comments

Comments
 (0)