del:删除了HTTP服务器 and 删除了自动化加分的json显示

This commit is contained in:
NanGua-QWQ
2026-02-28 20:32:46 +08:00
parent efa78f2bce
commit fa391041e7
11 changed files with 25 additions and 639 deletions
-1
View File
@@ -31,7 +31,6 @@
"mica-electron": "^1.5.16", "mica-electron": "^1.5.16",
"os": "^0.1.2", "os": "^0.1.2",
"pinyin-pro": "^3.27.0", "pinyin-pro": "^3.27.0",
"prismjs": "^1.30.0",
"react-router-dom": "^6.28.0", "react-router-dom": "^6.28.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"tdesign-react": "^1.16.3", "tdesign-react": "^1.16.3",
-9
View File
@@ -33,9 +33,6 @@ importers:
pinyin-pro: pinyin-pro:
specifier: ^3.27.0 specifier: ^3.27.0
version: 3.27.0 version: 3.27.0
prismjs:
specifier: ^1.30.0
version: 1.30.0
react-router-dom: react-router-dom:
specifier: ^6.28.0 specifier: ^6.28.0
version: 6.30.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 6.30.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2636,10 +2633,6 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
proc-log@5.0.0: proc-log@5.0.0:
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
engines: {node: ^18.17.0 || >=20.5.0} engines: {node: ^18.17.0 || >=20.5.0}
@@ -6179,8 +6172,6 @@ snapshots:
prettier@3.7.4: {} prettier@3.7.4: {}
prismjs@1.30.0: {}
proc-log@5.0.0: {} proc-log@5.0.0: {}
progress@2.0.3: {} progress@2.0.3: {}
-1
View File
@@ -17,7 +17,6 @@ export {
WindowManagerToken, WindowManagerToken,
TrayServiceToken, TrayServiceToken,
AutoScoreServiceToken, AutoScoreServiceToken,
HttpServerServiceToken,
FileSystemServiceToken FileSystemServiceToken
} from './tokens' } from './tokens'
export type { export type {
-1
View File
@@ -15,5 +15,4 @@ export const ThemeServiceToken = Symbol.for('secscore.themeService')
export const WindowManagerToken = Symbol.for('secscore.windowManager') export const WindowManagerToken = Symbol.for('secscore.windowManager')
export const TrayServiceToken = Symbol.for('secscore.trayService') export const TrayServiceToken = Symbol.for('secscore.trayService')
export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService') export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService')
export const HttpServerServiceToken = Symbol.for('secscore.httpServerService')
export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService') export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService')
-7
View File
@@ -13,7 +13,6 @@ import { PermissionService } from './services/PermissionService'
import { AuthService } from './services/AuthService' import { AuthService } from './services/AuthService'
import { DataService } from './services/DataService' import { DataService } from './services/DataService'
import { ThemeService } from './services/ThemeService' import { ThemeService } from './services/ThemeService'
import { HttpServerService } from './services/HttpServerService'
import { WindowManager, type windowManagerOptions } from './services/WindowManager' import { WindowManager, type windowManagerOptions } from './services/WindowManager'
import { TrayService } from './services/TrayService' import { TrayService } from './services/TrayService'
import { AutoScoreService } from './services/AutoScoreService' import { AutoScoreService } from './services/AutoScoreService'
@@ -40,7 +39,6 @@ import {
WindowManagerToken, WindowManagerToken,
TrayServiceToken, TrayServiceToken,
AutoScoreServiceToken, AutoScoreServiceToken,
HttpServerServiceToken,
FileSystemServiceToken FileSystemServiceToken
} from './hosting' } from './hosting'
@@ -274,10 +272,6 @@ app.whenReady().then(async () => {
(p) => new FileSystemService(p.get(MainContext), config.configDir) (p) => new FileSystemService(p.get(MainContext), config.configDir)
) )
services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext))) services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext)))
services.addSingleton(
HttpServerServiceToken,
(p) => new HttpServerService(p.get(MainContext))
)
}) })
.configure(async (_builderContext, appCtx) => { .configure(async (_builderContext, appCtx) => {
const services = appCtx.services const services = appCtx.services
@@ -303,7 +297,6 @@ app.whenReady().then(async () => {
const tray = services.get(TrayServiceToken) as TrayService const tray = services.get(TrayServiceToken) as TrayService
tray.initialize() tray.initialize()
} }
services.get(HttpServerServiceToken)
services.get(FileSystemServiceToken) services.get(FileSystemServiceToken)
const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService
await autoScore.initialize?.() await autoScore.initialize?.()
+19
View File
@@ -619,6 +619,25 @@ export class AutoScoreService extends Service {
return true return true
} }
async sortRules(ruleIds: number[]): Promise<boolean> {
const ruleMap = new Map(this.rules.map((r) => [r.id, r]))
const sortedRules: AutoScoreRule[] = []
for (const id of ruleIds) {
const rule = ruleMap.get(id)
if (rule) {
sortedRules.push(rule)
}
}
for (const rule of this.rules) {
if (!ruleIds.includes(rule.id)) {
sortedRules.push(rule)
}
}
this.rules = sortedRules
await this.saveRulesToFile()
return true
}
getRules(): AutoScoreRule[] { getRules(): AutoScoreRule[] {
return [...this.rules] return [...this.rules]
} }
-359
View File
@@ -1,359 +0,0 @@
import { Service } from '../../shared/kernel'
import { MainContext } from '../context'
import express, { type Express, type Request, type Response, type NextFunction } from 'express'
import { StudentEntity } from '../db/entities'
import net from 'net'
declare module '../../shared/kernel' {
interface Context {
httpServer: HttpServerService
}
}
export interface HttpServerConfig {
port: number
host: string
corsOrigin?: string
}
export class HttpServerService extends Service {
private app: Express | null = null
private server: any = null
private config: HttpServerConfig = {
port: 3000,
host: 'localhost',
corsOrigin: '*' // 默认允许所有跨域请求
}
constructor(ctx: MainContext) {
super(ctx, 'httpServer')
this.registerIpc()
}
private get mainCtx() {
return this.ctx as MainContext
}
private get permissions() {
return this.ctx.permissions
}
private registerIpc() {
// 启动HTTP服务器
this.mainCtx.handle('http:server:start', async (event, config?: Partial<HttpServerConfig>) => {
if (!this.permissions.requirePermission(event, 'admin')) {
return { success: false, message: 'Permission denied' }
}
try {
await this.start(config)
return {
success: true,
data: {
url: `http://${this.config.host}:${this.config.port}`,
config: this.config
}
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return { success: false, message: `Failed to start HTTP server: ${message}` }
}
})
// 停止HTTP服务器
this.mainCtx.handle('http:server:stop', async (event) => {
if (!this.permissions.requirePermission(event, 'admin')) {
return { success: false, message: 'Permission denied' }
}
try {
await this.stop()
return { success: true }
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return { success: false, message: `Failed to stop HTTP server: ${message}` }
}
})
// 获取HTTP服务器状态
this.mainCtx.handle('http:server:status', async (event) => {
if (!this.permissions.requirePermission(event, 'admin')) {
return { success: false, message: 'Permission denied' }
}
return {
success: true,
data: {
isRunning: this.isRunning(),
config: this.config,
url: this.isRunning() ? `http://${this.config.host}:${this.config.port}` : null
}
}
})
}
async start(config?: Partial<HttpServerConfig>): Promise<void> {
if (this.isRunning()) {
throw new Error('HTTP server is already running')
}
// 合并配置
this.config = { ...this.config, ...config }
// 检查端口是否可用,如果不可用则自动寻找可用端口
const originalPort = this.config.port
let attempts = 0
const maxAttempts = 100 // 最多尝试100个端口
while (attempts < maxAttempts) {
if (await this.isPortAvailable(this.config.port)) {
break
}
// 端口被占用,尝试下一个端口
this.config.port++
attempts++
}
if (attempts === maxAttempts) {
throw new Error(
`Could not find available port after ${maxAttempts} attempts starting from ${originalPort}`
)
}
if (attempts > 0) {
this.mainCtx.logger.warn(
`Original port ${originalPort} was unavailable. Using port ${this.config.port} instead.`
)
}
// 创建Express应用
this.app = express()
// 配置中间件
this.app.use(express.json())
this.app.use(express.urlencoded({ extended: true }))
// 简单的CORS处理
this.app.use((_: Request, res: Response, next: NextFunction) => {
res.header('Access-Control-Allow-Origin', this.config.corsOrigin || '*')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
next()
})
// 设置路由
this.setupRoutes()
// 启动服务器
return new Promise((resolve, reject) => {
this.server = this.app!.listen(this.config.port, this.config.host, () => {
this.mainCtx.logger.info(
`HTTP server started at http://${this.config.host}:${this.config.port}`
)
resolve()
})
this.server.on('error', (error: Error) => {
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
reject(error)
})
})
}
async stop(): Promise<void> {
if (!this.isRunning()) {
return
}
return new Promise((resolve, reject) => {
this.server!.close((error?: Error) => {
if (error) {
this.mainCtx.logger.error(`Failed to stop HTTP server: ${error.message}`)
reject(error)
} else {
this.mainCtx.logger.info('HTTP server stopped')
this.server = null
this.app = null
resolve()
}
})
})
}
isRunning(): boolean {
return this.server !== null && this.app !== null
}
private async isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = net.createServer()
server.listen(port, '127.0.0.1')
server.on('error', () => {
resolve(false)
})
server.on('listening', () => {
server.close(() => {
resolve(true)
})
})
})
}
private setupRoutes() {
if (!this.app) return
// 健康检查端点
this.app.get('/health', (_, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})
// 获取所有学生分数
this.app.get('/api/students/scores', async (_req: Request, res: Response) => {
try {
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
const students = await studentRepo.find({
select: ['id', 'name', 'score'],
order: { score: 'DESC', name: 'ASC' }
})
res.json({
success: true,
data: students.map((student) => ({
id: student.id,
name: student.name,
score: student.score
}))
})
} catch (error) {
this.mainCtx.logger.error(`Failed to fetch student scores: ${error}`)
res.status(500).json({
success: false,
message: 'Failed to fetch student scores'
})
}
})
// 获取单个学生分数
this.app.get('/api/students/:id/score', async (_req: Request, res: Response) => {
try {
const id = parseInt(_req.params.id)
if (isNaN(id)) {
return res.status(400).json({
success: false,
message: 'Invalid student ID'
})
}
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
const student = await studentRepo.findOne({
where: { id },
select: ['id', 'name', 'score']
})
if (!student) {
return res.status(404).json({
success: false,
message: 'Student not found'
})
}
res.json({
success: true,
data: {
id: student.id,
name: student.name,
score: student.score
}
})
} catch (error) {
this.mainCtx.logger.error(`Failed to fetch student score: ${error}`)
res.status(500).json({
success: false,
message: 'Failed to fetch student score'
})
}
})
// 按姓名搜索学生分数
this.app.get('/api/students/search', async (req: Request, res: Response) => {
try {
const name = req.query.name as string
if (!name || name.trim() === '') {
return res.status(400).json({
success: false,
message: 'Name parameter is required'
})
}
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
const students = await studentRepo.find({
where: { name },
select: ['id', 'name', 'score']
})
res.json({
success: true,
data: students.map((student) => ({
id: student.id,
name: student.name,
score: student.score
}))
})
} catch (error) {
this.mainCtx.logger.error(`Failed to search student: ${error}`)
res.status(500).json({
success: false,
message: 'Failed to search student'
})
}
})
// 获取排行榜(前N名学生)
this.app.get('/api/leaderboard', async (req: Request, res: Response) => {
try {
const limit = parseInt(req.query.limit as string) || 10
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
const students = await studentRepo.find({
select: ['id', 'name', 'score'],
order: { score: 'DESC', name: 'ASC' },
take: limit
})
res.json({
success: true,
data: students.map((student) => ({
id: student.id,
name: student.name,
score: student.score
}))
})
} catch (error) {
this.mainCtx.logger.error(`Failed to fetch leaderboard: ${error}`)
res.status(500).json({
success: false,
message: 'Failed to fetch leaderboard'
})
}
})
// stray expression removed
// 404处理
this.app.use((_req: Request, res: Response) => {
res.status(404).json({
success: false,
message: 'Endpoint not found'
})
})
// 错误处理中间件
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.app.use((error: Error, _: Request, res: Response, _next: NextFunction) => {
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
res.status(500).json({
success: false,
message: 'Internal server error'
})
})
}
}
@@ -18,7 +18,6 @@ import {
Select, Select,
TooltipLite TooltipLite
} from 'tdesign-react' } from 'tdesign-react'
import Code from './Code'
interface AutoScoreRule { interface AutoScoreRule {
id: number id: number
@@ -568,41 +567,6 @@ export const AutoScoreManager: React.FC = () => {
style={{ color: 'var(--ss-text-main)' }} style={{ color: 'var(--ss-text-main)' }}
/> />
</Card> </Card>
<Card style={{ marginBottom: '24px' }}>
<Code
code={(() => {
if (editingRuleId !== null) {
const values = form.getFieldsValue(true) as unknown as AutoScoreRuleFormValues
const studentNames = Array.isArray(values.studentNames) ? values.studentNames : []
const triggersPayload = triggerList.map((t) => ({
event: t.eventName,
value: t.value,
relation: t.relation
}))
const actionsPayload = actionList.map((a) => ({
event: a.eventName,
value: a.value,
reason: a.reason
}))
const currentRule = {
id: editingRuleId,
enabled: true,
name: values.name || '',
studentNames,
triggers: triggersPayload,
actions: actionsPayload
}
return JSON.stringify(currentRule, null, 2)
} else {
return JSON.stringify(rules, null, 2)
}
})()}
language={'json'}
/>
</Card>
</div> </div>
) )
} }
-38
View File
@@ -1,38 +0,0 @@
import { useEffect } from 'react'
import Prism from 'prismjs'
import { useTheme } from '../contexts/ThemeContext'
import 'prismjs/themes/prism-okaidia.min.css'
const Code = ({ code, language }) => {
const { currentTheme } = useTheme()
useEffect(() => {
// 重新高亮代码
Prism.highlightAll()
}, [currentTheme, code, language])
// 根据主题设置不同的类名
const isDark = currentTheme?.mode === 'dark'
return (
<div
style={{
opacity: '0.6',
// 根据主题动态设置样式
backgroundColor: isDark ? '#282c34' : '#f5f2f0',
color: isDark ? '#abb2bf' : '#383a42',
padding: '16px',
borderRadius: '8px',
overflow: 'auto'
}}
>
<pre className={isDark ? 'prism-okaidia' : 'prism-coy'}>
<code className={`language-${language}`}>{code}</code>
</pre>
</div>
)
}
export default Code
-187
View File
@@ -62,18 +62,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
const [settleDialogVisible, setSettleDialogVisible] = useState(false) const [settleDialogVisible, setSettleDialogVisible] = useState(false)
const [urlRegisterLoading, setUrlRegisterLoading] = useState(false) const [urlRegisterLoading, setUrlRegisterLoading] = useState(false)
// HTTP Server状态
const [httpServerStatus, setHttpServerStatus] = useState<{
isRunning: boolean
config: { port: number; host: string; corsOrigin?: string }
url: string | null
} | null>(null)
const [httpLoading, setHttpLoading] = useState(false)
const [httpPort, setHttpPort] = useState('3000')
const [httpHost, setHttpHost] = useState('localhost')
const [httpCorsOrigin, setHttpCorsOrigin] = useState('*')
const canAdmin = permission === 'admin' const canAdmin = permission === 'admin'
const permissionTag = useMemo(() => { const permissionTag = useMemo(() => {
@@ -103,7 +91,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
useEffect(() => { useEffect(() => {
loadAll() loadAll()
refreshHttpServerStatus()
if (!(window as any).api) return if (!(window as any).api) return
const unsubscribe = (window as any).api.onSettingChanged((change: any) => { const unsubscribe = (window as any).api.onSettingChanged((change: any) => {
setSettings((prev) => { setSettings((prev) => {
@@ -261,71 +248,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
setSettleDialogVisible(true) setSettleDialogVisible(true)
} }
// HTTP Server函数
const refreshHttpServerStatus = async () => {
if (!(window as any).api) return
try {
const res = await (window as any).api.httpServerStatus()
if (res.success && res.data) {
setHttpServerStatus(res.data)
setHttpPort(res.data.config.port.toString())
setHttpHost(res.data.config.host)
setHttpCorsOrigin(res.data.config.corsOrigin || '*')
}
} catch (error) {
console.error('Failed to refresh HTTP server status:', error)
}
}
const handleHttpServerStart = async () => {
if (!(window as any).api) return
setHttpLoading(true)
try {
const config: any = {}
const portNum = parseInt(httpPort)
if (!isNaN(portNum) && portNum > 0 && portNum <= 65535) {
config.port = portNum
}
if (httpHost.trim()) {
config.host = httpHost.trim()
}
if (httpCorsOrigin.trim()) {
config.corsOrigin = httpCorsOrigin.trim()
}
const res = await (window as any).api.httpServerStart(config)
if (res.success) {
MessagePlugin.success(`HTTP服务器已启动: ${res.data.url}`)
await refreshHttpServerStatus()
} else {
MessagePlugin.error(res.message || '启动失败')
}
} catch (error) {
MessagePlugin.error('启动HTTP服务器时发生错误')
console.error(error)
} finally {
setHttpLoading(false)
}
}
const handleHttpServerStop = async () => {
if (!(window as any).api) return
setHttpLoading(true)
try {
const res = await (window as any).api.httpServerStop()
if (res.success) {
MessagePlugin.success('HTTP服务器已停止')
await refreshHttpServerStatus()
} else {
MessagePlugin.error(res.message || '停止失败')
}
} catch (error) {
MessagePlugin.error('停止HTTP服务器时发生错误')
console.error(error)
} finally {
setHttpLoading(false)
}
}
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
return ( return (
<div style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}> <div style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
@@ -618,115 +540,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
</Card> </Card>
</Tabs.TabPanel> </Tabs.TabPanel>
<Tabs.TabPanel value="http" label="HTTP服务器">
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
<div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
HTTP服务器 API
</div>
<Divider />
<div
style={{
marginBottom: '12px',
fontSize: '13px',
color: 'var(--ss-text-secondary)'
}}
>
HTTP API提供学生分数查询服务访
</div>
<Form labelWidth={120}>
<Form.FormItem label="服务器状态">
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Tag theme={httpServerStatus?.isRunning ? 'success' : 'default'} variant="light">
{httpServerStatus?.isRunning ? '运行中' : '已停止'}
</Tag>
{httpServerStatus?.isRunning && (
<div style={{ fontSize: '12px', color: 'var(--ss-text-secondary)' }}>
访: {httpServerStatus.url}
</div>
)}
</div>
</Form.FormItem>
<Form.FormItem label="端口设置">
<Input
value={httpPort}
onChange={(v) => setHttpPort(v)}
placeholder="端口号 (默认: 3000)"
style={{ width: '200px' }}
disabled={!canAdmin || httpLoading}
/>
</Form.FormItem>
<Form.FormItem label="主机地址">
<Input
value={httpHost}
onChange={(v) => setHttpHost(v)}
placeholder="主机地址 (默认: localhost)"
style={{ width: '200px' }}
disabled={!canAdmin || httpLoading}
/>
</Form.FormItem>
<Form.FormItem label="CORS来源">
<Input
value={httpCorsOrigin}
onChange={(v) => setHttpCorsOrigin(v)}
placeholder="CORS来源 (默认: *)"
style={{ width: '200px' }}
disabled={!canAdmin || httpLoading}
/>
<div
style={{ marginTop: '4px', fontSize: '12px', color: 'var(--ss-text-secondary)' }}
>
*
</div>
</Form.FormItem>
<Form.FormItem label="操作">
<Space>
<Button
theme="primary"
loading={httpLoading}
disabled={!canAdmin}
onClick={handleHttpServerStart}
>
{httpServerStatus?.isRunning ? '重启服务器' : '启动服务器'}
</Button>
<Button
theme="danger"
variant="outline"
loading={httpLoading}
disabled={!canAdmin || !httpServerStatus?.isRunning}
onClick={handleHttpServerStop}
>
</Button>
<Button
variant="outline"
disabled={!canAdmin || httpLoading}
onClick={refreshHttpServerStatus}
>
</Button>
</Space>
</Form.FormItem>
<Form.FormItem label="API端点">
<div
style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '12px' }}
>
<div> GET /health - </div>
<div> GET /api/students/scores - </div>
<div> GET /api/students/:id/score - </div>
<div> GET /api/students/search?name= - </div>
<div> GET /api/leaderboard?limit=10 - </div>
</div>
</Form.FormItem>
</Form>
</Card>
</Tabs.TabPanel>
<Tabs.TabPanel value="url" label="URL 链接"> <Tabs.TabPanel value="url" label="URL 链接">
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}> <Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
<div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}> <div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
@@ -64,6 +64,12 @@ export class AutoScoreService extends Service {
return await (window as any).api.invoke('auto-score:toggleRule', { ruleId, enabled }) return await (window as any).api.invoke('auto-score:toggleRule', { ruleId, enabled })
} }
async sortRules(
ruleIds: number[]
): Promise<{ success: boolean; data?: boolean; message?: string }> {
return await (window as any).api.invoke('auto-score:sortRules', ruleIds)
}
async getStatus(): Promise<{ success: boolean; data?: { enabled: boolean }; message?: string }> { async getStatus(): Promise<{ success: boolean; data?: { enabled: boolean }; message?: string }> {
return await (window as any).api.invoke('auto-score:getStatus', {}) return await (window as any).api.invoke('auto-score:getStatus', {})
} }