Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions docs/ZHIZI_CLOUD_ENGINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,48 @@ GoAgent -> 智子云登录 -> Socket.IO 远程 KataGo -> GTP kata-analyze
在 GoAgent 设置页打开“分析引擎”:

1. 引擎模式选择 `智子云远程算力:直连`。
2. 填写智子云账号和密码,点击“登录并连接智子云”。
2. 选择算力档位:
- `标准`:默认 `gpu-type 1x`,适合日常复盘。
- `快速`:默认 `gpu-type 3x`,适合胜率图和当前手。
- `深度`:默认 `gpu-type 6x`,适合关键局面精读。
3. 填写智子云账号和密码,点击“登录并连接智子云”。
- GoAgent 会调用智子云登录接口获取 token。
- 登录成功后,GoAgent 会把 token 保存到本地加密存储。
- 登录成功后,引擎模式会自动切到 `智子云远程算力:直连`。
- 后续只需要启动 GoAgent,不需要先启动其它应用。
3. 如果密码登录提示密码不正确,可以点击“发送验证码”,收到短信后用“验证码登录”。
4. 如果密码登录提示密码不正确,可以点击“发送验证码”,收到短信后用“验证码登录”。
- 这和智子 Web 端的 `send-code` / `fast-login` 流程一致。
- 验证码只用于本次登录;GoAgent 不会长期保存验证码。
4. Token 通常不需要手动填写。只有你已经从其它方式拿到了 token,才使用“高级:Token”。
5. `zz-ikatago 路径` 是兼容旧连接器的高级选项,普通用户可以不填。
6. 附加参数一般留空;只有你明确知道智子云 Socket.IO 连接参数时再填写。
5. 点击 `检测连接`。GoAgent 会获取短期 Socket.IO token,启动远程 KataGo worker,并跑一个小局面 smoke test。
6. Token 通常不需要手动填写。只有你已经从其它方式拿到了 token,才使用“高级:Token”。
7. `zz-ikatago 路径` 是兼容旧连接器的高级选项,普通用户可以不填。
8. 附加参数一般留空;只有你明确知道智子云 Socket.IO 连接参数时再填写。

`自动` 模式现在是本机优先的默认模式,不会因为本机分析失败就自动上传局面到智子云。需要远程算力时,请手动启用 `智子云远程算力:直连`。

## 充值 / 官方 App

当前智子开放文档没有提供可嵌入 GoAgent 的订单、支付、充值或余额 API。因此 GoAgent 不做应用内支付,也不会保存任何支付信息。

如果检测连接时提示 `额度不足`、`没有可用算力` 或 `暂无 worker`:

1. 点击设置页里的 `下载智子官方 App`。
2. GoAgent 会打开 `https://zhizigo.com/download`,这是智子围棋官方 App 下载页。
3. 在智子官方 App 内登录同一账号,并按 App 内指引充值/开通远程算力。
4. 回到 GoAgent,点击 `刷新远程会话` 或 `检测连接`。

后续如果智子官方开放稳定的购买/余额 API,可以在不改变远程分析协议的前提下接入更直接的状态检测。

## Socket.IO 参数

GoAgent 默认按智子开放文档构造远程 worker 参数:

```text
--platform all --engine-type go --gpu-type 1x --kata-name katago-TENSORRT --kata-weight 28bnbt
```

其中 `gpu-type` 会跟随设置里的算力档位变化,`kata-weight` 默认是 `28bnbt`。高级用户可以在设置里覆盖 platform、gpu type、kata name、kata weight 或附加参数。

## 退出和重新登录

如果刚购买套餐、充值或修改了智子云账号状态,但 GoAgent 仍提示额度不足或远程算力不可用,可以在设置页点击 `退出智子云登录`。GoAgent 会:
Expand Down Expand Up @@ -67,7 +95,7 @@ GoAgent 不会把智子 token 写入普通设置文件,也不会在日志中
- `智子云未登录`:当前选择了 `智子云远程算力:直连`,但本地没有可用 token。请在设置页用账号密码或短信验证码登录;普通用户不需要填写 `zz-ikatago` 路径。
- `密码不正确`:智子官方密码接口拒绝了这组密码。可以改用“发送验证码”再“验证码登录”。
- `验证码不正确或已过期`:重新发送验证码后再登录。
- `智子云余额不足或当前没有可用算力`:先确认智子云套餐或余额已经生效;如果刚充值,点击 `退出智子云登录` 后重新登录,让 GoAgent 刷新 token 和远程会话
- `智子云余额不足或当前没有可用算力`:先下载智子官方 App,在 App 内充值/开通远程算力;如果刚充值,点击 `刷新远程会话` 或 `检测连接`
- `智子云 KataGo 启动超时`:检查 token 是否有效,或在 GoAgent 中重新登录。
- `智子云 GTP 命令失败`:可能是远端引擎尚未 ready、账号状态异常,或智子云当前连接不可用。
- 如果本机 KataGo 可用,且不是强制智子云模式,GoAgent 会在智子云失败后回退本机 KataGo。
95 changes: 81 additions & 14 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell, type Conte
import { isAbsolute, relative, resolve, join } from 'node:path'
import { appHome, findGame, getGames, getIkatagoPassword, getSettings, getTtsCustomApiKey, getTtsVolcengineAccessToken, getTtsVolcengineApiKey, getZhiziToken, hasIkatagoPassword, hasLlmApiKey, hasTtsCustomApiKey, hasTtsVolcengineAccessToken, hasTtsVolcengineApiKey, hasZhiziToken, replaceSettings, setSettings, upsertGames } from './lib/store'
import { BRAND_NAME } from '@shared/brand'
import type { AnalyzeGameQuickRequest, AnalyzePositionRequest, AnalyzeTrialPositionRequest, AppSettings, DashboardData, FoxSyncRequest, KataGoAssetInstallRequest, KataGoBenchmarkRequest, KataGoCancelAnalysisRequest, LibraryDeleteRequest, LlmModelsListRequest, LlmSettingsTestRequest, ReviewRequest, TeacherBoardImageRenderImage, TeacherBoardImageRenderRequest, TeacherBoardImageRenderResponse, TeacherChatMessage, TeacherRunCancelRequest, TeacherRunRequest, ZhiziCloudConnectionTestResult, ZhiziCloudLoginCodeRequest, ZhiziCloudLoginRequest, ZhiziCloudLoginResult, ZhiziCloudSendCodeRequest, ZhiziCloudSendCodeResult } from './lib/types'
import type { AnalyzeGameQuickRequest, AnalyzePositionRequest, AnalyzeTrialPositionRequest, AppSettings, DashboardData, FoxSyncRequest, KataGoAssetInstallRequest, KataGoBenchmarkRequest, KataGoCancelAnalysisRequest, LibraryDeleteRequest, LlmModelsListRequest, LlmSettingsTestRequest, ReviewRequest, TeacherBoardImageRenderImage, TeacherBoardImageRenderRequest, TeacherBoardImageRenderResponse, TeacherChatMessage, TeacherRunCancelRequest, TeacherRunRequest, ZhiziCloudAccountStatusResult, ZhiziCloudConnectionTestResult, ZhiziCloudLoginCodeRequest, ZhiziCloudLoginRequest, ZhiziCloudLoginResult, ZhiziCloudSendCodeRequest, ZhiziCloudSendCodeResult, ZhiziCloudStatus } from './lib/types'
import { importSgfFile, readGameRecord } from './services/sgf'
import { ensureFoxGameDownloaded, syncFoxGames } from './services/fox'
import { runReview } from './services/review'
Expand Down Expand Up @@ -30,7 +30,7 @@ import {
} from './services/studentProfile'
import { archiveTeacherSession, createTeacherSession, deleteTeacherSession, getActiveTeacherSession, listTeacherSessions, updateTeacherSessionMessages } from './services/teacherSession'
import { clearTtsCache, inspectTtsAssets, listTtsVoices, synthesizeTts, testTtsSettings } from './services/tts'
import { loginZhiziCloudByCode, loginZhiziCloudByPassword, sendZhiziCloudLoginCode } from './services/zhiziCloudAuth'
import { inspectZhiziCloudAccount, loginZhiziCloudByCode, loginZhiziCloudByPassword, sendZhiziCloudLoginCode, ZHIZI_OFFICIAL_APP_DOWNLOAD_URL } from './services/zhiziCloudAuth'
import { queryZhiziGtpAnalysisBatch } from './services/zhiziGtpEngine'

let mainWindow: BrowserWindow | null = null
Expand Down Expand Up @@ -108,21 +108,40 @@ function requestTeacherBoardImages(event: IpcMainInvokeEvent, request: TeacherBo
})
}

function humanizeZhiziConnectionError(error: unknown): string {
function classifyZhiziConnectionError(error: unknown): { status: ZhiziCloudStatus; message: string } {
const text = error instanceof Error ? error.message : String(error)
if (/not_enough_credit|余额不足|not enough credit|没有可用算力/i.test(text)) {
return '智子云 token 有效,但远程 worker 分配失败:当前账号没有可用算力或额度不足。请在智子云确认套餐/余额后重试。'
return {
status: 'no-credit',
message: '智子云 token 有效,但远程 worker 分配失败:当前账号没有可用算力或额度不足。请下载智子官方 App,在 App 内充值/开通后回到 GoAgent 重新检测。'
}
}
if (/invalid_status|NoSuchKey|ssh\.json|colab/i.test(text)) {
return '智子云远程平台没有找到当前账号的可用 worker 配置。请确认该账号已开通对应远程算力。'
return {
status: 'worker-unavailable',
message: '智子云远程平台没有找到当前账号的可用 worker 配置。请确认该账号已开通对应远程算力。'
}
}
if (/token 已失效|401|invalid.*token|auth/i.test(text)) {
return '智子云 token 已失效,请退出后重新登录智子云。'
return {
status: 'token-expired',
message: '智子云 token 已失效,请退出后重新登录智子云。'
}
}
if (/websocket error|Socket 连接失败|Socket 已断开|transport error|xhr poll error|timeout|超时/i.test(text)) {
return `智子云 Socket 连接失败:${text.slice(0, 260)}`
return {
status: 'network-error',
message: `智子云 Socket 连接失败:${text.slice(0, 260)}`
}
}
return {
status: 'worker-unavailable',
message: `智子云连接检测失败:${text.slice(0, 320)}`
}
return `智子云连接检测失败:${text.slice(0, 320)}`
}

function humanizeZhiziConnectionError(error: unknown): string {
return classifyZhiziConnectionError(error).message
}

function attachTextEditingContextMenu(window: BrowserWindow): void {
Expand Down Expand Up @@ -518,12 +537,15 @@ app.whenReady().then(() => {
setSettings({
zhiziUsername: payload.phone.trim(),
zhiziToken: result.token,
katagoEngineMode: 'zhizi'
katagoEngineMode: 'zhizi',
zhiziLastStatus: 'logged-in',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: true,
message: `${result.message} GoAgent 可直接连接智子云。`,
hasToken: true,
status: 'logged-in',
dashboard: await dashboard()
}
})
Expand All @@ -539,12 +561,15 @@ app.whenReady().then(() => {
setSettings({
zhiziUsername: payload.phone.trim(),
zhiziToken: result.token,
katagoEngineMode: 'zhizi'
katagoEngineMode: 'zhizi',
zhiziLastStatus: 'logged-in',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: true,
message: `${result.message} GoAgent 可直接连接智子云。`,
hasToken: true,
status: 'logged-in',
dashboard: await dashboard()
}
})
Expand All @@ -553,20 +578,44 @@ app.whenReady().then(() => {
setSettings({
zhiziToken: '',
katagoEngineMode: 'auto',
zhiziUseWhenLocalSlow: false
zhiziUseWhenLocalSlow: false,
zhiziLastStatus: 'logged-out',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: true,
message: '已退出智子云登录,已清除本地 token,并切回自动分析模式。重新登录后会自动连接智子云。',
hasToken: false,
status: 'logged-out',
dashboard: await dashboard()
}
})
ipcMain.handle('zhizi:test-connection', async (): Promise<ZhiziCloudConnectionTestResult> => {
ipcMain.handle('zhizi:me', async (): Promise<ZhiziCloudAccountStatusResult> => {
const settings = getSettings()
const result = await inspectZhiziCloudAccount(settings.zhiziToken)
setSettings({
zhiziLastStatus: result.status,
zhiziLastCheckedAt: new Date().toISOString()
})
return {
...result,
dashboard: await dashboard()
}
})
ipcMain.handle('zhizi:open-official-app-download', async () => {
await shell.openExternal(ZHIZI_OFFICIAL_APP_DOWNLOAD_URL)
return { ok: true, url: ZHIZI_OFFICIAL_APP_DOWNLOAD_URL }
})
async function runZhiziConnectionTest(): Promise<ZhiziCloudConnectionTestResult> {
const settings = getSettings()
if (!settings.zhiziToken.trim()) {
setSettings({
zhiziLastStatus: 'logged-out',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: false,
status: 'logged-out',
message: '智子云未登录:请先用账号密码或短信验证码登录。'
}
}
Expand Down Expand Up @@ -599,13 +648,23 @@ app.whenReady().then(() => {
const result = results.get('zhizi-smoke')
const best = result?.moveInfos?.[0]
if (!best) {
setSettings({
zhiziLastStatus: 'worker-unavailable',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: false,
status: 'worker-unavailable',
message: '智子云已连接,但没有返回候选点。请稍后重试。'
}
}
setSettings({
zhiziLastStatus: 'ready',
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: true,
status: 'ready',
message: '智子云连接成功,远程 KataGo 已返回候选点。',
candidateCount: result?.moveInfos?.length ?? 0,
topMove: typeof best.move === 'string' ? best.move : undefined,
Expand All @@ -614,12 +673,20 @@ app.whenReady().then(() => {
scoreMean: typeof best.scoreMean === 'number' ? best.scoreMean : typeof best.scoreLead === 'number' ? best.scoreLead : undefined
}
} catch (error) {
const classified = classifyZhiziConnectionError(error)
setSettings({
zhiziLastStatus: classified.status,
zhiziLastCheckedAt: new Date().toISOString()
})
return {
ok: false,
message: humanizeZhiziConnectionError(error)
status: classified.status,
message: classified.message
}
}
})
}
ipcMain.handle('zhizi:refresh-session', async (): Promise<ZhiziCloudConnectionTestResult> => runZhiziConnectionTest())
ipcMain.handle('zhizi:test-connection', async (): Promise<ZhiziCloudConnectionTestResult> => runZhiziConnectionTest())
ipcMain.handle('tts:inspect-assets', async () => inspectTtsAssets())
ipcMain.handle('tts:list-voices', async () => listTtsVoices())
ipcMain.handle('tts:synthesize', async (_event, payload) => synthesizeTts(payload))
Expand Down
7 changes: 7 additions & 0 deletions src/main/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,15 @@ const defaults: AppSettings = {
zhiziClientBin: '',
zhiziUsername: '',
zhiziToken: '',
zhiziGpuTier: 'standard',
zhiziPlatform: 'all',
zhiziGpuType: '',
zhiziKataName: 'katago-TENSORRT',
zhiziKataWeight: '28bnbt',
zhiziExtraArgs: '',
zhiziUseWhenLocalSlow: false,
zhiziLastStatus: 'logged-out',
zhiziLastCheckedAt: '',
pythonBin: defaultPythonBin(),
llmBaseUrl: 'https://api.openai.com/v1',
llmApiKey: '',
Expand Down
26 changes: 26 additions & 0 deletions src/main/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,15 @@ export interface AppSettings {
zhiziClientBin: string
zhiziUsername: string
zhiziToken: string
zhiziGpuTier: ZhiziCloudGpuTier
zhiziPlatform: string
zhiziGpuType: string
zhiziKataName: string
zhiziKataWeight: string
zhiziExtraArgs: string
zhiziUseWhenLocalSlow: boolean
zhiziLastStatus: ZhiziCloudStatus
zhiziLastCheckedAt: string
pythonBin: string
llmBaseUrl: string
llmApiKey: string
Expand Down Expand Up @@ -157,6 +164,15 @@ export interface AppSettings {
export type KataGoEngineMode = 'auto' | 'persistent' | 'spawn' | 'ikatago' | 'zhizi'
export type KataGoAnalysisSpeedMode = 'auto' | 'fast' | 'balanced' | 'deep'
export type KataGoModelPresetId = string
export type ZhiziCloudGpuTier = 'standard' | 'fast' | 'deep'
export type ZhiziCloudStatus =
| 'logged-out'
| 'logged-in'
| 'ready'
| 'no-credit'
| 'token-expired'
| 'network-error'
| 'worker-unavailable'

export interface ZhiziCloudLoginRequest {
phone: string
Expand All @@ -181,11 +197,21 @@ export interface ZhiziCloudLoginResult {
ok: boolean
message: string
hasToken: boolean
status?: ZhiziCloudStatus
dashboard?: DashboardData
}

export interface ZhiziCloudAccountStatusResult {
ok: boolean
status: ZhiziCloudStatus
message: string
account?: Record<string, unknown>
dashboard?: DashboardData
}

export interface ZhiziCloudConnectionTestResult {
ok: boolean
status?: ZhiziCloudStatus
message: string
candidateCount?: number
topMove?: string
Expand Down
Loading
Loading