diff --git a/TrulyMEM.spec b/TrulyMEM.spec new file mode 100644 index 0000000..aacf7ce --- /dev/null +++ b/TrulyMEM.spec @@ -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, +) diff --git a/build.log b/build.log new file mode 100644 index 0000000..15d6333 --- /dev/null +++ b/build.log @@ -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! diff --git a/entry/src/main/ets/pages/GraphPage.ets b/entry/src/main/ets/pages/GraphPage.ets index d1720e4..0b5f950 100644 --- a/entry/src/main/ets/pages/GraphPage.ets +++ b/entry/src/main/ets/pages/GraphPage.ets @@ -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 { + 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%') diff --git a/entry/src/main/ets/services/GraphMemoryService.ets b/entry/src/main/ets/services/GraphMemoryService.ets index dd96ad5..ba7b817 100644 --- a/entry/src/main/ets/services/GraphMemoryService.ets +++ b/entry/src/main/ets/services/GraphMemoryService.ets @@ -748,4 +748,181 @@ export class GraphMemoryService { const graphData: GraphData = { nodes, edges }; return graphData; } + + /** + * 查询节点的完整信息 — 自身属性 + 所有相连关系 + 相邻节点名列表 + */ + async getNodeDetail(nodeName: string): Promise { + 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 { + const graphData = await this.getGraphDataForView(); + + // 计算每个节点的连接度数 + const degreeMap: Record = {}; + 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 { + const graphData = await this.getGraphDataForView(); + + // 计算每个节点的度 + const degreeMap: Record = {}; + 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 } + }; + } } diff --git a/entry/src/main/resources/rawfile/graph.html b/entry/src/main/resources/rawfile/graph.html index 80db78f..446758c 100644 --- a/entry/src/main/resources/rawfile/graph.html +++ b/entry/src/main/resources/rawfile/graph.html @@ -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); } @@ -40,6 +57,23 @@ + +
+ + + + + + +
+
+ + + +
+
@@ -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 = `

${detail.name}

`; + detailHtml += `

类型: ${detail.type}

`; + detailHtml += `

提及次数: ${detail.mention_count}

`; + detailHtml += `

连接数: ${detail.connection_count}

`; + if (detail.connections && detail.connections.length > 0) { + detailHtml += `

连接关系:

    `; + detail.connections.forEach(conn => { + detailHtml += `
  • ${conn.type}: ${conn.target_name}
  • `; + }); + detailHtml += `
`; + } + 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(); diff --git a/full_output.log b/full_output.log new file mode 100644 index 0000000..8c9841b --- /dev/null +++ b/full_output.log @@ -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' +15778 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']! +15778 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' +15784 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' +15927 INFO: Processing pre-safe-import-module hook 'hook-packaging.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module' +16048 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' +16055 INFO: Processing standard module hook 'hook-jaraco.text.py' from '/usr/local/lib/python3.13/dist-packages/_pyinstaller_hooks_contrib/stdhooks' +16088 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' +16089 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' +16094 INFO: Processing pre-safe-import-module hook 'hook-backports.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module' +16094 INFO: Setuptools: 'backports' appears to be a full setuptools-vendored copy - creating alias to 'setuptools._vendor.backports'! +16318 INFO: Processing pre-safe-import-module hook 'hook-tomli.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module' +16318 INFO: Setuptools: 'tomli' appears to be a setuptools-vendored copy - creating alias to 'setuptools._vendor.tomli'! +16606 INFO: Processing standard module hook 'hook-pkg_resources.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks' +16784 INFO: Processing pre-safe-import-module hook 'hook-wheel.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module' +16850 INFO: Processing standard module hook 'hook-setuptools._vendor.importlib_metadata.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks' +16851 INFO: Processing pre-safe-import-module hook 'hook-zipp.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/pre_safe_import_module' +16888 INFO: Analyzing hidden import 'ui.handlers' +16889 INFO: Analyzing hidden import 'ui.services' +16889 INFO: Analyzing hidden import 'ui.services.config_manager' +16890 INFO: Analyzing hidden import 'ui.services.config_service' +16891 INFO: Processing module hooks (post-graph stage)... +17198 WARNING: Hidden import "charset_normalizer.md__mypyc" not found! +18749 INFO: Performing binary vs. data reclassification (622 entries) +18756 INFO: Looking for ctypes DLLs +18829 WARNING: Library ole32 required via ctypes not found +18836 WARNING: Library shell32 required via ctypes not found +18874 INFO: Analyzing run-time hooks ... +18883 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks' +18885 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks' +18886 INFO: Including run-time hook 'pyi_rth_pkgutil.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks' +18887 INFO: Including run-time hook 'pyi_rth_setuptools.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks' +18888 INFO: Including run-time hook 'pyi_rth_pkgres.py' from '/usr/local/lib/python3.13/dist-packages/PyInstaller/hooks/rthooks' +18925 INFO: Creating base_library.zip... +18938 INFO: Looking for dynamic libraries +19222 INFO: Warnings written to /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/warn-trulymem.txt +19336 INFO: Graph cross-reference written to /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/xref-trulymem.html +19370 INFO: checking PYZ +19370 INFO: Building PYZ because PYZ-00.toc is non existent +19370 INFO: Building PYZ (ZlibArchive) /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/PYZ-00.pyz +20340 INFO: Building PYZ (ZlibArchive) /home/program/TrulyMEM-TrueHumanMEM/build/trulymem/PYZ-00.pyz completed successfully. +20361 WARNING: Ignoring icon; supported only on Windows and macOS! +20372 INFO: checking PKG +20372 INFO: Building PKG because PKG-00.toc is non existent +20372 INFO: Building PKG (CArchive) TrulyMEM.pkg +25396 INFO: Building PKG (CArchive) TrulyMEM.pkg completed successfully. +25401 INFO: Bootloader /usr/local/lib/python3.13/dist-packages/PyInstaller/bootloader/Linux-64bit-intel/run +25401 INFO: checking EXE +25401 INFO: Building EXE because EXE-00.toc is non existent +25401 INFO: Building EXE from EXE-00.toc +25401 INFO: Copying bootloader EXE to /home/program/TrulyMEM-TrueHumanMEM/dist/TrulyMEM +25402 INFO: Appending PKG archive to custom ELF section in EXE +25457 INFO: Building EXE from EXE-00.toc completed successfully. +25463 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 06:53 . +drwxr-xr-x 15 root root 4096 Apr 30 06:52 .. +-rwxr-xr-x 1 root root 36696800 Apr 30 06:53 TrulyMEM +Build finished successfully! +total 35M +-rwxr-xr-x 1 root root 35M Apr 30 06:53 TrulyMEM