Skip to content
Merged
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
17 changes: 15 additions & 2 deletions apps/desktop/src/main/context/app_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { createWin32Platform, isTencentFilesRoot, type Platform } from '@weq/pla
import { startMcpServer, stopMcpServer } from '../mcp/server';
import { aiToolSpecs, runAiTool } from '../mcp/openai_tools';
import { getExternalMcpHub, disposeExternalMcp } from '../mcp/external';
import { sampleHitokoto } from '../hitokoto';
import {
accountConfigId,
UserConfigService,
Expand Down Expand Up @@ -529,6 +530,9 @@ export function initAppContext(): AppContext {
// OIDB-backed video / file download URL resolver (needs the online QQ pid);
// injected into the export manager for 视频 / 文件 媒体补全.
const mediaUrl = new MediaUrlService(platform.native.ntHelper, session, resolveOnlinePid);
// QQ 空间 Web 查询(说说 / 相册)—— 也注入进导出管理器,供「好友空间导出」
// 翻页拉说说。需在线 QQ 凭证;离线时 fetchMsgList 会抛错(前端已先拦截)。
const webQuery = new WebQueryService(platform.native.ntHelper, session, resolveOnlinePid);
// Shared instances also fed to the export manager's ChatLab deps (name /
// role / profile resolution), so they're built before the services object.
const groupInfo = new GroupInfoService(session);
Expand Down Expand Up @@ -582,6 +586,8 @@ export function initAppContext(): AppContext {
name.startsWith('mcp__') ? getExternalMcpHub().run(name, args) : runAiTool(name, args),
// 配置变更/启动时把外部 MCP 配置同步给 Hub(连接惰性建立)。
syncExternalMcp: (raw) => getExternalMcpHub().configure(raw),
// 写报告时随机抽一批「一言」候选,供模型挑一句做主题大字(多元化)。
sampleHitokoto,
}),
exportManager: new (await import('@weq/service')).ExportTaskManager(
new MsgService(session),
Expand Down Expand Up @@ -631,10 +637,12 @@ export function initAppContext(): AppContext {
return uin ? { uid: '', uin, nick: '' } : null;
},
},
// 好友 QQ 空间说说导出:翻页拉取能力(需在线 QQ)。
qzone: { fetchMsgList: (uin, pos, num) => webQuery.getQzoneMsgList(uin, pos, num) },
},
),
dbDecrypt: new DbDecryptService(session, platform),
webQuery: new WebQueryService(platform.native.ntHelper, session, resolveOnlinePid),
webQuery,
groupAlbumMedia: new GroupAlbumMediaService(platform.native.ntHelper, session, resolveOnlinePid),
};
// Scheduled export manager — fires saved templates through the export
Expand Down Expand Up @@ -745,6 +753,8 @@ export function initAppContext(): AppContext {
userConfig.cacheDir('media'),
);
const mediaUrl = new MediaUrlService(platform.native.ntHelper, session, noPid);
// 静态账号无在线 QQ —— webQuery 用 noPid,「好友空间导出」会优雅失败(离线)。
const webQuery = new WebQueryService(platform.native.ntHelper, session, noPid);
const groupInfo = new GroupInfoService(session);
const profile = new ProfileService(session);
const fileSearch = new FileSearchService(session, platform);
Expand Down Expand Up @@ -792,6 +802,8 @@ export function initAppContext(): AppContext {
name.startsWith('mcp__') ? getExternalMcpHub().run(name, args) : runAiTool(name, args),
// 配置变更/启动时把外部 MCP 配置同步给 Hub(连接惰性建立)。
syncExternalMcp: (raw) => getExternalMcpHub().configure(raw),
// 写报告时随机抽一批「一言」候选,供模型挑一句做主题大字(多元化)。
sampleHitokoto,
}),
exportManager: new (await import('@weq/service')).ExportTaskManager(
new MsgService(session),
Expand Down Expand Up @@ -834,10 +846,11 @@ export function initAppContext(): AppContext {
return uin ? { uid: '', uin, nick: '' } : null;
},
},
qzone: { fetchMsgList: (uin, pos, num) => webQuery.getQzoneMsgList(uin, pos, num) },
},
),
dbDecrypt: new DbDecryptService(session, platform),
webQuery: new WebQueryService(platform.native.ntHelper, session, noPid),
webQuery,
groupAlbumMedia: new GroupAlbumMediaService(platform.native.ntHelper, session, noPid),
};

Expand Down
65 changes: 65 additions & 0 deletions apps/desktop/src/main/hitokoto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 一言(hitokoto)池 —— 供 WeQ 助手写报告时挑一句做「主题句大字」。
*
* 数据是 `resources/hitokoto.json`(约 9000 条高质量句子,字段 hitokoto/from)。
* 报告风格千篇一律的一大主因是标题僵化;给模型一批**随机**候选句,让它按报告主题/
* 心情语义挑最贴合的一句渲染成封面级大字,既保证多元、又保证相关(LLM 挑句 = 比机械
* 关键词匹配更聪明的「匹配」)。
*
* 读盘一次后常驻内存;随机抽样在主进程做(此处允许 Math.random)。
*/

import { readFileSync } from 'node:fs';
import { resolveResource } from './resource';

interface HitokotoEntry {
hitokoto?: string;
from?: string;
}

/** 采样结果:句子 + 出处(出处可能缺失)。 */
export interface HitokotoVerse {
text: string;
from: string;
}

let cache: HitokotoEntry[] | null = null;

function load(): HitokotoEntry[] {
if (cache) return cache;
try {
const path = resolveResource('hitokoto.json');
const parsed = path ? JSON.parse(readFileSync(path, 'utf-8')) : [];
cache = Array.isArray(parsed) ? parsed : [];
} catch {
cache = [];
}
return cache;
}

/**
* 随机抽 n 条一言(去重、去空、句长适中——太长的做不了大字标题)。
* 资源缺失时返回空数组,调用方(系统提示)自动跳过该小节。
*/
export function sampleHitokoto(n: number): HitokotoVerse[] {
const all = load();
if (!all.length) return [];
const pool = all.filter((e) => {
const t = e.hitokoto?.trim();
return t && t.length >= 4 && t.length <= 40;
});
if (!pool.length) return [];

const picked: HitokotoVerse[] = [];
const seen = new Set<number>();
const want = Math.min(n, pool.length);
let guard = 0;
while (picked.length < want && guard < want * 20) {
guard += 1;
const i = Math.floor(Math.random() * pool.length);
if (seen.has(i)) continue;
seen.add(i);
picked.push({ text: pool[i]!.hitokoto!.trim(), from: pool[i]!.from?.trim() || '' });
}
return picked;
}
Loading
Loading