feat: 原生系统提示词只读展示、会话末尾 fork、深浅色主题
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, ipcMain, Menu, shell } from 'electron'
|
||||
import { app, BrowserWindow, ipcMain, Menu, nativeTheme, shell } from 'electron'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { registerIpc } from './ipc'
|
||||
@@ -32,13 +32,16 @@ function registerWindowControls(): void {
|
||||
}
|
||||
|
||||
async function createWindow(): Promise<void> {
|
||||
// 窗口底色跟随深浅色(加载/resize 时不闪白);构造函数里已按持久化偏好设置过
|
||||
// nativeTheme.themeSource,这里用实际生效值
|
||||
const dark = nativeTheme.shouldUseDarkColors
|
||||
const win = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 840,
|
||||
minWidth: 960,
|
||||
minHeight: 640,
|
||||
show: false,
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: dark ? '#212121' : '#ffffff',
|
||||
title: 'Inkreach Agent',
|
||||
// 窗口/任务栏图标(Windows/Linux;macOS 打包时由 build 配置提供 icns)
|
||||
...(existsSync(APP_ICON) ? { icon: APP_ICON } : {}),
|
||||
@@ -52,6 +55,11 @@ async function createWindow(): Promise<void> {
|
||||
}
|
||||
})
|
||||
|
||||
// 深浅色切换时同步窗口底色(避免暗色下 resize/加载闪白块)
|
||||
nativeTheme.on('updated', () => {
|
||||
win.setBackgroundColor(nativeTheme.shouldUseDarkColors ? '#212121' : '#ffffff')
|
||||
})
|
||||
|
||||
// 最大化状态同步给渲染层(切换「最大化/还原」字形)
|
||||
const sendWinState = (): void => win.webContents.send('win:state', win.isMaximized())
|
||||
win.on('maximize', sendWinState)
|
||||
|
||||
@@ -178,19 +178,25 @@ export function registerIpc(): AgentService {
|
||||
)
|
||||
})
|
||||
|
||||
// 会话末尾分叉为新会话(pi fork,原会话保留)
|
||||
ipcMain.handle('session:fork', () => pass(() => service.forkSession()))
|
||||
|
||||
// ---- 应用设置 ----
|
||||
ipcMain.handle('settings:get', () => service.getSettings())
|
||||
|
||||
ipcMain.handle('settings:setPrompt', (_event, text: unknown) => {
|
||||
if (typeof text !== 'string') throw new Error('invalid prompt')
|
||||
return pass(() => service.setSystemPrompt(text))
|
||||
})
|
||||
// 当前生效的系统提示词(pi 原生 + 技能注入,只读展示)
|
||||
ipcMain.handle('settings:getSystemPrompt', () => service.getSystemPrompt())
|
||||
|
||||
ipcMain.handle('settings:setTheme', (_event, id: unknown) => {
|
||||
if (typeof id !== 'string' || !id.trim()) throw new Error('invalid theme id')
|
||||
service.setThemeColor(id.trim())
|
||||
})
|
||||
|
||||
ipcMain.handle('settings:setThemeMode', (_event, mode: unknown) => {
|
||||
if (mode !== 'light' && mode !== 'dark' && mode !== 'system') throw new Error('invalid theme mode')
|
||||
service.setThemeMode(mode)
|
||||
})
|
||||
|
||||
// ---- 工作区 ----
|
||||
ipcMain.handle('workspaces:list', () =>
|
||||
service.listWorkspaces().catch((err: unknown) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { app, dialog, shell } from 'electron'
|
||||
import { nativeTheme } from 'electron'
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import {
|
||||
@@ -39,6 +40,7 @@ import type {
|
||||
SessionStatsInfo,
|
||||
SkillDetail,
|
||||
ThinkingLevel,
|
||||
ThemeMode,
|
||||
WorkspaceFile,
|
||||
WorkspaceMeta
|
||||
} from '@shared/types'
|
||||
@@ -63,7 +65,7 @@ export class AgentService {
|
||||
private flushTimer: NodeJS.Timeout | null = null
|
||||
private attached: AgentSession | null = null
|
||||
private unsubscribeAttached: (() => void) | null = null
|
||||
private settings: AppSettings = { systemPrompt: '' }
|
||||
private settings: AppSettings = {}
|
||||
/** 当前工作区目录(agent cwd),会话按目录隔离 */
|
||||
private workspaceDir = ''
|
||||
private approvalsRegistered = false
|
||||
@@ -73,6 +75,13 @@ export class AgentService {
|
||||
private skillsEstimate: { cwd: string; value: number } | null = null
|
||||
readonly approvals = new ApprovalService()
|
||||
|
||||
constructor() {
|
||||
// 设置同步读入 + 应用持久化的深浅色偏好(nativeTheme 会影响渲染层
|
||||
// prefers-color-scheme 与原生控件配色;窗口创建前生效可避免首帧闪错色)
|
||||
this.loadSettings()
|
||||
nativeTheme.themeSource = this.settings.themeMode ?? 'system'
|
||||
}
|
||||
|
||||
/** 工作区根目录:文档/Inkreach,每个子目录 = 一个工作区 */
|
||||
get workspacesRoot(): string {
|
||||
return join(app.getPath('documents'), 'Inkreach')
|
||||
@@ -94,7 +103,6 @@ export class AgentService {
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(this.settingsFile, 'utf8')) as Partial<AppSettings>
|
||||
this.settings = {
|
||||
systemPrompt: typeof raw.systemPrompt === 'string' ? raw.systemPrompt : '',
|
||||
workspace: typeof raw.workspace === 'string' ? raw.workspace : undefined,
|
||||
customWorkspaces: Array.isArray(raw.customWorkspaces)
|
||||
? raw.customWorkspaces.filter((p): p is string => typeof p === 'string')
|
||||
@@ -102,10 +110,11 @@ export class AgentService {
|
||||
// 历史版本存过 autoEdit/plan,统一收敛回 confirm
|
||||
permissionMode:
|
||||
raw.permissionMode === 'full' ? 'full' : 'confirm',
|
||||
themeColor: typeof raw.themeColor === 'string' ? raw.themeColor : undefined
|
||||
themeColor: typeof raw.themeColor === 'string' ? raw.themeColor : undefined,
|
||||
themeMode: raw.themeMode === 'light' || raw.themeMode === 'dark' ? raw.themeMode : 'system'
|
||||
}
|
||||
} catch {
|
||||
this.settings = { systemPrompt: '', permissionMode: this.permissionMode }
|
||||
this.settings = { permissionMode: this.permissionMode, themeMode: 'system' }
|
||||
}
|
||||
this.permissionMode = this.settings.permissionMode ?? 'confirm'
|
||||
this.initWorkspaces()
|
||||
@@ -258,7 +267,6 @@ export class AgentService {
|
||||
cwd: targetCwd,
|
||||
agentDir: targetAgentDir,
|
||||
resourceLoaderOptions: {
|
||||
...(this.settings.systemPrompt.trim() ? { systemPrompt: this.settings.systemPrompt } : {}),
|
||||
extensionFactories: [
|
||||
createApprovalExtension(this.approvals, TOOLS_REQUIRING_APPROVAL, () => this.permissionMode)
|
||||
]
|
||||
@@ -1054,17 +1062,45 @@ export class AgentService {
|
||||
this.emitSwitched(session)
|
||||
}
|
||||
|
||||
/** 在会话末尾分叉出新会话(pi fork:复制到新会话文件,原会话保留不动)。
|
||||
* fork 后 runtime 重挂新会话,rebind 回调负责发 session_switched。 */
|
||||
async forkSession(): Promise<void> {
|
||||
const runtime = await this.getRuntime()
|
||||
const session = runtime.session
|
||||
if (session.isStreaming) {
|
||||
await session.abort()
|
||||
}
|
||||
const leafId = session.sessionManager.getLeafId()
|
||||
if (!leafId) {
|
||||
throw new Error('会话还没有可分叉的内容')
|
||||
}
|
||||
const result = await runtime.fork(leafId, { position: 'at' })
|
||||
if (result.cancelled) return
|
||||
}
|
||||
|
||||
// ---- 应用设置 ----
|
||||
|
||||
getSettings(): AppSettings {
|
||||
return { ...this.settings }
|
||||
}
|
||||
|
||||
/** 更新系统提示词后重建运行时(continueRecent 重新挂载当前最近会话,历史保留) */
|
||||
async setSystemPrompt(text: string): Promise<void> {
|
||||
this.settings = { ...this.settings, systemPrompt: text }
|
||||
/** 当前生效的系统提示词(pi 原生提示词 + 技能注入,只读展示用) */
|
||||
async getSystemPrompt(): Promise<string> {
|
||||
const runtime = await this.getRuntime()
|
||||
return runtime.session.systemPrompt
|
||||
}
|
||||
|
||||
/** 深浅色模式:写入 Electron nativeTheme(渲染层 prefers-color-scheme 随之变化)并持久化 */
|
||||
setThemeMode(mode: ThemeMode): void {
|
||||
this.settings = { ...this.settings, themeMode: mode }
|
||||
this.saveSettings()
|
||||
nativeTheme.themeSource = mode
|
||||
}
|
||||
|
||||
/** 主题色偏好(纯外观,仅持久化;渲染层负责应用) */
|
||||
setThemeColor(id: string): void {
|
||||
this.settings = { ...this.settings, themeColor: id }
|
||||
this.saveSettings()
|
||||
await this.rebuildRuntime()
|
||||
}
|
||||
|
||||
/** 切换权限模式:即时生效(审批扩展每笔工具调用实时读取),并广播给渲染层 */
|
||||
@@ -1078,12 +1114,6 @@ export class AgentService {
|
||||
}
|
||||
}
|
||||
|
||||
/** 主题色偏好(纯外观,仅持久化;渲染层负责应用) */
|
||||
setThemeColor(id: string): void {
|
||||
this.settings = { ...this.settings, themeColor: id }
|
||||
this.saveSettings()
|
||||
}
|
||||
|
||||
private async rebuildRuntime(): Promise<void> {
|
||||
const old = this.runtime
|
||||
this.runtime = null
|
||||
|
||||
Reference in New Issue
Block a user