Skip to content

Commit e3c7a13

Browse files
committed
feat: decouple management panel delivery
1 parent e261fb7 commit e3c7a13

6 files changed

Lines changed: 118 additions & 30 deletions

File tree

.github/workflows/release.yaml

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
11
name: Release and Publish
2+
23
on:
34
push:
4-
# 监听 main 分支的 push 事件
55
branches:
66
- main
7-
# 赋予 release-please-action 权限
7+
# Frontend source is delivered by chatgpt-plugin, not the npm package.
8+
# A frontend-only commit therefore must not create a release PR or publish.
9+
paths-ignore:
10+
- "frontend/**"
11+
812
permissions:
913
contents: write
1014
pull-requests: write
15+
1116
jobs:
12-
# 设置 release-please 任务
1317
release-please:
14-
# 设置任务运行环境为 ubuntu-latest
1518
runs-on: ubuntu-latest
1619
steps:
17-
# 使用 release-please-action 动作来自动创建发布 PR
1820
- uses: googleapis/release-please-action@v4
1921
id: release
2022
with:
21-
# 设置发布类型为 node
2223
release-type: node
23-
# 检出代码
24+
2425
- uses: actions/checkout@v4
2526
if: ${{ steps.release.outputs.release_created }}
26-
# 设置 Node.js 环境
27+
2728
- uses: actions/setup-node@v4
29+
if: ${{ steps.release.outputs.release_created }}
2830
with:
29-
# 设置 Node.js 版本
3031
node-version: 20
31-
# 设置 npm 源
3232
registry-url: "https://registry.npmjs.org"
33-
# 安装 pnpm
33+
3434
- uses: pnpm/action-setup@v4
35+
if: ${{ steps.release.outputs.release_created }}
3536
with:
3637
version: 9
37-
# 安装依赖
38+
3839
- run: pnpm install
3940
if: ${{ steps.release.outputs.release_created }}
40-
# 安装前端依赖并构建
41-
- run: cd frontend && pnpm install && pnpm build
42-
if: ${{ steps.release.outputs.release_created }}
43-
# 构建后端
41+
4442
- run: pnpm run prepublishOnly
4543
if: ${{ steps.release.outputs.release_created }}
46-
# 发布到 npm
44+
4745
- run: pnpm publish --access public --no-git-checks --//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
4846
env:
4947
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

frontend/src/views/config/index.vue

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ const config = reactive({
9999
* enable: boolean,
100100
* hit: string[],
101101
* probability: number,
102+
* speakingMode: 'reply' | 'contextual',
103+
* contextualPrompt: string,
102104
* defaultPreset: string,
103105
* presetPrefix?: string,
104106
* presetMap: Array<{
@@ -117,6 +119,8 @@ const config = reactive({
117119
enable: false,
118120
hit: ['bym'],
119121
probability: 0.02,
122+
speakingMode: 'reply',
123+
contextualPrompt: '你现在不是在回复某一条特定消息,而是作为这个群里的一名普通群友自然参与当前聊天。请阅读前面的群聊上下文,选择一个自然的切入点发言,可以接续话题、补充信息、吐槽、提问或表达态度。不要解释任务,不要提及“上下文”“指令”“AI”或“机器人”,不要强行引用、@或逐句回答触发你的那条消息。直接输出一段适合发到群里的自然发言。',
120124
defaultPreset: '',
121125
presetPrefix: '',
122126
presetMap: [],
@@ -127,6 +131,8 @@ const config = reactive({
127131
enable: boolean
128132
hit: string[]
129133
probability: number
134+
speakingMode: 'reply' | 'contextual'
135+
contextualPrompt: string
130136
defaultPreset: string
131137
presetPrefix?: string
132138
presetMap: Array<{
@@ -320,10 +326,14 @@ async function getConfig(): Promise<CustomConfig> {
320326
else {
321327
message.error('配置加载失败')
322328
}
323-
// Keep defaults introduced by newer plugin versions when loading an older
324-
// persisted vision object that does not yet contain them.
329+
// Keep defaults introduced by newer plugin versions when loading older
330+
// persisted nested config objects that do not yet contain them.
325331
Object.assign(config, {
326332
...data.data,
333+
bym: {
334+
...config.bym,
335+
...(data.data?.bym || {}),
336+
},
327337
llm: {
328338
...config.llm,
329339
...(data.data?.llm || {}),
@@ -647,6 +657,31 @@ onMounted(async () => {
647657
<NSwitch v-model:value="config.bym.enable" />
648658
</NFormItemGridItem>
649659

660+
<NFormItemGridItem span="24 s:24 m:12" label="发言策略" path="speakingMode" :span-feedback="24">
661+
<NSelect
662+
v-model:value="config.bym.speakingMode"
663+
:options="[
664+
{ label: '回复触发消息(原有模式)', value: 'reply' },
665+
{ label: '自主融入群聊(结合上下文)', value: 'contextual' },
666+
]"
667+
/>
668+
<template #feedback>
669+
<span class="form-hint">自主模式仅在群聊上下文开启时生效,否则自动使用原有模式</span>
670+
</template>
671+
</NFormItemGridItem>
672+
673+
<NFormItemGridItem v-if="config.bym.speakingMode === 'contextual'" span="24" label="自主发言指令" path="contextualPrompt" :span-feedback="24">
674+
<NInput
675+
v-model:value="config.bym.contextualPrompt"
676+
type="textarea"
677+
:autosize="{ minRows: 4, maxRows: 10 }"
678+
placeholder="告诉模型如何作为普通群友结合上下文自然发言"
679+
/>
680+
<template #feedback>
681+
<span class="form-hint">该指令会替代触发者本轮消息作为 user prompt;群聊上下文仍在其前方</span>
682+
</template>
683+
</NFormItemGridItem>
684+
650685
<!-- 伪人必定触发词 -->
651686
<NFormItemGridItem span="24" label="伪人触发词" path="hit" :span-feedback="24">
652687
<NDynamicTags v-model:value="config.bym.hit" placeholder="输入触发词并按回车添加" />

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"types": "dist/index.d.ts",
1616
"files": [
1717
"dist",
18-
"frontend/build",
1918
"LICENSE",
2019
"README.md",
2120
"package.json"
2221
],
2322
"scripts": {
2423
"build": "tsc --noEmit && tsdown",
24+
"build:plugin-frontend": "node scripts/build-plugin-frontend.mjs",
2525
"dev": "nodemon --exec ts-node src/index.ts",
2626
"lint": "eslint . --ext .ts",
2727
"lint:fix": "eslint . --ext .ts --fix",
@@ -73,4 +73,4 @@
7373
"dependencies": {
7474
"@modelcontextprotocol/sdk": "^1.29.0"
7575
}
76-
}
76+
}

scripts/build-plugin-frontend.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { spawnSync } from 'node:child_process'
2+
import fs from 'node:fs'
3+
import path from 'node:path'
4+
import { fileURLToPath } from 'node:url'
5+
6+
const projectDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7+
const pluginDir = path.resolve(
8+
process.argv[2] ||
9+
process.env.CHAITE_PLUGIN_DIR ||
10+
path.join(projectDir, '..', 'Yunzai', 'plugins', 'chatgpt-plugin'),
11+
)
12+
const manifestPath = path.join(pluginDir, 'package.json')
13+
if (!fs.existsSync(manifestPath) || JSON.parse(fs.readFileSync(manifestPath, 'utf8')).name !== 'chatgpt-plugin') {
14+
console.error(`chatgpt-plugin not found at ${pluginDir}. Pass its path as the first argument or set CHAITE_PLUGIN_DIR.`)
15+
process.exit(1)
16+
}
17+
18+
const frontendDir = path.join(projectDir, 'frontend')
19+
const outDir = path.join(pluginDir, 'resources', 'admin')
20+
const pnpmArgs = [
21+
'--dir', frontendDir,
22+
'exec', 'vite', 'build',
23+
'--mode', 'prod',
24+
'--outDir', outDir,
25+
'--emptyOutDir',
26+
]
27+
const pnpmCli = process.env.npm_execpath
28+
const command = pnpmCli ? process.execPath : 'pnpm'
29+
const args = pnpmCli ? [pnpmCli, ...pnpmArgs] : pnpmArgs
30+
const result = spawnSync(command, args, { stdio: 'inherit' })
31+
32+
if (result.error) {
33+
console.error(result.error)
34+
process.exit(1)
35+
}
36+
process.exit(result.status ?? 1)

src/controllers/index.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ import cors from 'cors'
1616
import { authenticateToken } from './middlewares'
1717
import { getLogger } from '../utils'
1818
import path from 'path'
19+
import fs from 'node:fs'
1920
import type { Server } from 'node:http'
2021
import { __dirname } from '../__dirname'
2122

22-
export function createApp (configure?: (app: Application) => void): Application {
23+
export interface ApiServerOptions {
24+
/** Directory containing the built management panel (index.html + assets). */
25+
frontendDir?: string
26+
}
27+
28+
export function createApp (configure?: (app: Application) => void, options: ApiServerOptions = {}): Application {
2329
const app = express()
2430
app.use(express.json())
2531
app.use(cors())
@@ -37,15 +43,27 @@ export function createApp (configure?: (app: Application) => void): Application
3743
app.use('/api/logs', authenticateToken, LogsRouter)
3844
app.use('/v1', authenticateToken, OpenAIRouter)
3945
configure?.(app)
40-
app.use(express.static(path.join(__dirname, '../frontend/build')))
41-
app.get('*', (_req, res) => {
42-
res.sendFile(path.join(__dirname, '../frontend/build/index.html'))
43-
})
46+
const frontendDir = options.frontendDir
47+
? path.resolve(options.frontendDir)
48+
: path.join(__dirname, '../frontend/build')
49+
const frontendEntry = path.join(frontendDir, 'index.html')
50+
if (fs.existsSync(frontendEntry)) {
51+
app.use(express.static(frontendDir))
52+
app.get('*', (_req, res) => {
53+
res.sendFile(frontendEntry)
54+
})
55+
}
56+
else {
57+
getLogger().warn(`Chaite management panel not found at ${frontendDir}`)
58+
app.get('*', (_req, res) => {
59+
res.status(404).json({ code: -1, data: null, message: 'Management panel is not installed' })
60+
})
61+
}
4462
return app
4563
}
4664

47-
export function runServer (host: string, port: number, configure?: (app: Application) => void): { app: Application, server: Server } {
48-
const app = createApp(configure)
65+
export function runServer (host: string, port: number, configure?: (app: Application) => void, options: ApiServerOptions = {}): { app: Application, server: Server } {
66+
const app = createApp(configure, options)
4967
const server = app.listen(port, host, () => {
5068
getLogger().info(`Chaite server is running at http://${host}:${port}`)
5169
})

src/core/chaite.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { Channel, ChatPreset } from '../channels'
2525
import { RAGManager } from '../rag'
2626
import EventEmitter from 'node:events'
27-
import { runServer } from '../controllers'
27+
import { ApiServerOptions, runServer } from '../controllers'
2828
import { ToolsGroupManager } from '../share'
2929
import { TriggerManager } from '../share'
3030
import type { Application } from 'express'
@@ -610,11 +610,12 @@ export class Chaite extends EventEmitter {
610610
return this.expressApp
611611
}
612612

613-
runApiServer(configureApp?: (app: Application) => void): Application {
613+
runApiServer(configureApp?: (app: Application) => void, options: ApiServerOptions = {}): Application {
614614
const { app } = runServer(
615615
this.globalConfig?.getHost() || DEFAULT_HOST,
616616
this.globalConfig?.getPort() || DEFAULT_PORT,
617617
configureApp,
618+
options,
618619
)
619620
this.expressApp = app
620621
return app

0 commit comments

Comments
 (0)