feat: harmonyos星图全面适配交互功能-搜索/过滤/连接高亮/拖拽/触摸/节点详情

This commit is contained in:
root
2026-04-30 10:24:06 +08:00
parent 45c098d10b
commit d594099f02
6 changed files with 1201 additions and 15 deletions

45
TrulyMEM.spec Normal file
View File

@ -0,0 +1,45 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
datas = [('ui/styles', 'ui/styles'), ('core/prompts/templates', 'core/prompts/templates'), ('static', 'static'), ('templates', 'templates'), ('core/web_api.py', 'core/')]
binaries = []
hiddenimports = ['textual', 'textual.app', 'textual.widgets', 'textual.css', 'openai', 'openai._client', 'neo4j', 'sqlite3', 'core', 'core.embedded_db', 'core.graph_client', 'core.tool_executor', 'core.tool_limiter', 'core.tools', 'core.tools.memory_tools', 'core.prompts', 'core.prompts.prompt_manager', 'core.server', 'core.client', 'core.migrate', 'core.activity_recorder', 'ui', 'ui.app', 'ui.login_screen', 'ui.models', 'ui.models.message', 'ui.models.config', 'ui.models.log_entry', 'ui.widgets', 'ui.handlers', 'ui.services', 'ui.services.config_manager', 'ui.services.config_service', 'core.web_api', 'flask', 'flask_cors', 'werkzeug']
tmp_ret = collect_all('textual')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
a = Analysis(
['trulymem_entry.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='TrulyMEM',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

135
build.log Normal file
View File

@ -0,0 +1,135 @@
===== Building TrulyMEM for Linux =====
Project root: /home/program/TrulyMEM-TrueHumanMEM
The virtual environment was not created successfully because ensurepip is not
available. On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.
apt install python3.13-venv
You may need to use sudo with that command. After installing the python3-venv
package, recreate your virtual environment.
Failing command: /home/program/TrulyMEM-TrueHumanMEM/.venv_build/bin/python3
Warning: venv creation failed, falling back to system Python
Cleaning previous builds...
================================
Building TrulyMEM (TUI + Web embedded)
================================
29 INFO: PyInstaller: 6.20.0, contrib hooks: 2026.4
29 INFO: Python: 3.13.12
31 INFO: Platform: Linux-6.1.0-44-amd64-x86_64-with-glibc2.42
31 INFO: Python environment: /usr
33 INFO: Removing temporary files and cleaning cache in /root/.cache/pyinstaller
34 INFO: Module search paths (PYTHONPATH):
['/home/program/TrulyMEM-TrueHumanMEM',
'/home/program/TrulyMEM-TrueHumanMEM',
'/usr/lib/python313.zip',
'/usr/lib/python3.13',
'/usr/lib/python3.13/lib-dynload',
'/usr/local/lib/python3.13/dist-packages',
'/usr/lib/python3/dist-packages',
'/home/program/TrulyMEM-TrueHumanMEM']
141 INFO: Appending 'datas' from .spec
141 INFO: checking Analysis
141 INFO: Building Analysis because Analysis-00.toc is non existent
141 INFO: Looking for Python shared library...
149 INFO: Using Python shared library: /usr/lib/x86_64-linux-gnu/libpython3.13.so.1.0
149 INFO: Running Analysis Analysis-00.toc
149 INFO: Target bytecode optimization level: 0
149 INFO: Initializing module dependency graph...
149 INFO: Initializing module graph hook caches...
153 INFO: Analyzing modules for base_library.zip ...
645 INFO: Processing standard module hook 'hook-encodings.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
1604 INFO: Processing standard module hook 'hook-pickle.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2203 INFO: Processing standard module hook 'hook-heapq.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2434 INFO: Caching module dependency graph...
2456 INFO: Analyzing /home/program/TrulyMEM-TrueHumanMEM/trulymem_entry.py
2487 INFO: Processing standard module hook 'hook-sqlite3.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2606 INFO: Processing standard module hook 'hook-platform.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2628 INFO: Processing standard module hook 'hook-sysconfig.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2631 INFO: Processing standard module hook 'hook-_ctypes.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2641 INFO: Processing pre-safe-import-module hook 'hook-typing_extensions.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
2641 INFO: SetuptoolsInfo: initializing cached setuptools info...
4602 INFO: Processing standard module hook 'hook-multiprocessing.util.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
4748 INFO: Processing standard module hook 'hook-xml.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
5155 INFO: Processing standard module hook 'hook-pydantic.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
5444 INFO: Processing standard module hook 'hook-rich.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
5729 INFO: Processing standard module hook 'hook-pygments.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
6155 INFO: Processing standard module hook 'hook-chardet.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
7547 INFO: Processing standard module hook 'hook-zoneinfo.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
8697 INFO: Processing standard module hook 'hook-certifi.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
8766 INFO: Processing standard module hook 'hook-anyio.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
9419 INFO: Processing standard module hook 'hook-difflib.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
10529 INFO: Processing standard module hook 'hook-numpy.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
11824 INFO: Processing standard module hook 'hook-charset_normalizer.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
12622 INFO: Processing standard module hook 'hook-pytz.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
13192 INFO: Processing pre-safe-import-module hook 'hook-platformdirs.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
13199 INFO: Processing standard module hook 'hook-platformdirs.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
14917 INFO: Processing standard module hook 'hook-jinja2.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
15284 INFO: Processing pre-safe-import-module hook 'hook-importlib_metadata.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15285 INFO: Setuptools: 'importlib_metadata' appears to be a setuptools-vendored copy - creating alias to 'setuptools._vendor.importlib_metadata'!
15289 INFO: Processing standard module hook 'hook-setuptools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
15296 INFO: Processing pre-safe-import-module hook 'hook-distutils.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15314 INFO: Processing pre-safe-import-module hook 'hook-jaraco.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15314 INFO: Setuptools: 'jaraco' appears to be a partial setuptools-vendored copy - extending search paths to ['/usr/lib/python3/dist-packages/jaraco', '/usr/lib/python3/dist-packages/setuptools/_vendor/jaraco']!
15315 INFO: Processing pre-safe-import-module hook 'hook-jaraco.functools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15320 INFO: Processing pre-safe-import-module hook 'hook-more_itertools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15459 INFO: Processing pre-safe-import-module hook 'hook-packaging.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15575 INFO: Processing pre-safe-import-module hook 'hook-jaraco.text.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15581 INFO: Processing standard module hook 'hook-jaraco.text.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
15614 INFO: Processing pre-safe-import-module hook 'hook-importlib_resources.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15615 INFO: Processing pre-safe-import-module hook 'hook-jaraco.context.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15620 INFO: Processing pre-safe-import-module hook 'hook-backports.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15620 INFO: Setuptools: 'backports' appears to be a full setuptools-vendored copy - creating alias to 'setuptools._vendor.backports'!
15843 INFO: Processing pre-safe-import-module hook 'hook-tomli.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15843 INFO: Setuptools: 'tomli' appears to be a setuptools-vendored copy - creating alias to 'setuptools._vendor.tomli'!
16138 INFO: Processing standard module hook 'hook-pkg_resources.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
16316 INFO: Processing pre-safe-import-module hook 'hook-wheel.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
16376 INFO: Processing standard module hook 'hook-setuptools._vendor.importlib_metadata.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
16378 INFO: Processing pre-safe-import-module hook 'hook-zipp.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
16414 INFO: Analyzing hidden import 'ui.handlers'
16414 INFO: Analyzing hidden import 'ui.services'
16415 INFO: Analyzing hidden import 'ui.services.config_manager'
16416 INFO: Analyzing hidden import 'ui.services.config_service'
16417 INFO: Processing module hooks (post-graph stage)...
16716 WARNING: Hidden import "charset_normalizer.md__mypyc" not found!
18203 INFO: Performing binary vs. data reclassification (622 entries)
18209 INFO: Looking for ctypes DLLs
18283 WARNING: Library shell32 required via ctypes not found
18292 WARNING: Library ole32 required via ctypes not found
18321 INFO: Analyzing run-time hooks ...
18329 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks'
18331 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks'
18333 INFO: Including run-time hook 'pyi_rth_pkgutil.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks'
18334 INFO: Including run-time hook 'pyi_rth_setuptools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks'
18335 INFO: Including run-time hook 'pyi_rth_pkgres.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks'
18371 INFO: Creating base_library.zip...
18384 INFO: Looking for dynamic libraries
18673 INFO: Warnings written to /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/warn-trulymem.txt
18783 INFO: Graph cross-reference written to /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/xref-trulymem.html
18816 INFO: checking PYZ
18816 INFO: Building PYZ because PYZ-00.toc is non existent
18816 INFO: Building PYZ (ZlibArchive) /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/PYZ-00.pyz
19768 INFO: Building PYZ (ZlibArchive) /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/PYZ-00.pyz completed successfully.
19790 WARNING: Ignoring icon; supported only on Windows and macOS!
19801 INFO: checking PKG
19801 INFO: Building PKG because PKG-00.toc is non existent
19801 INFO: Building PKG (CArchive) TrulyMEM.pkg
24763 INFO: Building PKG (CArchive) TrulyMEM.pkg completed successfully.
24768 INFO: Bootloader /usr/local/lib/python3.13/dist-packages/PyInstaller/bootloader/Linux-64bit-intel/run
24768 INFO: checking EXE
24768 INFO: Building EXE because EXE-00.toc is non existent
24768 INFO: Building EXE from EXE-00.toc
24768 INFO: Copying bootloader EXE to /home/program/TrulyMEM-TrueHumanMEM/dist/TrulyMEM
24768 INFO: Appending PKG archive to custom ELF section in EXE
24825 INFO: Building EXE from EXE-00.toc completed successfully.
24830 INFO: Build complete! The results are available in: /home/program/TrulyMEM-TrueHumanMEM/dist
================================
===== Build Complete =====
Binary: dist/TrulyMEM
total 35848
drwxr-xr-x 2 root root 4096 Apr 30 07:06 .
drwxr-xr-x 15 root root 4096 Apr 30 07:05 ..
-rwxr-xr-x 1 root root 36698096 Apr 30 07:06 TrulyMEM
Build finished successfully!

View File

@ -5,6 +5,7 @@
*/
import web_webview from '@ohos.web.webview';
import { GraphDatabase, RecallEntity } from '../model/GraphDatabase';
import { GraphMemoryService } from '../services/GraphMemoryService';
// ========= 图数据结构定义 =========
@ -23,19 +24,46 @@ interface GraphEdgeItem {
relation: string;
}
interface NodeDetailInfo {
name: string;
type: string;
mention_count: number;
connection_count: number;
connections: Array<{ type: string; target_name: string }>;
}
// ========= WebView 原生桥接 =========
class NativeBridge {
private controller: web_webview.WebviewController;
private onRequestGraphData: () => void;
private onNodeClickCallback: (nodeId: number, nodeName: string) => void;
private onSearchCallback: (query: string) => void;
constructor(controller: web_webview.WebviewController, onRequestGraphData: () => void) {
constructor(
controller: web_webview.WebviewController,
onRequestGraphData: () => void,
onNodeClickCallback: (nodeId: number, nodeName: string) => void,
onSearchCallback: (query: string) => void
) {
this.controller = controller;
this.onRequestGraphData = onRequestGraphData;
this.onNodeClickCallback = onNodeClickCallback;
this.onSearchCallback = onSearchCallback;
}
onNodeClick(nodeId: number, nodeName: string): void {
console.info('Node clicked: id=' + nodeId + ', name=' + nodeName);
if (this.onNodeClickCallback) {
this.onNodeClickCallback(nodeId, nodeName);
}
}
onSearch(query: string): void {
console.info('Search from WebView: ' + query);
if (this.onSearchCallback) {
this.onSearchCallback(query);
}
}
requestGraphData(): void {
@ -54,11 +82,18 @@ export struct GraphPage {
@Prop db: GraphDatabase;
@State nodeCount: number = 0;
@State edgeCount: number = 0;
@State selectedNodeDetail: NodeDetailInfo | null = null;
@State showNodeDetail: boolean = false;
@State searchText: string = '';
private graphService: GraphMemoryService = new GraphMemoryService(this.db);
// 初始化桥接对象
private bridge: NativeBridge = new NativeBridge(this.controller, (): void => {
this.pushGraphDataToWebView();
});
private bridge: NativeBridge = new NativeBridge(
this.controller,
(): void => { this.pushGraphDataToWebView(); },
(nodeId: number, nodeName: string): void => { this.handleNodeClick(nodeId, nodeName); },
(query: string): void => { this.handleSearchFromWeb(query); }
);
/**
* 外部触发刷新图数据(聊天写入新记忆后调用)
@ -67,6 +102,46 @@ export struct GraphPage {
await this.pushGraphDataToWebView();
}
/**
* 处理节点点击 - 查询详细信息并显示浮层
*/
private async handleNodeClick(nodeId: number, nodeName: string): Promise<void> {
try {
// 从数据库查询节点详细信息
const detail = await this.graphService.getNodeDetail(nodeName);
if (detail) {
this.selectedNodeDetail = detail;
this.showNodeDetail = true;
}
} catch (err) {
console.error('handleNodeClick error: ' + JSON.stringify(err));
}
}
/**
* 处理来自 WebView 的搜索请求
*/
private handleSearchFromWeb(query: string): void {
this.searchText = query;
}
/**
* 处理搜索输入 - 通知 WebView 过滤
*/
private onSearchInput(value: string): void {
this.searchText = value;
const jsCode = `window.dispatchEvent(new MessageEvent('message', { data: { type: 'search_nodes', query: '${value}' } }));`;
this.controller.runJavaScript(jsCode);
}
/**
* 关闭节点详情浮层
*/
private closeNodeDetail(): void {
this.showNodeDetail = false;
this.selectedNodeDetail = null;
}
/**
* 从数据库读取全量图数据,通过 runJavaScript 推送给 WebView
*/
@ -137,13 +212,15 @@ export struct GraphPage {
}
build() {
Column() {
Stack() {
// WebView 显示 3D 星图
Web({ src: $rawfile('graph.html'), controller: this.controller })
.javaScriptAccess(true)
.width('100%')
.height('100%')
// 页面加载完成后推送数据
.zoomAccess(true) // 启用缩放
// 捏合手势支持 - 双指缩放
.pinchGesture(PinchGesture({ fingers: 2 }))
.onPageEnd(() => {
this.pushGraphDataToWebView();
})
@ -151,10 +228,93 @@ export struct GraphPage {
.javaScriptProxy({
object: this.bridge,
name: 'nativeBridge',
methodList: ['onNodeClick'],
methodList: ['onNodeClick', 'onSearch'],
asyncMethodList: ['requestGraphData'],
controller: this.controller
})
// 搜索框组件 - 在 WebView 上方
Column() {
TextInput({ placeholder: '搜索节点...', text: this.searchText })
.width('80%')
.height(40)
.backgroundColor('rgba(10, 10, 26, 0.8)')
.fontColor('#ffffff')
.placeholderColor('#666688')
.borderRadius(8)
.border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' })
.margin({ top: 20 })
.onChange((value: string) => {
this.onSearchInput(value);
})
}
.width('100%')
.alignContent(Alignment.Top)
.position({ x: 0, y: 0 })
.zIndex(10)
// 节点详情浮层
if (this.showNodeDetail && this.selectedNodeDetail !== null) {
Column() {
Column() {
Text(this.selectedNodeDetail.name)
.fontSize(18)
.fontColor('#44ff88')
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Text('类型: ' + this.selectedNodeDetail.type)
.fontSize(14)
.fontColor('#aaaacc')
Text('提及次数: ' + this.selectedNodeDetail.mention_count)
.fontSize(14)
.fontColor('#aaaacc')
Text('连接数: ' + this.selectedNodeDetail.connection_count)
.fontSize(14)
.fontColor('#aaaacc')
if (this.selectedNodeDetail.connections && this.selectedNodeDetail.connections.length > 0) {
Text('连接关系:')
.fontSize(14)
.fontColor('#8888aa')
.margin({ top: 10, bottom: 5 })
List() {
ForEach(this.selectedNodeDetail.connections, (conn: { type: string; target_name: string }) => {
ListItem() {
Text(conn.type + ': ' + conn.target_name)
.fontSize(12)
.fontColor('#aaaacc')
}
})
}
.height(100)
}
Button('关闭')
.width(80)
.height(30)
.margin({ top: 15 })
.backgroundColor('rgba(100, 100, 255, 0.3)')
.fontColor('#ffffff')
.onClick(() => {
this.closeNodeDetail();
})
}
.padding(20)
.backgroundColor('rgba(10, 10, 26, 0.95)')
.borderRadius(12)
.border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' })
.width(300)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0, 0, 0, 0.5)')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.zIndex(20)
}
}
.width('100%')
.height('100%')

View File

@ -748,4 +748,181 @@ export class GraphMemoryService {
const graphData: GraphData = { nodes, edges };
return graphData;
}
/**
* 查询节点的完整信息 — 自身属性 + 所有相连关系 + 相邻节点名列表
*/
async getNodeDetail(nodeName: string): Promise<NodeDetailInfo | null> {
try {
const recallResult = await this.db.recall(nodeName, [nodeName], 1);
if (recallResult.entities.length === 0) {
return null;
}
const entity = recallResult.entities[0];
const connections: Array<{ type: string; target_name: string }> = [];
let connectionCount = 0;
for (const rel of recallResult.relations) {
if (rel.source === nodeName) {
connections.push({
type: rel.type,
target_name: rel.target
});
connectionCount++;
} else if (rel.target === nodeName) {
connections.push({
type: rel.type + ' (反向)',
target_name: rel.source
});
connectionCount++;
}
}
const detail: NodeDetailInfo = {
name: entity.name,
type: entity.type,
mention_count: entity.mention_count,
connection_count: connectionCount,
connections
};
return detail;
} catch (err) {
console.error('getNodeDetail error: ' + JSON.stringify(err));
return null;
}
}
/**
* 返回带连接度数的图数据(每个节点增加 degree 字段)
*/
async getJoinedData(): Promise<GraphData> {
const graphData = await this.getGraphDataForView();
// 计算每个节点的连接度数
const degreeMap: Record<number, number> = {};
for (const edge of graphData.edges) {
degreeMap[edge.from] = (degreeMap[edge.from] || 0) + 1;
degreeMap[edge.to] = (degreeMap[edge.to] || 0) + 1;
}
// 为节点添加 degree 字段(通过扩展 NodeData
const nodesWithDegree = graphData.nodes.map(node => ({
...node,
degree: degreeMap[node.id] || 0
}));
return { nodes: nodesWithDegree as any[], edges: graphData.edges };
}
}
// ========= NodeDetailInfo 接口定义 =========
interface NodeDetailInfo {
name: string;
type: string;
mention_count: number;
connection_count: number;
connections: Array<{ type: string; target_name: string }>;
}
for (const rel of recallResult.relations) {
const from = nameToId[rel.source];
const to = nameToId[rel.target];
if (from !== undefined && to !== undefined) {
const edge: EdgeData = {
from,
to,
label: rel.type,
weight: rel.confidence
};
edges.push(edge);
}
}
const graphData: GraphData = { nodes, edges };
return graphData;
}
/**
* 获取节点详细信息——自身属性 + 所有相连关系 + 相邻节点名列表
* 供 GraphPage 节点点击后查询用
*/
async getNodeDetail(nodeName: string): Promise<{
name: string;
type: string;
mention_count: number;
connection_count: number;
connections: Array<{ type: string; target_name: string }>;
} | null> {
try {
// 搜索节点
const searchResults = await this.db.search(nodeName);
const node = searchResults.find(n => n.name === nodeName);
if (!node) {
return null;
}
// 查询与该节点相关的所有关系
const recallResult = await this.db.recall(nodeName, [nodeName], 1);
const connections: Array<{ type: string; target_name: string }> = [];
for (const rel of recallResult.relations) {
if (rel.source === nodeName) {
connections.push({ type: rel.type, target_name: rel.target });
} else if (rel.target === nodeName) {
connections.push({ type: rel.type + '(反向)', target_name: rel.source });
}
}
return {
name: node.name,
type: node.type,
mention_count: node.mentions,
connection_count: connections.length,
connections
};
} catch (e) {
console.error('getNodeDetail error: ' + JSON.stringify(e));
return null;
}
}
/**
* 获取带连接度的图数据
* 每个节点增加 degree 字段,供前端根据度调整显示大小
*/
async getJoinedData(): Promise<GraphData & { stats: { maxDegree: number; avgDegree: number } }> {
const graphData = await this.getGraphDataForView();
// 计算每个节点的度
const degreeMap: Record<number, number> = {};
for (const node of graphData.nodes) {
degreeMap[node.id] = 0;
}
for (const edge of graphData.edges) {
degreeMap[edge.from] = (degreeMap[edge.from] || 0) + 1;
degreeMap[edge.to] = (degreeMap[edge.to] || 0) + 1;
}
// 为每个节点附加度
const enrichedNodes: NodeData[] = [];
for (const n of graphData.nodes) {
const enriched: NodeData & { degree: number } = n as (NodeData & { degree: number });
enriched.degree = degreeMap[n.id] || 0;
enrichedNodes.push(enriched);
}
const degrees = Object.values(degreeMap);
const maxDegree = Math.max(...degrees, 1);
const avgDegree = degrees.length > 0
? degrees.reduce((a, b) => a + b, 0) / degrees.length
: 0;
return {
nodes: enrichedNodes,
edges: graphData.edges,
stats: { maxDegree, avgDegree }
};
}
}

View File

@ -19,6 +19,23 @@
#node-info .label { color: #8888aa; }
#loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 20px; color: #4488ff; z-index: 200; }
#nav { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%); display: flex; gap: 20px; z-index: 100; }
/* 搜索框 */
#search-box { position: absolute; top: 80px; left: 20px; z-index: 100; }
#search-input { background: rgba(10, 10, 26, 0.8); border: 1px solid rgba(100, 100, 255, 0.3); color: #ffffff; padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 14px; width: 200px; outline: none; backdrop-filter: blur(10px); }
#search-input::placeholder { color: #666688; }
/* 类型过滤按钮 */
#type-filter { position: absolute; top: 120px; left: 20px; display: flex; gap: 8px; z-index: 100; flex-wrap: wrap; }
.type-btn { background: rgba(10, 10, 26, 0.8); border: 1px solid rgba(100, 100, 255, 0.3); color: #aaaacc; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-family: 'Courier New', monospace; font-size: 12px; backdrop-filter: blur(10px); transition: all 0.3s; }
.type-btn.active { background: rgba(68, 136, 255, 0.3); border-color: #4488ff; color: #ffffff; }
/* 缩放控制按钮 */
#zoom-controls { position: absolute; bottom: 100px; right: 20px; display: flex; flex-direction: column; gap: 10px; z-index: 100; }
.zoom-btn { width: 40px; height: 40px; border-radius: 50%; background: rgba(10, 10, 26, 0.8); border: 1px solid rgba(100, 100, 255, 0.3); color: #ffffff; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px); font-family: 'Courier New', monospace; }
/* 边标签 */
#edge-label { position: absolute; display: none; background: rgba(10, 10, 26, 0.9); color: #44ff88; padding: 4px 8px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 150; border: 1px solid rgba(68, 255, 136, 0.3); }
.nav-btn { background: rgba(10, 10, 26, 0.8); border: 1px solid rgba(100, 100, 255, 0.3); color: #aaaacc; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-family: 'Courier New', monospace; font-size: 14px; backdrop-filter: blur(10px); }
</style>
</head>
@ -40,6 +57,23 @@
<div id="nav">
<button class="nav-btn active">🌌 星图</button>
</div>
<div id="search-box">
<input type="text" id="search-input" placeholder="搜索节点...">
</div>
<div id="type-filter">
<button class="type-btn active" data-type="全部">全部</button>
<button class="type-btn" data-type="Person">Person</button>
<button class="type-btn" data-type="Task">Task</button>
<button class="type-btn" data-type="AI">AI</button>
<button class="type-btn" data-type="Concept">Concept</button>
<button class="type-btn" data-type="Object">Object</button>
</div>
<div id="zoom-controls">
<button class="zoom-btn" id="zoom-in">+</button>
<button class="zoom-btn" id="zoom-out">-</button>
<button class="zoom-btn" id="zoom-reset">R</button>
</div>
<div id="edge-label"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
@ -52,6 +86,13 @@
let hoveredNode = null, selectedNode = null;
let animationId;
let highlightPulse = 0;
let searchTerm = '';
let activeTypeFilter = '全部';
let isDragging = false;
let dragNode = null;
let originalPhysicsState = true;
let edgeLabelEl = null;
let nodePositions = {}; // 存储节点位置用于拖拽
const typeColors = { 'person': 0x4488ff, 'task': 0xff8844, 'ai': 0xaa44ff, 'concept': 0x44ff88, 'object': 0xff4444 };
const defaultColor = 0xcccccc;
@ -88,13 +129,72 @@
window.__graphData = event.data.payload;
loadGraphData();
}
if (event.data.type === 'search_nodes') {
searchTerm = event.data.query || '';
document.getElementById('search-input').value = searchTerm;
applyFilters();
}
if (event.data.type === 'node_detail') {
showNodeDetailPanel(event.data.detail);
}
if (event.data.type === 'highlight_node') {
highlightNodeById(event.data.nodeId);
}
});
// 搜索输入框事件
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', (e) => {
searchTerm = e.target.value.toLowerCase();
applyFilters();
// 通知 ArkTS
try {
if (window.nativeBridge && window.nativeBridge.onSearch) {
window.nativeBridge.onSearch(searchTerm);
}
} catch(e) {}
});
// 类型过滤按钮事件
document.querySelectorAll('.type-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeTypeFilter = btn.dataset.type;
applyFilters();
});
});
// 缩放控制按钮
document.getElementById('zoom-in').addEventListener('click', () => {
camera.position.multiplyScalar(0.8);
controls.update();
});
document.getElementById('zoom-out').addEventListener('click', () => {
camera.position.multiplyScalar(1.2);
controls.update();
});
document.getElementById('zoom-reset').addEventListener('click', () => {
if (Object.keys(nodePositions).length > 0) {
const allPositions = Object.values(nodePositions);
let maxDist = 0;
allPositions.forEach(pos => { maxDist = Math.max(maxDist, Math.sqrt(pos.x*pos.x + pos.y*pos.y + pos.z*pos.z)); });
camera.position.set(maxDist * 2.2, maxDist * 1.5, maxDist * 2.2);
controls.target.set(0, 0, 0);
controls.update();
}
});
// 边标签元素
edgeLabelEl = document.getElementById('edge-label');
// 鼠标移动检测边悬停
renderer.domElement.addEventListener('mousemove', onEdgeHoverCheck);
// 通知 ArkTS 请求图数据
try {
if (window.nativeBridge && window.nativeBridge.requestGraphData) {
window.nativeBridge.requestGraphData();
}
} catch(e) {}
// 触摸事件支持
initTouchEvents();
// 初始化拖拽功能
initDragFunctionality();
animate();
}
@ -180,21 +280,22 @@
nodeDegrees[e.target] = (nodeDegrees[e.target] || 0) + 1;
});
const positions = {};
nodePositions = positions; // 存储供拖拽使用
const maxDegree = Math.max(...Object.values(nodeDegrees), 1);
nodes.forEach((node, i) => {
const angle = (i / nodes.length) * Math.PI * 2;
const radius = 15 + (nodeDegrees[node.id] / maxDegree) * 35;
positions[node.id] = { x: radius * Math.cos(angle), y: (Math.random() - 0.5) * 10, z: radius * Math.sin(angle) };
});
for (let iter = 0; iter < 50; iter++) {
for (let iter = 0; iter < 200; iter++) {
Object.keys(positions).forEach(id1 => {
Object.keys(positions).forEach(id2 => {
if (id1 >= id2) return;
const pos1 = positions[id1], pos2 = positions[id2];
const dx = pos1.x - pos2.x, dy = pos1.y - pos2.y, dz = pos1.z - pos2.z;
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz) + 0.1;
if (dist < 10) {
const force = 0.1 / dist;
if (dist < 20) { // 增加排斥距离
const force = 0.3 / (dist * dist); // 增加排斥力系数(平方反比)
pos1.x += (dx/dist)*force; pos1.y += (dy/dist)*force; pos1.z += (dz/dist)*force;
pos2.x -= (dx/dist)*force; pos2.y -= (dy/dist)*force; pos2.z -= (dz/dist)*force;
}
@ -206,7 +307,7 @@
const dx = pos2.x - pos1.x, dy = pos2.y - pos1.y, dz = pos2.z - pos1.z;
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz) + 0.1;
if (dist > 15) {
const force = 0.05;
const force = 0.08; // 增加吸引力
pos1.x += (dx/dist)*force; pos1.y += (dy/dist)*force; pos1.z += (dz/dist)*force;
pos2.x -= (dx/dist)*force; pos2.y -= (dy/dist)*force; pos2.z -= (dz/dist)*force;
}
@ -218,23 +319,27 @@
const radius = 0.4 + Math.min(node.mention_count * 0.15, 1.5);
const color = typeColors[node.type] || defaultColor;
const geometry = new THREE.SphereGeometry(radius, 16, 12);
const material = new THREE.MeshPhongMaterial({ color: color, emissive: color, emissiveIntensity: 0.5 + Math.min(node.mention_count * 0.05, 0.3), shininess: 30 });
const material = new THREE.MeshPhongMaterial({ color: color, emissive: color, emissiveIntensity: 0.5 + Math.min(node.mention_count * 0.05, 0.3), shininess: 30, transparent: true, opacity: 0 });
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set(pos.x, pos.y, pos.z);
sphere.userData = { nodeId: node.id, nodeData: node };
scene.add(sphere);
nodeMeshes.push(sphere);
// 淡入动画
fadeInObject(sphere, 500);
});
edges.forEach(edge => {
const pos1 = positions[edge.source], pos2 = positions[edge.target];
if (!pos1 || !pos2) return;
const color = edgeColors[edge.relation_type] || defaultEdgeColor;
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(pos1.x, pos1.y, pos1.z), new THREE.Vector3(pos2.x, pos2.y, pos2.z)]);
const material = new THREE.LineBasicMaterial({ color: color, transparent: true, opacity: 0.4, linewidth: 1 });
const material = new THREE.LineBasicMaterial({ color: color, transparent: true, opacity: 0, linewidth: 1 });
const line = new THREE.Line(geometry, material);
line.userData = { edgeId: edge.id, edgeData: edge };
scene.add(line);
edgeLines.push(line);
// 淡入动画
fadeInLine(line, 500);
});
const allPositions = Object.values(positions);
if (allPositions.length > 0) {
@ -265,14 +370,19 @@
}
function onMouseClick(event) {
if (isDragging) return;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(nodeMeshes);
if (intersects.length > 0) {
const node = intersects[0].object;
if (selectedNode === node) { selectedNode = null; document.getElementById('node-info').style.display = 'none'; }
else {
if (selectedNode === node) {
selectedNode = null;
document.getElementById('node-info').style.display = 'none';
resetHighlight();
} else {
selectedNode = node;
showNodeInfo(node.userData.nodeData, true);
highlightNodeConnections(node.userData.nodeData);
// 通知 ArkTS 节点被点击
try {
if (window.nativeBridge && window.nativeBridge.onNodeClick) {
@ -280,6 +390,11 @@
}
} catch(e) {}
}
} else {
// 点击空白处恢复
selectedNode = null;
document.getElementById('node-info').style.display = 'none';
resetHighlight();
}
}
@ -294,6 +409,339 @@
function hideNodeInfo() { if (!selectedNode) document.getElementById('node-info').style.display = 'none'; }
// 淡入动画
function fadeInObject(obj, duration) {
const startOpacity = 0;
const endOpacity = obj.material.opacity !== undefined ? (obj.material.transparent ? obj.material.opacity : 1) : 1;
obj.material.opacity = startOpacity;
obj.material.transparent = true;
const startTime = Date.now();
function animateFade() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
obj.material.opacity = startOpacity + (endOpacity - startOpacity) * progress;
if (progress < 1) {
requestAnimationFrame(animateFade);
} else {
obj.material.transparent = endOpacity < 1;
}
}
animateFade();
}
function fadeInLine(line, duration) {
const startOpacity = 0;
const endOpacity = 0.4;
line.material.opacity = startOpacity;
const startTime = Date.now();
function animateFade() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
line.material.opacity = startOpacity + (endOpacity - startOpacity) * progress;
if (progress < 1) {
requestAnimationFrame(animateFade);
}
}
animateFade();
}
// 通过节点ID高亮节点供ArkTS调用
function highlightNodeById(nodeId) {
const mesh = nodeMeshes.find(m => m.userData.nodeData.id === nodeId);
if (mesh) {
selectedNode = mesh;
showNodeInfo(mesh.userData.nodeData, true);
highlightNodeConnections(mesh.userData.nodeData);
}
}
// 显示节点详情面板供ArkTS调用
function showNodeDetailPanel(detail) {
// 更新node-info面板显示详细信息
document.getElementById('info-name').textContent = detail.name;
document.getElementById('info-type').textContent = detail.type;
document.getElementById('info-mentions').textContent = detail.mention_count;
document.getElementById('info-links').textContent = detail.connection_count;
document.getElementById('node-info').style.display = 'block';
// 如果有连接信息,添加到面板
let detailHtml = `<h3>${detail.name}</h3>`;
detailHtml += `<p><span class="label">类型:</span> ${detail.type}</p>`;
detailHtml += `<p><span class="label">提及次数:</span> ${detail.mention_count}</p>`;
detailHtml += `<p><span class="label">连接数:</span> ${detail.connection_count}</p>`;
if (detail.connections && detail.connections.length > 0) {
detailHtml += `<p><span class="label">连接关系:</span></p><ul style="margin-left: 15px; font-size: 12px;">`;
detail.connections.forEach(conn => {
detailHtml += `<li>${conn.type}: ${conn.target_name}</li>`;
});
detailHtml += `</ul>`;
}
document.getElementById('node-info').innerHTML = detailHtml;
}
// 节点拖拽功能
function initDragFunctionality() {
let dragStartPos = { x: 0, y: 0 };
renderer.domElement.addEventListener('mousedown', (event) => {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(nodeMeshes);
if (intersects.length > 0) {
isDragging = false;
dragNode = intersects[0].object;
dragStartPos = { x: event.clientX, y: event.clientY };
originalPhysicsState = true; // 暂停物理模拟
}
});
renderer.domElement.addEventListener('mousemove', (event) => {
if (dragNode) {
const dx = event.clientX - dragStartPos.x;
const dy = event.clientY - dragStartPos.y;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
isDragging = true;
}
if (isDragging) {
// 将屏幕坐标转换为3D空间
const rect = renderer.domElement.getBoundingClientRect();
const mouseX = ((event.clientX - rect.left) / rect.width) * 2 - 1;
const mouseY = -((event.clientY - rect.top) / rect.height) * 2 + 1;
const vector = new THREE.Vector3(mouseX, mouseY, 0.5);
vector.unproject(camera);
const dir = vector.sub(camera.position).normalize();
const distance = -camera.position.z / dir.z;
const newPos = camera.position.clone().add(dir.multiplyScalar(distance));
dragNode.position.copy(newPos);
// 更新存储的位置
if (nodePositions[dragNode.userData.nodeId]) {
nodePositions[dragNode.userData.nodeId] = { x: newPos.x, y: newPos.y, z: newPos.z };
}
}
}
});
renderer.domElement.addEventListener('mouseup', () => {
if (dragNode) {
// 恢复物理模拟
dragNode = null;
isDragging = false;
}
});
}
// Touch事件支持
function initTouchEvents() {
let touchStart = null;
let touchStartDistance = 0;
let touchStartPos = { x: 0, y: 0 };
let isTouchDrag = false;
renderer.domElement.addEventListener('touchstart', (event) => {
event.preventDefault();
if (event.touches.length === 1) {
const touch = event.touches[0];
touchStartPos = { x: touch.clientX, y: touch.clientY };
isTouchDrag = false;
// 模拟鼠标事件用于射线检测
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((touch.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((touch.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(nodeMeshes);
if (intersects.length > 0) {
const node = intersects[0].object;
if (selectedNode === node) {
selectedNode = null;
document.getElementById('node-info').style.display = 'none';
resetHighlight();
} else {
selectedNode = node;
showNodeInfo(node.userData.nodeData, true);
highlightNodeConnections(node.userData.nodeData);
try {
if (window.nativeBridge && window.nativeBridge.onNodeClick) {
window.nativeBridge.onNodeClick(node.userData.nodeData.id, node.userData.nodeData.name);
}
} catch(e) {}
}
}
} else if (event.touches.length === 2) {
// 双指缩放
const dx = event.touches[0].clientX - event.touches[1].clientX;
const dy = event.touches[0].clientY - event.touches[1].clientY;
touchStartDistance = Math.sqrt(dx*dx + dy*dy);
}
}, { passive: false });
renderer.domElement.addEventListener('touchmove', (event) => {
event.preventDefault();
if (event.touches.length === 1 && controls) {
const touch = event.touches[0];
const dx = touch.clientX - touchStartPos.x;
const dy = touch.clientY - touchStartPos.y;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
isTouchDrag = true;
}
// 模拟OrbitControls的鼠标移动
if (isTouchDrag) {
const rotateSpeed = 0.005;
controls.rotateLeft(-dx * rotateSpeed);
controls.rotateUp(-dy * rotateSpeed);
controls.update();
touchStartPos = { x: touch.clientX, y: touch.clientY };
}
} else if (event.touches.length === 2 && controls) {
// 双指缩放
const dx = event.touches[0].clientX - event.touches[1].clientX;
const dy = event.touches[0].clientY - event.touches[1].clientY;
const distance = Math.sqrt(dx*dx + dy*dy);
const scale = touchStartDistance / distance;
camera.position.multiplyScalar(scale);
controls.update();
touchStartDistance = distance;
}
}, { passive: false });
renderer.domElement.addEventListener('touchend', (event) => {
if (event.touches.length === 0) {
isTouchDrag = false;
}
}, { passive: false });
}
// 搜索和类型过滤
function applyFilters() {
nodeMeshes.forEach(mesh => {
const nodeData = mesh.userData.nodeData;
const nameMatch = nodeData.name.toLowerCase().includes(searchTerm);
const typeMatch = activeTypeFilter === '全部' || nodeData.type === activeTypeFilter;
if (nameMatch && typeMatch) {
mesh.material.transparent = false;
mesh.material.opacity = 1;
mesh.scale.setScalar(1);
} else {
mesh.material.transparent = true;
mesh.material.opacity = 0.2;
mesh.scale.setScalar(0.8);
}
});
// 同时过滤边
edgeLines.forEach(line => {
const edgeData = line.userData.edgeData;
const sourceNode = nodes.find(n => n.id === edgeData.source);
const targetNode = nodes.find(n => n.id === edgeData.target);
const sourceMatch = sourceNode && sourceNode.name.toLowerCase().includes(searchTerm) && (activeTypeFilter === '全部' || sourceNode.type === activeTypeFilter);
const targetMatch = targetNode && targetNode.name.toLowerCase().includes(searchTerm) && (activeTypeFilter === '全部' || targetNode.type === activeTypeFilter);
if ((sourceMatch || targetMatch) && searchTerm === '' && activeTypeFilter === '全部') {
line.material.transparent = true;
line.material.opacity = 0.4;
} else if (sourceMatch || targetMatch) {
line.material.transparent = true;
line.material.opacity = 0.6;
} else {
line.material.transparent = true;
line.material.opacity = 0.1;
}
});
}
// 连接高亮
function highlightNodeConnections(nodeData) {
const connectedNodeIds = new Set();
const connectedEdgeIds = new Set();
// 找出所有连接的节点和边
edges.forEach(edge => {
if (edge.source === nodeData.id || edge.target === nodeData.id) {
connectedNodeIds.add(edge.source);
connectedNodeIds.add(edge.target);
connectedEdgeIds.add(edge.id);
}
});
nodeMeshes.forEach(mesh => {
const meshNodeId = mesh.userData.nodeData.id;
if (meshNodeId === nodeData.id) {
// 选中的节点
mesh.material.emissiveIntensity = 1.0;
mesh.scale.setScalar(1.5);
} else if (connectedNodeIds.has(meshNodeId)) {
// 直接连接的节点
mesh.material.emissiveIntensity = 0.8;
mesh.scale.setScalar(1.3);
mesh.material.transparent = false;
mesh.material.opacity = 1;
} else {
// 无关系的节点
mesh.material.transparent = true;
mesh.material.opacity = 0.15;
mesh.material.emissiveIntensity = 0.2;
mesh.scale.setScalar(0.9);
}
});
edgeLines.forEach(line => {
if (connectedEdgeIds.has(line.userData.edgeData.id)) {
line.material.transparent = true;
line.material.opacity = 0.8;
line.material.linewidth = 2;
} else {
line.material.transparent = true;
line.material.opacity = 0.1;
line.material.linewidth = 1;
}
});
}
function resetHighlight() {
nodeMeshes.forEach(mesh => {
const nodeData = mesh.userData.nodeData;
const nameMatch = nodeData.name.toLowerCase().includes(searchTerm);
const typeMatch = activeTypeFilter === '全部' || nodeData.type === activeTypeFilter;
mesh.material.emissiveIntensity = 0.5 + Math.min(nodeData.mention_count * 0.05, 0.3);
if (nameMatch && typeMatch) {
mesh.material.transparent = false;
mesh.material.opacity = 1;
mesh.scale.setScalar(1);
} else {
mesh.material.transparent = true;
mesh.material.opacity = 0.2;
mesh.scale.setScalar(0.8);
}
});
edgeLines.forEach(line => {
line.material.transparent = true;
line.material.opacity = 0.4;
line.material.linewidth = 1;
});
}
// 边标签显示
function onEdgeHoverCheck(event) {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
// 检查边悬停
const edgeIntersects = raycaster.intersectObjects(edgeLines);
if (edgeIntersects.length > 0) {
const edge = edgeIntersects[0].object;
const edgeData = edge.userData.edgeData;
edgeLabelEl.textContent = edgeData.relation_type || '关系';
edgeLabelEl.style.display = 'block';
edgeLabelEl.style.left = (event.clientX - rect.left + 10) + 'px';
edgeLabelEl.style.top = (event.clientY - rect.top - 10) + 'px';
} else {
edgeLabelEl.style.display = 'none';
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

221
full_output.log Normal file
View File

@ -0,0 +1,221 @@
Reading package lists...
Building dependency tree...
Reading state information...
python3 is already the newest version (3.13.9-3).
python3 set to manually installed.
python3-pip is already the newest version (26.0.1+dfsg-1).
You might want to run 'apt --fix-broken install' to correct these.
The following packages have unmet dependencies:
libxfont2 : Depends: libfontenc1 (>= 1:1.1.8) but 1:1.1.4-1 is to be installed
python3-venv : Depends: python3.13-venv (>= 3.13.5-1~) but it is not going to be installed
Depends: python3 (= 3.13.5-1) but 3.13.9-3 is to be installed
xserver-common : Depends: x11-xkb-utils but it is not going to be installed
Recommends: xfonts-base but it is not going to be installed
E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
Correcting dependencies... Done
Solving dependencies...
Upgrading:
libfontenc1 libxt-dev
Installing dependencies:
libxt6t64 x11-xkb-utils
REMOVING:
libxt6
apt-listchanges: Reading changelogs...
dpkg-preconfigure: unable to re-open stdin: No such file or directory
Summary:
Upgrading: 2, Installing: 2, Removing: 1, Not Upgrading: 708
Download size: 0 B / 778 kB
Space needed: 526 kB / 419 GB available
(Reading database…
(Reading database… 5%
(Reading database… 10%
(Reading database… 15%
(Reading database… 20%
(Reading database… 25%
(Reading database… 30%
(Reading database… 35%
(Reading database… 40%
(Reading database… 45%
(Reading database… 50%
(Reading database… 55%
(Reading database… 60%
(Reading database… 65%
(Reading database… 70%
(Reading database… 75%
(Reading database… 80%
(Reading database… 85%
(Reading database… 90%
(Reading database… 95%
(Reading database… 100%
(Reading database… 103500 files and directories currently installed.)
Preparing to unpack …/libxt-dev_1%3a1.2.1-1.2+b2_amd64.deb…
Unpacking libxt-dev:amd64 (1:1.2.1-1.2+b2) over (1:1.2.1-1.1)…
dpkg: libxt6:amd64: dependency problems, but removing anyway as you requested:
x11-xserver-utils depends on libxt6.
x11-utils depends on libxt6 (>= 1:1.1.0).
libxmu6:amd64 depends on libxt6.
libxaw7:amd64 depends on libxt6.
libgs10:amd64 depends on libxt6.
(Reading database…
(Reading database… 5%
(Reading database… 10%
(Reading database… 15%
(Reading database… 20%
(Reading database… 25%
(Reading database… 30%
(Reading database… 35%
(Reading database… 40%
(Reading database… 45%
(Reading database… 50%
(Reading database… 55%
(Reading database… 60%
(Reading database… 65%
(Reading database… 70%
(Reading database… 75%
(Reading database… 80%
(Reading database… 85%
(Reading database… 90%
(Reading database… 95%
(Reading database… 100%
(Reading database… 103501 files and directories currently installed.)
Removing libxt6:amd64 (1:1.2.1-1.1)…
Selecting previously unselected package libxt6t64:amd64.
(Reading database…
(Reading database… 5%
(Reading database… 10%
(Reading database… 15%
(Reading database… 20%
(Reading database… 25%
(Reading database… 30%
(Reading database… 35%
(Reading database… 40%
(Reading database… 45%
(Reading database… 50%
(Reading database… 55%
(Reading database… 60%
(Reading database… 65%
(Reading database… 70%
(Reading database… 75%
(Reading database… 80%
(Reading database… 85%
(Reading database… 90%
(Reading database… 95%
(Reading database… 100%
(Reading database… 103495 files and directories currently installed.)
Preparing to unpack …/libxt6t64_1%3a1.2.1-1.2+b2_amd64.deb…
Unpacking libxt6t64:amd64 (1:1.2.1-1.2+b2)…
Preparing to unpack …/libfontenc1_1%3a1.1.8-1+b2_amd64.deb…
Unpacking libfontenc1:amd64 (1:1.1.8-1+b2) over (1:1.1.4-1)…
Selecting previously unselected package x11-xkb-utils.
Preparing to unpack …/x11-xkb-utils_7.7+9_amd64.deb…
Unpacking x11-xkb-utils (7.7+9)…
Setting up libfontenc1:amd64 (1:1.1.8-1+b2)…
Setting up libxt6t64:amd64 (1:1.2.1-1.2+b2)…
Setting up x11-xkb-utils (7.7+9)…
Setting up libxt-dev:amd64 (1:1.2.1-1.2+b2)…
Processing triggers for man-db (2.11.2-2)…
Processing triggers for libc-bin (2.42-14)…
needrestart is being skipped since dpkg has failed
Reading package lists...
Building dependency tree...
Reading state information...
Solving dependencies...
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:
The following packages have unmet dependencies:
python3-venv : Depends: python3.13-venv (>= 3.13.5-1~) but it is not going to be installed
Depends: python3 (= 3.13.5-1) but 3.13.9-3 is to be installed
E: Unable to satisfy dependencies. Reached two conflicting assignments:
1. python3-venv:amd64=3.13.5-1 is selected for install
2. python3-venv:amd64=3.13.5-1 Depends python3 (= 3.13.5-1)
but none of the choices are installable:
- python3:amd64=3.13.5-1 is not selected for install
===== Building TrulyMEM for Linux =====
Project root: /home/program/TrulyMEM-TrueHumanMEM
The virtual environment was not created successfully because ensurepip is not
available. On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.
apt install python3.13-venv
You may need to use sudo with that command. After installing the python3-venv
package, recreate your virtual environment.
Failing command: /home/program/TrulyMEM-TrueHumanMEM/.venv_build/bin/python3
Warning: venv creation failed, falling back to system Python
Cleaning previous builds...
================================
Building TrulyMEM (TUI + Web embedded)
================================
31 INFO: PyInstaller: 6.20.0, contrib hooks: 2026.4
31 INFO: Python: 3.13.12
33 INFO: Platform: Linux-6.1.0-44-amd64-x86_64-with-glibc2.42
33 INFO: Python environment: /usr
36 INFO: Removing temporary files and cleaning cache in /root/.cache/pyinstaller
37 INFO: Module search paths (PYTHONPATH):
['/home/program/TrulyMEM-TrueHumanMEM',
'/home/program/TrulyMEM-TrueHumanMEM',
'/usr/lib/python313.zip',
'/usr/lib/python3.13',
'/usr/lib/python3.13/lib-dynload',
'/usr/local/lib/python3.13/dist-packages',
'/usr/lib/python3/dist-packages',
'/home/program/TrulyMEM-TrueHumanMEM']
158 INFO: Appending 'datas' from .spec
158 INFO: checking Analysis
158 INFO: Building Analysis because Analysis-00.toc is non existent
159 INFO: Looking for Python shared library...
166 INFO: Using Python shared library: /usr/lib/x86_64-linux-gnu/libpython3.13.so.1.0
166 INFO: Running Analysis Analysis-00.toc
166 INFO: Target bytecode optimization level: 0
166 INFO: Initializing module dependency graph...
166 INFO: Initializing module graph hook caches...
170 INFO: Analyzing modules for base_library.zip ...
651 INFO: Processing standard module hook 'hook-encodings.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
943 INFO: Processing standard module hook 'hook-heapq.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
1656 INFO: Processing standard module hook 'hook-pickle.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2496 INFO: Caching module dependency graph...
2519 INFO: Analyzing /home/program/TrulyMEM-TrueHumanMEM/trulymem_entry.py
2554 INFO: Processing standard module hook 'hook-sqlite3.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2678 INFO: Processing standard module hook 'hook-platform.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2702 INFO: Processing standard module hook 'hook-sysconfig.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2706 INFO: Processing standard module hook 'hook-_ctypes.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
2716 INFO: Processing pre-safe-import-module hook 'hook-typing_extensions.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
2717 INFO: SetuptoolsInfo: initializing cached setuptools info...
4768 INFO: Processing standard module hook 'hook-multiprocessing.util.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
4924 INFO: Processing standard module hook 'hook-xml.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
5338 INFO: Processing standard module hook 'hook-pydantic.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
5622 INFO: Processing standard module hook 'hook-rich.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
5908 INFO: Processing standard module hook 'hook-pygments.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
6310 INFO: Processing standard module hook 'hook-chardet.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
7729 INFO: Processing standard module hook 'hook-zoneinfo.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
8919 INFO: Processing standard module hook 'hook-certifi.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
8990 INFO: Processing standard module hook 'hook-anyio.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
9647 INFO: Processing standard module hook 'hook-difflib.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
10808 INFO: Processing standard module hook 'hook-numpy.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
12163 INFO: Processing standard module hook 'hook-charset_normalizer.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
12999 INFO: Processing standard module hook 'hook-pytz.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
13591 INFO: Processing pre-safe-import-module hook 'hook-platformdirs.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
13598 INFO: Processing standard module hook 'hook-platformdirs.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
15377 INFO: Processing standard module hook 'hook-jinja2.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks'
15747 INFO: Processing pre-safe-import-module hook 'hook-importlib_metadata.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15747 INFO: Setuptools: 'importlib_metadata' appears to be a setuptools-vendored copy - creating alias to 'setuptools._vendor.importlib_metadata'!
15752 INFO: Processing standard module hook 'hook-setuptools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks'
15759 INFO: Processing pre-safe-import-module hook 'hook-distutils.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'
15778 INFO: Processing pre-safe-import-module hook 'hook-jaraco.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module'