refactor: move harmonyos project to harmony/ directory

This commit is contained in:
root
2026-04-28 16:32:04 +08:00
parent 2a42183aaa
commit e83151bd1e
120 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,10 @@
{
"app": {
"bundleName": "com.trulymem.app",
"vendor": "trulymem",
"versionCode": 1000001,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" rx="30" fill="#6366f1"/>
<text x="100" y="130" font-family="Arial" font-size="80" fill="white" text-anchor="middle" font-weight="bold">T</text>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,47 @@
{
"app": {
"products": [
{
"name": "default",
"targetSdkVersion": "6.1.0(23)",
"compatibleSdkVersion": "6.1.0(23)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{
"name": "debug"
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
},
{
"name": "trulymem-core",
"srcPath": "./trulymem-core",
"targets": [
{
"name": "default"
}
]
}
]
}

32
harmony/code-linter.json5 Normal file
View File

@ -0,0 +1,32 @@
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@security/no-unsafe-aes": "error",
"@security/no-unsafe-hash": "error",
"@security/no-unsafe-mac": "warn",
"@security/no-unsafe-dh": "error",
"@security/no-unsafe-dsa": "error",
"@security/no-unsafe-ecdsa": "error",
"@security/no-unsafe-rsa-encrypt": "error",
"@security/no-unsafe-rsa-sign": "error",
"@security/no-unsafe-rsa-key": "error",
"@security/no-unsafe-dsa-key": "error",
"@security/no-unsafe-dh-key": "error",
"@security/no-unsafe-3des": "error"
}
}

View File

@ -0,0 +1,38 @@
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
},
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"buildMode":"Debug"}

View File

@ -0,0 +1 @@
{"hspPkgNames":[],"compileEntries":["&entry/build/generated/r/ResourceTable&","&entry/src/main/ets/entryability/EntryAbility&","&entry/src/main/ets/pages/Index&"],"updateVersionInfo":{}}

View File

@ -0,0 +1 @@
{"resolveConflictMode":true}

View File

@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=ResourceTable.js.map

View File

@ -0,0 +1,30 @@
import type AbilityConstant from "@ohos:app.ability.AbilityConstant";
import UIAbility from "@ohos:app.ability.UIAbility";
import type Window from "@ohos:window";
import type Want from "@ohos:app.ability.Want";
export default class EntryAbility extends UIAbility {
onCreate(want: Want, param: AbilityConstant.LaunchParam): void {
console.info('EntryAbility onCreate');
}
onDestroy(): void {
console.info('EntryAbility onDestroy');
}
onWindowStageCreate(windowStage: Window.WindowStage): void {
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
console.error('Failed to load content: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading content: ' + JSON.stringify(data));
});
}
onWindowStageDestroy(): void {
console.info('EntryAbility onWindowStageDestroy');
}
onForeground(): void {
console.info('EntryAbility onForeground');
}
onBackground(): void {
console.info('EntryAbility onBackground');
}
}

View File

@ -0,0 +1,222 @@
import relationalStore from "@ohos:data.relationalStore";
import type { Context } from "@ohos:abilityAccessCtrl";
interface TripletData {
subject: string;
relation: string;
object: string;
}
interface CriteriaData {
subject?: string;
target?: string;
relation?: string;
sessionId?: string;
}
interface NodeData {
id: number;
label: string;
type: string;
mentions: number;
}
interface EdgeData {
from: number;
to: number;
label: string;
weight: number;
}
interface GraphData {
nodes: NodeData[];
edges: EdgeData[];
}
interface RecallResult {
data: GraphData;
}
interface ChatMessage {
role: string;
content: string;
}
interface CleanupResult {
cleaned: number;
}
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'trulymem.db',
securityLevel: relationalStore.SecurityLevel.S1
};
export class GraphDatabase {
private store?: relationalStore.RdbStore;
private context?: Context;
async init(context: Context): Promise<void> {
this.context = context;
this.store = await relationalStore.getRdbStore(context, STORE_CONFIG);
await this.createTables();
}
private async createTables(): Promise<void> {
if (!this.store)
return;
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
type TEXT DEFAULT 'concept',
mentions INTEGER DEFAULT 1,
created_at TEXT,
updated_at TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subject_id INTEGER NOT NULL,
relation TEXT NOT NULL,
object_id INTEGER NOT NULL,
weight REAL DEFAULT 1.0,
created_at TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS chat_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
content TEXT NOT NULL,
tools TEXT,
created_at TEXT
)
`);
}
async commit(triplets: TripletData[]): Promise<void> {
if (!this.store)
return;
for (const triplet of triplets) {
const subjectId: number = await this.upsertNode(triplet.subject);
const objectId: number = await this.upsertNode(triplet.object);
const bucket: relationalStore.ValuesBucket = {
'subject_id': subjectId,
'relation': triplet.relation,
'object_id': objectId,
'created_at': new Date().toISOString()
};
await this.store.insert('relations', bucket);
}
}
private async upsertNode(name: string): Promise<number> {
if (!this.store)
return -1;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
predicates.equalTo('name', name);
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id']);
if (resultSet.goToNextRow()) {
const id: number = resultSet.getLong(resultSet.getColumnIndex('id'));
resultSet.close();
return id;
}
resultSet.close();
const bucket: relationalStore.ValuesBucket = {
'name': name,
'type': 'concept',
'created_at': new Date().toISOString(),
'updated_at': new Date().toISOString()
};
return await this.store.insert('nodes', bucket);
}
async recall(queryIntent: string, seedEntities?: string[]): Promise<RecallResult> {
if (!this.store) {
const empty: RecallResult = { data: { nodes: [], edges: [] } };
return empty;
}
const nodes: NodeData[] = await this.getAllNodes();
const edges: EdgeData[] = await this.getAllEdges();
const result: RecallResult = { data: { nodes, edges } };
return result;
}
async purge(criteria: CriteriaData): Promise<void> {
if (!this.store)
return;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
await this.store.delete(predicates);
}
async introspect(): Promise<RecallResult> {
const nodes: NodeData[] = await this.getAllNodes();
const edges: EdgeData[] = await this.getAllEdges();
const result: RecallResult = { data: { nodes, edges } };
return result;
}
async archive(days: number): Promise<void> {
// Archive old data - placeholder
}
async cleanup(dryRun: boolean): Promise<CleanupResult> {
const result: CleanupResult = { cleaned: 0 };
return result;
}
async saveChatMessage(role: string, content: string, tools?: string): Promise<void> {
if (!this.store)
return;
const bucket: relationalStore.ValuesBucket = {
'role': role,
'content': content,
'tools': tools || null,
'created_at': new Date().toISOString()
};
await this.store.insert('chat_records', bucket);
}
async getChatHistory(limit?: number): Promise<ChatMessage[]> {
if (!this.store)
return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records');
predicates.orderByDesc('created_at');
if (limit) {
predicates.limitAs(limit);
}
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['role', 'content']);
const messages: ChatMessage[] = [];
while (resultSet.goToNextRow()) {
const msg: ChatMessage = {
role: resultSet.getString(resultSet.getColumnIndex('role')),
content: resultSet.getString(resultSet.getColumnIndex('content'))
};
messages.push(msg);
}
resultSet.close();
return messages.reverse();
}
async clearChatHistory(): Promise<void> {
if (!this.store)
return;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records');
await this.store.delete(predicates);
}
private async getAllNodes(): Promise<NodeData[]> {
if (!this.store)
return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']);
const nodes: NodeData[] = [];
while (resultSet.goToNextRow()) {
const node: NodeData = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
label: resultSet.getString(resultSet.getColumnIndex('name')),
type: resultSet.getString(resultSet.getColumnIndex('type')),
mentions: resultSet.getLong(resultSet.getColumnIndex('mentions'))
};
nodes.push(node);
}
resultSet.close();
return nodes;
}
private async getAllEdges(): Promise<EdgeData[]> {
if (!this.store)
return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations');
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'relation', 'object_id', 'weight']);
const edges: EdgeData[] = [];
while (resultSet.goToNextRow()) {
const edge: EdgeData = {
from: resultSet.getLong(resultSet.getColumnIndex('subject_id')),
to: resultSet.getLong(resultSet.getColumnIndex('object_id')),
label: resultSet.getString(resultSet.getColumnIndex('relation')),
weight: resultSet.getDouble(resultSet.getColumnIndex('weight'))
};
edges.push(edge);
}
resultSet.close();
return edges;
}
}

View File

@ -0,0 +1,205 @@
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface ChatPage_Params {
messages?: ChatMessage[];
inputText?: string;
db?: GraphDatabase;
scrollController?: Scroller;
}
import http from "@ohos:net.http";
import dataPreferences from "@ohos:data.preferences";
import type { GraphDatabase } from '../model/GraphDatabase';
interface ChatMessage {
role: string;
content: string;
}
export class ChatPage extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__messages = new ObservedPropertyObjectPU([], this, "messages");
this.__inputText = new ObservedPropertySimplePU('', this, "inputText");
this.__db = new SynchedPropertyObjectOneWayPU(params.db, this, "db");
this.scrollController = new Scroller();
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: ChatPage_Params) {
if (params.messages !== undefined) {
this.messages = params.messages;
}
if (params.inputText !== undefined) {
this.inputText = params.inputText;
}
if (params.scrollController !== undefined) {
this.scrollController = params.scrollController;
}
}
updateStateVars(params: ChatPage_Params) {
this.__db.reset(params.db);
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__messages.purgeDependencyOnElmtId(rmElmtId);
this.__inputText.purgeDependencyOnElmtId(rmElmtId);
this.__db.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__messages.aboutToBeDeleted();
this.__inputText.aboutToBeDeleted();
this.__db.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __messages: ObservedPropertyObjectPU<ChatMessage[]>;
get messages() {
return this.__messages.get();
}
set messages(newValue: ChatMessage[]) {
this.__messages.set(newValue);
}
private __inputText: ObservedPropertySimplePU<string>;
get inputText() {
return this.__inputText.get();
}
set inputText(newValue: string) {
this.__inputText.set(newValue);
}
private __db: SynchedPropertySimpleOneWayPU<GraphDatabase>;
get db() {
return this.__db.get();
}
set db(newValue: GraphDatabase) {
this.__db.set(newValue);
}
private scrollController: Scroller;
async aboutToAppear() {
this.messages = await this.db.getChatHistory(50);
}
async sendMessage() {
if (!this.inputText.trim())
return;
const userMessage: string = this.inputText;
this.inputText = '';
await this.db.saveChatMessage('user', userMessage);
const userMsg: ChatMessage = { role: 'user', content: userMessage };
this.messages = [...this.messages, userMsg];
const pref: dataPreferences.Preferences = await dataPreferences.getPreferences(getContext(this), 'trulymem_config');
const baseUrl: string = String(await pref.get('base_url', 'https://api.deepseek.com'));
const model: string = String(await pref.get('model', 'deepseek-chat'));
const apiKey: string = String(await pref.get('api_key', ''));
try {
const httpRequest: http.HttpRequest = http.createHttp();
const response: http.HttpResponse = await httpRequest.request(baseUrl + '/chat/completions', {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey
},
extraData: {
model: model,
messages: [{ role: 'user', content: userMessage }]
}
});
if (response.responseCode === 200) {
const data: object = JSON.parse(String(response.result));
const choicesArr: object[] = data['choices'] as object[];
if (choicesArr && choicesArr.length > 0) {
const firstChoice: object = choicesArr[0];
const msgObj: object = firstChoice['message'] as object;
const assistantMessage: string = String(msgObj['content']);
await this.db.saveChatMessage('assistant', assistantMessage);
const aiMsg: ChatMessage = { role: 'assistant', content: assistantMessage };
this.messages = [...this.messages, aiMsg];
}
}
}
catch (err) {
console.error('HTTP request failed: ' + JSON.stringify(err));
}
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
Column.width('100%');
Column.height('100%');
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
List.create();
List.width('100%');
List.layoutWeight(1);
}, List);
this.observeComponentCreation2((elmtId, isInitialRender) => {
ForEach.create();
const forEachItemGenFunction = _item => {
const msg = _item;
{
const itemCreation = (elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
ListItem.create(deepRenderFunction, true);
if (!isInitialRender) {
ListItem.pop();
}
ViewStackProcessor.StopGetAccessRecording();
};
const itemCreation2 = (elmtId, isInitialRender) => {
ListItem.create(deepRenderFunction, true);
};
const deepRenderFunction = (elmtId, isInitialRender) => {
itemCreation(elmtId, isInitialRender);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
Column.padding(12);
Column.backgroundColor(msg.role === 'user' ? '#E3F2FD' : '#F5F5F5');
Column.borderRadius(8);
Column.margin({ left: 8, right: 8, bottom: 8 });
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(msg.role);
Text.fontSize(12);
Text.fontColor('#666');
Text.width('100%');
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(msg.content);
Text.fontSize(16);
Text.width('100%');
Text.margin({ top: 4 });
}, Text);
Text.pop();
Column.pop();
ListItem.pop();
};
this.observeComponentCreation2(itemCreation2, ListItem);
ListItem.pop();
}
};
this.forEachUpdateFunction(elmtId, this.messages, forEachItemGenFunction);
}, ForEach);
ForEach.pop();
List.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
Row.width('100%');
Row.padding(8);
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
TextArea.create({ text: this.inputText, placeholder: '输入消息...' });
TextArea.layoutWeight(1);
TextArea.onChange((v: string) => { this.inputText = v; });
}, TextArea);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Button.createWithLabel('发送');
Button.onClick(() => this.sendMessage());
}, Button);
Button.pop();
Row.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}

View File

@ -0,0 +1,65 @@
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface GraphPage_Params {
controller?: web_webview.WebviewController;
db?: GraphDatabase;
}
import web_webview from "@ohos:web.webview";
import type { GraphDatabase } from '../model/GraphDatabase';
export class GraphPage extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.controller = new web_webview.WebviewController();
this.__db = new SynchedPropertyObjectOneWayPU(params.db, this, "db");
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: GraphPage_Params) {
if (params.controller !== undefined) {
this.controller = params.controller;
}
}
updateStateVars(params: GraphPage_Params) {
this.__db.reset(params.db);
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__db.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__db.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private controller: web_webview.WebviewController;
private __db: SynchedPropertySimpleOneWayPU<GraphDatabase>;
get db() {
return this.__db.get();
}
set db(newValue: GraphDatabase) {
this.__db.set(newValue);
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
Column.width('100%');
Column.height('100%');
Column.onAppear(() => {
this.controller.refresh();
});
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Web.create({ src: { "id": 0, "type": 30000, params: ['graph.html'], "bundleName": "com.trulymem.app", "moduleName": "entry" }, controller: this.controller });
Web.javaScriptAccess(true);
Web.width('100%');
Web.height('100%');
}, Web);
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}

View File

@ -0,0 +1,119 @@
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface Index_Params {
currentIndex?: number;
db?: GraphDatabase;
}
import { MainPage } from "@normalized:N&&&entry/src/main/ets/pages/MainPage&";
import { SettingsPage } from "@normalized:N&&&entry/src/main/ets/pages/SettingsPage&";
import { GraphDatabase } from "@normalized:N&&&entry/src/main/ets/model/GraphDatabase&";
class Index extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__currentIndex = new ObservedPropertySimplePU(0, this, "currentIndex");
this.db = new GraphDatabase();
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: Index_Params) {
if (params.currentIndex !== undefined) {
this.currentIndex = params.currentIndex;
}
if (params.db !== undefined) {
this.db = params.db;
}
}
updateStateVars(params: Index_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__currentIndex.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__currentIndex.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __currentIndex: ObservedPropertySimplePU<number>;
get currentIndex() {
return this.__currentIndex.get();
}
set currentIndex(newValue: number) {
this.__currentIndex.set(newValue);
}
private db: GraphDatabase;
aboutToAppear() {
this.db.init(getContext(this));
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
Column.width('100%');
Column.height('100%');
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Tabs.create({ index: this.currentIndex, barPosition: BarPosition.End });
Tabs.width('100%');
Tabs.height('100%');
Tabs.onChange((index: number) => { this.currentIndex = index; });
}, Tabs);
this.observeComponentCreation2((elmtId, isInitialRender) => {
TabContent.create(() => {
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new MainPage(this, { db: this.db }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 19, col: 21 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
db: this.db
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {
db: this.db
});
}
}, { name: "MainPage" });
}
});
TabContent.tabBar('🌌 TrulyMEM');
}, TabContent);
TabContent.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
TabContent.create(() => {
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new SettingsPage(this, {}, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 24, col: 21 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {});
}
}, { name: "SettingsPage" });
}
});
TabContent.tabBar('⚙ 设置');
}, TabContent);
TabContent.pop();
Tabs.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
static getEntryName(): string {
return "Index";
}
}
registerNamedRoute(() => new Index(undefined, {}), "", { bundleName: "com.trulymem.app", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" });

View File

@ -0,0 +1,97 @@
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface MainPage_Params {
db?: GraphDatabase;
}
import type { GraphDatabase } from '../model/GraphDatabase';
import { GraphPage } from "@normalized:N&&&entry/src/main/ets/pages/GraphPage&";
import { ChatPage } from "@normalized:N&&&entry/src/main/ets/pages/ChatPage&";
export class MainPage extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__db = new SynchedPropertyObjectOneWayPU(params.db, this, "db");
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: MainPage_Params) {
}
updateStateVars(params: MainPage_Params) {
this.__db.reset(params.db);
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__db.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__db.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __db: SynchedPropertySimpleOneWayPU<GraphDatabase>;
get db() {
return this.__db.get();
}
set db(newValue: GraphDatabase) {
this.__db.set(newValue);
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
GridRow.create({ columns: { sm: 1, md: 2, lg: 2 }, gutter: { x: 8, y: 8 } });
GridRow.width('100%');
GridRow.height('100%');
}, GridRow);
this.observeComponentCreation2((elmtId, isInitialRender) => {
GridCol.create({ span: { sm: 1, md: 1, lg: 1 } });
}, GridCol);
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new GraphPage(this, { db: this.db }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/MainPage.ets", line: 12, col: 17 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
db: this.db
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {
db: this.db
});
}
}, { name: "GraphPage" });
}
GridCol.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
GridCol.create({ span: { sm: 1, md: 1, lg: 1 } });
}, GridCol);
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new ChatPage(this, { db: this.db }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/MainPage.ets", line: 15, col: 17 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
db: this.db
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {
db: this.db
});
}
}, { name: "ChatPage" });
}
GridCol.pop();
GridRow.pop();
}
rerender() {
this.updateDirtyElements();
}
}

View File

@ -0,0 +1,152 @@
if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface SettingsPage_Params {
baseUrl?: string;
model?: string;
apiKey?: string;
pref?: dataPreferences.Preferences;
}
import dataPreferences from "@ohos:data.preferences";
export class SettingsPage extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__baseUrl = new ObservedPropertySimplePU('', this, "baseUrl");
this.__model = new ObservedPropertySimplePU('', this, "model");
this.__apiKey = new ObservedPropertySimplePU('', this, "apiKey");
this.pref = undefined;
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: SettingsPage_Params) {
if (params.baseUrl !== undefined) {
this.baseUrl = params.baseUrl;
}
if (params.model !== undefined) {
this.model = params.model;
}
if (params.apiKey !== undefined) {
this.apiKey = params.apiKey;
}
if (params.pref !== undefined) {
this.pref = params.pref;
}
}
updateStateVars(params: SettingsPage_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__baseUrl.purgeDependencyOnElmtId(rmElmtId);
this.__model.purgeDependencyOnElmtId(rmElmtId);
this.__apiKey.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__baseUrl.aboutToBeDeleted();
this.__model.aboutToBeDeleted();
this.__apiKey.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __baseUrl: ObservedPropertySimplePU<string>;
get baseUrl() {
return this.__baseUrl.get();
}
set baseUrl(newValue: string) {
this.__baseUrl.set(newValue);
}
private __model: ObservedPropertySimplePU<string>;
get model() {
return this.__model.get();
}
set model(newValue: string) {
this.__model.set(newValue);
}
private __apiKey: ObservedPropertySimplePU<string>;
get apiKey() {
return this.__apiKey.get();
}
set apiKey(newValue: string) {
this.__apiKey.set(newValue);
}
private pref?: dataPreferences.Preferences;
async aboutToAppear() {
const ctx = getContext(this);
this.pref = await dataPreferences.getPreferences(ctx, 'trulymem_config');
this.baseUrl = String(await this.pref.get('base_url', 'https://api.deepseek.com'));
this.model = String(await this.pref.get('model', 'deepseek-chat'));
this.apiKey = String(await this.pref.get('api_key', ''));
}
async onBaseUrlChange(value: string) {
this.baseUrl = value;
await this.pref?.put('base_url', value);
await this.pref?.flush();
}
async onModelChange(value: string) {
this.model = value;
await this.pref?.put('model', value);
await this.pref?.flush();
}
async onApiKeyChange(value: string) {
this.apiKey = value;
await this.pref?.put('api_key', value);
await this.pref?.flush();
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
Column.width('100%');
Column.height('100%');
Column.padding(16);
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('API 配置');
Text.fontSize(24);
Text.fontWeight(FontWeight.Bold);
Text.margin({ top: 20, bottom: 16 });
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('Base URL');
Text.fontSize(14);
Text.width('100%');
Text.margin({ left: 16 });
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
TextInput.create({ placeholder: 'https://api.deepseek.com', text: this.baseUrl });
TextInput.onChange((v: string) => { this.onBaseUrlChange(v); });
TextInput.margin({ left: 16, right: 16, bottom: 12 });
}, TextInput);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('Model ID');
Text.fontSize(14);
Text.width('100%');
Text.margin({ left: 16 });
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
TextInput.create({ placeholder: 'deepseek-chat', text: this.model });
TextInput.onChange((v: string) => { this.onModelChange(v); });
TextInput.margin({ left: 16, right: 16, bottom: 12 });
}, TextInput);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('API Key');
Text.fontSize(14);
Text.width('100%');
Text.margin({ left: 16 });
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
TextInput.create({ placeholder: 'sk-...', text: this.apiKey });
TextInput.type(InputType.Password);
TextInput.onChange((v: string) => { this.onApiKeyChange(v); });
TextInput.margin({ left: 16, right: 16, bottom: 12 });
}, TextInput);
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}

View File

@ -0,0 +1,8 @@
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/build/default/generated/r/default/ResourceTable.js;&entry/build/generated/r/ResourceTable&;esm;entry|entry|1.0.0|build/default/generated/r/default/ResourceTable.js;entry;false;ts
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/entryability/EntryAbility.ts;&entry/src/main/ets/entryability/EntryAbility&;esm;entry|entry|1.0.0|src/main/ets/entryability/EntryAbility.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/Index.ts;&entry/src/main/ets/pages/Index&;esm;entry|entry|1.0.0|src/main/ets/pages/Index.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/MainPage.ts;&entry/src/main/ets/pages/MainPage&;esm;entry|entry|1.0.0|src/main/ets/pages/MainPage.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/model/GraphDatabase.ts;&entry/src/main/ets/model/GraphDatabase&;esm;entry|entry|1.0.0|src/main/ets/model/GraphDatabase.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/SettingsPage.ts;&entry/src/main/ets/pages/SettingsPage&;esm;entry|entry|1.0.0|src/main/ets/pages/SettingsPage.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/GraphPage.ts;&entry/src/main/ets/pages/GraphPage&;esm;entry|entry|1.0.0|src/main/ets/pages/GraphPage.ts;entry;false;ets
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/ChatPage.ts;&entry/src/main/ets/pages/ChatPage&;esm;entry|entry|1.0.0|src/main/ets/pages/ChatPage.ts;entry;false;ets

View File

@ -0,0 +1,9 @@
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/build/default/generated/r/default/ResourceTable.js;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/build/default/generated/r/default/ResourceTable.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/entryability/EntryAbility.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/entryability/EntryAbility.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/Index.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/Index.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/MainPage.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/MainPage.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/model/GraphDatabase.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/model/GraphDatabase.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/SettingsPage.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/SettingsPage.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/GraphPage.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/GraphPage.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/ChatPage.ts;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/ChatPage.protoBin
/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/npmEntries.txt;/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/npmEntries.protoBin

View File

@ -0,0 +1,7 @@
@system.app:@native.system.app
@ohos.app:@native.ohos.app
@system.router:@native.system.router
@system.curves:@native.system.curves
@ohos.curves:@native.ohos.curves
@system.matrix4:@native.system.matrix4
@ohos.matrix4:@native.ohos.matrix4

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
/**
* Use these variables when you tailor your ArkTS code. They must be of the const type.
*/
export const BUNDLE_NAME = 'com.trulymem.app';
export const BUNDLE_TYPE = 'app';
export const VERSION_CODE = 1000001;
export const VERSION_NAME = '1.0.0';
export const TARGET_NAME = 'default';
export const PRODUCT_NAME = 'default';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
/**
* BuildProfile Class is used only for compatibility purposes.
*/
export default class BuildProfile {
static readonly BUNDLE_NAME = BUNDLE_NAME;
static readonly BUNDLE_TYPE = BUNDLE_TYPE;
static readonly VERSION_CODE = VERSION_CODE;
static readonly VERSION_NAME = VERSION_NAME;
static readonly TARGET_NAME = TARGET_NAME;
static readonly PRODUCT_NAME = PRODUCT_NAME;
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
static readonly DEBUG = DEBUG;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef RESOURCE_TABLE_H
#define RESOURCE_TABLE_H
#include<stdint.h>
namespace OHOS {
const int32_t STRING_ENTRYABILITY_DESC = 0x01000001;
const int32_t STRING_ENTRYABILITY_LABEL = 0x01000002;
const int32_t STRING_APP_NAME = 0x01000003;
const int32_t STRING_MODULE_DESC = 0x01000004;
const int32_t COLOR_START_WINDOW_BACKGROUND = 0x01000005;
const int32_t MEDIA_LAYERED_IMAGE = 0x01000000;
const int32_t MEDIA_STARTICON = 0x01000006;
const int32_t PROFILE_MAIN_PAGES = 0x01000007;
}
#endif

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//@ts-noCheck

View File

@ -0,0 +1 @@
[{"hapName":"entry-default-unsigned.hap","deviceTypes":["phone","tablet","2in1"],"isSigned":false}]

View File

@ -0,0 +1,26 @@
{
"modulePathMap": {
"entry": "/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry",
"trulymem-core": "/home/program/TrulyMEM-TrueHumanMEM/harmonyos/trulymem-core"
},
"compileMode": "esmodule",
"projectRootPath": "/home/program/TrulyMEM-TrueHumanMEM/harmonyos",
"nodeModulesPath": "/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/loader_out/default/node_modules",
"byteCodeHarInfo": {},
"declarationEntry": [],
"moduleName": "entry",
"hspNameOhmMap": {},
"harNameOhmMap": {},
"packageManagerType": "ohpm",
"compileEntry": [
"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/generated/r/default/ResourceTable.ts"
],
"otherCompileFiles": [],
"dynamicImportLibInfo": {},
"routerMap": [],
"hspResourcesMap": {},
"updateVersionInfo": {},
"customizedHar": false,
"anBuildOutPut": "/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/loader_out/default/an/arm64-v8a",
"anBuildMode": "type"
}

View File

@ -0,0 +1 @@
{"entry":{"packageName":"entry","bundleName":"","moduleName":"","version":"","entryPath":"src/main/","isSO":false,"dependencyAlias":""}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
{
"app": {
"bundleName": "com.trulymem.app",
"vendor": "trulymem",
"versionCode": 1000001,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name",
"apiReleaseType": "Release",
"compileSdkVersion": "6.1.0.105",
"targetAPIVersion": 60100023,
"minAPIVersion": 60100023,
"compileSdkType": "HarmonyOS",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug",
"debug": true
},
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO"
}
],
"packageName": "entry"
}
}

View File

@ -0,0 +1 @@
{"app":{"bundleName":"com.trulymem.app","vendor":"trulymem","versionCode":1000001,"versionName":"1.0.0","icon":"$media:layered_image","label":"$string:app_name","apiReleaseType":"Release","compileSdkVersion":"6.1.0.105","targetAPIVersion":60100023,"minAPIVersion":60100023,"compileSdkType":"HarmonyOS","targetMinorAPIVersion":0,"targetPatchAPIVersion":0,"appEnvironments":[],"bundleType":"app","buildMode":"debug","debug":true,"iconId":16777216,"labelId":16777219},"module":{"name":"entry","type":"entry","description":"$string:module_desc","mainElement":"EntryAbility","deviceTypes":["phone","tablet","2in1"],"deliveryWithInstall":true,"installationFree":false,"pages":"$profile:main_pages","abilities":[{"name":"EntryAbility","srcEntry":"./ets/entryability/EntryAbility.ets","description":"$string:EntryAbility_desc","icon":"$media:layered_image","label":"$string:EntryAbility_label","startWindowIcon":"$media:startIcon","startWindowBackground":"$color:start_window_background","exported":true,"skills":[{"entities":["entity.system.home"],"actions":["action.system.home"]}],"descriptionId":16777217,"iconId":16777216,"labelId":16777218,"startWindowIconId":16777222,"startWindowBackgroundId":16777221}],"requestPermissions":[{"name":"ohos.permission.INTERNET"},{"name":"ohos.permission.GET_NETWORK_INFO"}],"packageName":"entry","virtualMachine":"ark13.0.1.0","compileMode":"esmodule","dependencies":[],"descriptionId":16777220}}

View File

@ -0,0 +1 @@
{"libs":{},"stripped":{}}

View File

@ -0,0 +1 @@
{"libs":{},"binxo":{},"binxoSymbol":{"enableAsanBinxo":false,"excludeSoFromBinxo":[]}}

View File

@ -0,0 +1,69 @@
{
"app": {
"bundleName": "com.trulymem.app",
"vendor": "trulymem",
"versionCode": 1000001,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name",
"apiReleaseType": "Release",
"compileSdkVersion": "6.1.0.105",
"targetAPIVersion": 60100023,
"minAPIVersion": 60100023,
"compileSdkType": "HarmonyOS",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug",
"debug": true
},
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO"
}
],
"packageName": "entry",
"virtualMachine": "ark13.0.1.0",
"compileMode": "esmodule",
"dependencies": []
}
}

View File

@ -0,0 +1,8 @@
string EntryAbility_desc 0x01000001
string EntryAbility_label 0x01000002
string app_name 0x01000003
string module_desc 0x01000004
color start_window_background 0x01000005
media layered_image 0x01000000
media startIcon 0x01000006
profile main_pages 0x01000007

View File

@ -0,0 +1 @@
{"app":{"bundleName":"com.trulymem.app","vendor":"trulymem","versionCode":1000001,"versionName":"1.0.0","icon":"$media:layered_image","label":"$string:app_name","apiReleaseType":"Release","compileSdkVersion":"6.1.0.105","targetAPIVersion":60100023,"minAPIVersion":60100023,"compileSdkType":"HarmonyOS","targetMinorAPIVersion":0,"targetPatchAPIVersion":0,"appEnvironments":[],"bundleType":"app","buildMode":"debug","debug":true},"module":{"name":"entry","type":"entry","description":"$string:module_desc","mainElement":"EntryAbility","deviceTypes":["phone","tablet","2in1"],"deliveryWithInstall":true,"installationFree":false,"pages":"$profile:main_pages","abilities":[{"name":"EntryAbility","srcEntry":"./ets/entryability/EntryAbility.ets","description":"$string:EntryAbility_desc","icon":"$media:layered_image","label":"$string:EntryAbility_label","startWindowIcon":"$media:startIcon","startWindowBackground":"$color:start_window_background","exported":true,"skills":[{"entities":["entity.system.home"],"actions":["action.system.home"]}]}],"requestPermissions":[{"name":"ohos.permission.INTERNET"},{"name":"ohos.permission.GET_NETWORK_INFO"}],"packageName":"entry","virtualMachine":"ark13.0.1.0","compileMode":"esmodule","dependencies":[]}}

View File

@ -0,0 +1,35 @@
{
"record": [{
"type": "media",
"name": "layered_image",
"id": "0x01000000"
}, {
"type": "string",
"name": "EntryAbility_desc",
"id": "0x01000001"
}, {
"type": "string",
"name": "EntryAbility_label",
"id": "0x01000002"
}, {
"type": "string",
"name": "app_name",
"id": "0x01000003"
}, {
"type": "string",
"name": "module_desc",
"id": "0x01000004"
}, {
"type": "color",
"name": "start_window_background",
"id": "0x01000005"
}, {
"type": "media",
"name": "startIcon",
"id": "0x01000006"
}, {
"type": "profile",
"name": "main_pages",
"id": "0x01000007"
}]
}

View File

@ -0,0 +1,62 @@
{
"app": {
"bundleName": "com.trulymem.app",
"vendor": "trulymem",
"versionCode": 1000001,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name",
"apiReleaseType": "Release",
"compileSdkVersion": "6.1.0.105",
"targetAPIVersion": 60100023,
"minAPIVersion": 60100023,
"compileSdkType": "HarmonyOS",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug",
"debug": true,
"iconId": 16777216,
"labelId": 16777219
},
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet", "2in1"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}],
"descriptionId": 16777217,
"iconId": 16777216,
"labelId": 16777218,
"startWindowIconId": 16777222,
"startWindowBackgroundId": 16777221
}],
"requestPermissions": [{
"name": "ohos.permission.INTERNET"
}, {
"name": "ohos.permission.GET_NETWORK_INFO"
}],
"packageName": "entry",
"virtualMachine": "ark13.0.1.0",
"compileMode": "esmodule",
"dependencies": [],
"descriptionId": 16777220
}
}

View File

@ -0,0 +1 @@
{"context":{"extensionPath":"/home/program/tools/command-line-tools/sdk/default/hms/toolchains/lib/libimage_transcoder_shared.so"},"compression":{"media":{"enable":false},"filters":[]}}

View File

@ -0,0 +1 @@
{"configPath":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/process_profile/default/module.json","packageName":"com.trulymem.app","output":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/res/default","moduleNames":"entry,trulymem-core","ResourceTable":["/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/generated/r/default/ResourceTable.h","/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/generated/r/default/ResourceTable.ts"],"applicationResource":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/AppScope/resources","moduleResources":["/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/src/main/resources"],"dependencies":[],"iconCheck":true,"compression":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/res/default/opt-compression.json","ids":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/res/default/ids_map","definedIds":"/home/program/TrulyMEM-TrueHumanMEM/harmonyos/entry/build/default/intermediates/res/default/ids_map/id_defined.json","definedSysIds":"/home/program/tools/command-line-tools/sdk/default/hms/toolchains/id_defined.json"}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" rx="30" fill="#6366f1"/>
<text x="100" y="130" font-family="Arial" font-size="80" fill="white" text-anchor="middle" font-weight="bold">T</text>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" rx="20" fill="#6366f1"/>
<text x="100" y="130" font-family="Arial" font-size="60" fill="white" text-anchor="middle" font-weight="bold">TM</text>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>记忆星图 - TrulyMEM</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Courier New', monospace; background: #0a0a1a; color: #ffffff; overflow: hidden; width: 100vw; height: 100vh; }
#canvas-container { width: 100%; height: 100%; position: relative; }
canvas { display: block; }
#stats { position: absolute; top: 20px; left: 20px; background: rgba(10, 10, 26, 0.8); padding: 15px 20px; border-radius: 8px; border: 1px solid rgba(100, 100, 255, 0.3); font-size: 14px; z-index: 100; backdrop-filter: blur(10px); }
#stats h3 { margin-bottom: 8px; color: #4488ff; font-size: 16px; }
#stats p { margin: 4px 0; color: #aaaacc; }
#stats span { color: #ffffff; font-weight: bold; }
#node-info { position: absolute; top: 20px; right: 20px; background: rgba(10, 10, 26, 0.9); padding: 15px 20px; border-radius: 8px; border: 1px solid rgba(100, 100, 255, 0.3); font-size: 14px; z-index: 100; display: none; backdrop-filter: blur(10px); max-width: 300px; }
#node-info h3 { color: #44ff88; margin-bottom: 8px; font-size: 16px; }
#node-info p { margin: 4px 0; color: #aaaacc; }
#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; }
.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>
<body>
<div id="canvas-container">
<div id="loading">正在加载星图数据...</div>
<div id="stats">
<h3>🌌 记忆星图</h3>
<p>节点: <span id="node-count">0</span></p>
<p>边: <span id="edge-count">0</span></p>
<p>状态: <span id="status">初始化中...</span></p>
</div>
<div id="node-info">
<h3 id="info-name"></h3>
<p><span class="label">类型:</span> <span id="info-type"></span></p>
<p><span class="label">提及次数:</span> <span id="info-mentions"></span></p>
<p><span class="label">连接数:</span> <span id="info-links"></span></p>
</div>
<div id="nav">
<button class="nav-btn active">🌌 星图</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
let scene, camera, renderer, controls;
let nodes = [], edges = [];
let nodeMeshes = [], edgeLines = [];
let starField, nebulaParticles;
let raycaster, mouse;
let hoveredNode = null, selectedNode = null;
let animationId;
let highlightPulse = 0;
const typeColors = { 'person': 0x4488ff, 'task': 0xff8844, 'ai': 0xaa44ff, 'concept': 0x44ff88, 'object': 0xff4444 };
const defaultColor = 0xcccccc;
const edgeColors = { '喜欢': 0xff6b6b, '学习': 0x4ecdc4, '属于': 0x45b7d1, '相关': 0x96ceb4, '使用': 0xfeca57, '创建': 0xff9ff3 };
const defaultEdgeColor = 0x444466;
function init() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.015);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(0, 30, 60);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x0a0a1a, 1);
document.getElementById('canvas-container').appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const ambientLight = new THREE.AmbientLight(0x444466, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
scene.add(directionalLight);
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
createStarField();
createNebula();
window.addEventListener('resize', onWindowResize);
renderer.domElement.addEventListener('mousemove', onMouseMove);
renderer.domElement.addEventListener('click', onMouseClick);
window.addEventListener('message', (event) => {
if (event.data.type === 'graph_data') {
window.__graphData = event.data.payload;
loadGraphData();
}
});
window.parent.postMessage('request_graph_data', '*');
animate();
}
function createStarField() {
const starCount = 3000;
const positions = new Float32Array(starCount * 3);
const colors = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const i3 = i * 3;
const radius = 400 + Math.random() * 600;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
positions[i3] = radius * Math.sin(phi) * Math.cos(theta);
positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
positions[i3 + 2] = radius * Math.cos(phi);
const colorChoice = Math.random();
if (colorChoice < 0.7) {
colors[i3] = 0.8 + Math.random() * 0.2;
colors[i3 + 1] = 0.8 + Math.random() * 0.2;
colors[i3 + 2] = 1.0;
} else {
colors[i3] = 1.0;
colors[i3 + 1] = 0.9 + Math.random() * 0.1;
colors[i3 + 2] = 0.8 + Math.random() * 0.2;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ size: 1.5, vertexColors: true, transparent: true, opacity: 0.8, sizeAttenuation: true });
starField = new THREE.Points(geometry, material);
scene.add(starField);
}
function createNebula() {
const nebulaCount = 500;
const positions = new Float32Array(nebulaCount * 3);
const colors = new Float32Array(nebulaCount * 3);
for (let i = 0; i < nebulaCount; i++) {
const i3 = i * 3;
positions[i3] = (Math.random() - 0.5) * 800;
positions[i3 + 1] = (Math.random() - 0.5) * 800;
positions[i3 + 2] = (Math.random() - 0.5) * 800;
const colorChoice = Math.random();
if (colorChoice < 0.33) {
colors[i3] = 0.5 + Math.random() * 0.3; colors[i3 + 1] = 0.2 + Math.random() * 0.2; colors[i3 + 2] = 0.7 + Math.random() * 0.3;
} else if (colorChoice < 0.66) {
colors[i3] = 0.2 + Math.random() * 0.2; colors[i3 + 1] = 0.3 + Math.random() * 0.3; colors[i3 + 2] = 0.8 + Math.random() * 0.2;
} else {
colors[i3] = 0.7 + Math.random() * 0.3; colors[i3 + 1] = 0.2 + Math.random() * 0.2; colors[i3 + 2] = 0.5 + Math.random() * 0.3;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ size: 8, vertexColors: true, transparent: true, opacity: 0.15, sizeAttenuation: true, blending: THREE.AdditiveBlending });
nebulaParticles = new THREE.Points(geometry, material);
scene.add(nebulaParticles);
}
window.loadGraphData = function(data) {
if (data && data.nodes && data.edges) {
nodes = data.nodes.map(n => ({ id: n.id, name: n.label || n.name, type: n.type, mention_count: n.mentions || 1 }));
edges = data.edges.map(e => ({ id: e.id, source: e.from || e.source, target: e.to || e.target, relation_type: e.label || e.relation }));
document.getElementById('node-count').textContent = nodes.length;
document.getElementById('edge-count').textContent = edges.length;
document.getElementById('status').textContent = '就绪';
createGraphVisualization();
document.getElementById('loading').style.display = 'none';
}
}
function createGraphVisualization() {
nodeMeshes.forEach(mesh => scene.remove(mesh));
edgeLines.forEach(line => scene.remove(line));
nodeMeshes = [];
edgeLines = [];
if (nodes.length === 0) return;
const nodeDegrees = {};
nodes.forEach(n => nodeDegrees[n.id] = 0);
edges.forEach(e => {
nodeDegrees[e.source] = (nodeDegrees[e.source] || 0) + 1;
nodeDegrees[e.target] = (nodeDegrees[e.target] || 0) + 1;
});
const 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++) {
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;
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;
}
});
});
edges.forEach(edge => {
const pos1 = positions[edge.source], pos2 = positions[edge.target];
if (!pos1 || !pos2) return;
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;
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;
}
});
}
nodes.forEach(node => {
const pos = positions[node.id];
if (!pos) return;
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 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);
});
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 line = new THREE.Line(geometry, material);
line.userData = { edgeId: edge.id, edgeData: edge };
scene.add(line);
edgeLines.push(line);
});
const allPositions = Object.values(positions);
if (allPositions.length > 0) {
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);
}
}
function onMouseMove(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 intersects = raycaster.intersectObjects(nodeMeshes);
if (intersects.length > 0) {
const node = intersects[0].object;
if (hoveredNode !== node) {
if (hoveredNode) hoveredNode.scale.set(1, 1, 1);
hoveredNode = node;
node.scale.set(1.2, 1.2, 1.2);
showNodeInfo(node.userData.nodeData);
}
} else {
if (hoveredNode) { hoveredNode.scale.set(1, 1, 1); hoveredNode = null; hideNodeInfo(); }
}
}
function onMouseClick(event) {
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 { selectedNode = node; showNodeInfo(node.userData.nodeData, true); }
}
}
function showNodeInfo(nodeData, isClick = false) {
document.getElementById('info-name').textContent = nodeData.name;
document.getElementById('info-type').textContent = nodeData.type;
document.getElementById('info-mentions').textContent = nodeData.mention_count;
const linkCount = edges.filter(e => e.source === nodeData.id || e.target === nodeData.id).length;
document.getElementById('info-links').textContent = linkCount;
if (isClick) document.getElementById('node-info').style.display = 'block';
}
function hideNodeInfo() { if (!selectedNode) document.getElementById('node-info').style.display = 'none'; }
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
animationId = requestAnimationFrame(animate);
const time = Date.now() * 0.001;
highlightPulse = (highlightPulse + 0.02) % (Math.PI * 2);
controls.update();
if (starField) starField.rotation.y += 0.0001;
if (hoveredNode) { const pulse = 1 + Math.sin(highlightPulse * 3) * 0.05; hoveredNode.scale.set(pulse * 1.2, pulse * 1.2, pulse * 1.2); }
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>

View File

@ -0,0 +1 @@
{"routerMap":[]}

View File

@ -0,0 +1 @@
{"routerMap":[]}

View File

@ -0,0 +1 @@
{"crossAppSharedConfig":[]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"summary":{"app":{"bundleName":"com.trulymem.app","bundleType":"app","version":{"code":1000001,"name":"1.0.0"}},"modules":[{"mainAbility":"EntryAbility","deviceType":["phone","tablet","2in1"],"abilities":[{"name":"EntryAbility","label":"$string:EntryAbility_label"}],"distro":{"moduleType":"entry","installationFree":false,"deliveryWithInstall":true,"moduleName":"entry"},"apiVersion":{"compatible":23,"releaseType":"Release","target":23}}]},"packages":[{"deviceType":["phone","tablet","2in1"],"moduleType":"entry","deliveryWithInstall":true,"name":"entry-default"}]}

View File

@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks,
plugins: []
};

View File

@ -0,0 +1,34 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation
# 保留JSON解析相关的属性名防止混淆导致JSON解析失败
-keep-property-name
id
name
nameEn
icon
isCustom
builtin
custom
pixelMap

View File

@ -0,0 +1,19 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"librcon@../librcon": "librcon@../librcon"
},
"packages": {
"librcon@../librcon": {
"name": "librcon",
"version": "1.0.0",
"resolved": "../librcon",
"registryType": "local"
}
}
}

View File

@ -0,0 +1,11 @@
{
"name": "entry",
"version": "1.0.0",
"description": "TrulyMEM - A personal memory and knowledge graph application.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"trulymem-core": "file:../trulymem-core"
}
}

View File

@ -0,0 +1,44 @@
export class Constants {
static readonly DB_NAME: string = 'trulymem.db';
static readonly CONFIG_PREF_NAME: string = 'trulymem_config';
static readonly DEFAULT_BASE_URL: string = 'https://api.deepseek.com';
static readonly DEFAULT_MODEL: string = 'deepseek-chat';
static readonly SECURITY_LEVEL: number = 1; // S1
// Table names
static readonly TABLE_NODES: string = 'nodes';
static readonly TABLE_RELATIONS: string = 'relations';
static readonly TABLE_CHAT: string = 'chat_records';
// SQL definitions
static readonly SQL_CREATE_NODES: string = `
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
type TEXT DEFAULT 'concept',
mentions INTEGER DEFAULT 1,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
)`;
static readonly SQL_CREATE_RELATIONS: string = `
CREATE TABLE IF NOT EXISTS relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subject_id INTEGER NOT NULL,
relation TEXT NOT NULL,
object_id INTEGER NOT NULL,
weight REAL DEFAULT 1.0,
created_at TEXT DEFAULT (datetime('now','localtime')),
FOREIGN KEY (subject_id) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (object_id) REFERENCES nodes(id) ON DELETE CASCADE
)`;
static readonly SQL_CREATE_CHAT: string = `
CREATE TABLE IF NOT EXISTS chat_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
content TEXT NOT NULL,
tools TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
)`;
}

View File

@ -0,0 +1,36 @@
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, param: AbilityConstant.LaunchParam): void {
console.info('EntryAbility onCreate');
}
onDestroy(): void {
console.info('EntryAbility onDestroy');
}
onWindowStageCreate(windowStage: Window.WindowStage): void {
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
console.error('Failed to load content: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading content: ' + JSON.stringify(data));
});
}
onWindowStageDestroy(): void {
console.info('EntryAbility onWindowStageDestroy');
}
onForeground(): void {
console.info('EntryAbility onForeground');
}
onBackground(): void {
console.info('EntryAbility onBackground');
}
}

View File

@ -0,0 +1,237 @@
import relationalStore from '@ohos.data.relationalStore';
import { Context } from '@ohos.abilityAccessCtrl';
interface TripletData {
subject: string;
relation: string;
object: string;
}
interface CriteriaData {
subject?: string;
target?: string;
relation?: string;
sessionId?: string;
}
interface NodeData {
id: number;
label: string;
type: string;
mentions: number;
}
interface EdgeData {
from: number;
to: number;
label: string;
weight: number;
}
interface GraphData {
nodes: NodeData[];
edges: EdgeData[];
}
interface RecallResult {
data: GraphData;
}
interface ChatMessage {
role: string;
content: string;
}
interface CleanupResult {
cleaned: number;
}
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'trulymem.db',
securityLevel: relationalStore.SecurityLevel.S1
};
export class GraphDatabase {
private store?: relationalStore.RdbStore;
private context?: Context;
async init(context: Context): Promise<void> {
this.context = context;
this.store = await relationalStore.getRdbStore(context, STORE_CONFIG);
await this.createTables();
}
private async createTables(): Promise<void> {
if (!this.store) return;
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
type TEXT DEFAULT 'concept',
mentions INTEGER DEFAULT 1,
created_at TEXT,
updated_at TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subject_id INTEGER NOT NULL,
relation TEXT NOT NULL,
object_id INTEGER NOT NULL,
weight REAL DEFAULT 1.0,
created_at TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS chat_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
content TEXT NOT NULL,
tools TEXT,
created_at TEXT
)
`);
}
async commit(triplets: TripletData[]): Promise<void> {
if (!this.store) return;
for (const triplet of triplets) {
const subjectId: number = await this.upsertNode(triplet.subject);
const objectId: number = await this.upsertNode(triplet.object);
const bucket: relationalStore.ValuesBucket = {
'subject_id': subjectId,
'relation': triplet.relation,
'object_id': objectId,
'created_at': new Date().toISOString()
};
await this.store.insert('relations', bucket);
}
}
private async upsertNode(name: string): Promise<number> {
if (!this.store) return -1;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
predicates.equalTo('name', name);
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id']);
if (resultSet.goToNextRow()) {
const id: number = resultSet.getLong(resultSet.getColumnIndex('id'));
resultSet.close();
return id;
}
resultSet.close();
const bucket: relationalStore.ValuesBucket = {
'name': name,
'type': 'concept',
'created_at': new Date().toISOString(),
'updated_at': new Date().toISOString()
};
return await this.store.insert('nodes', bucket);
}
async recall(queryIntent: string, seedEntities?: string[]): Promise<RecallResult> {
if (!this.store) {
const empty: RecallResult = { data: { nodes: [], edges: [] } };
return empty;
}
const nodes: NodeData[] = await this.getAllNodes();
const edges: EdgeData[] = await this.getAllEdges();
const result: RecallResult = { data: { nodes, edges } };
return result;
}
async purge(criteria: CriteriaData): Promise<void> {
if (!this.store) return;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
await this.store.delete(predicates);
}
async introspect(): Promise<RecallResult> {
const nodes: NodeData[] = await this.getAllNodes();
const edges: EdgeData[] = await this.getAllEdges();
const result: RecallResult = { data: { nodes, edges } };
return result;
}
async archive(days: number): Promise<void> {
// Archive old data - placeholder
}
async cleanup(dryRun: boolean): Promise<CleanupResult> {
const result: CleanupResult = { cleaned: 0 };
return result;
}
async saveChatMessage(role: string, content: string, tools?: string): Promise<void> {
if (!this.store) return;
const bucket: relationalStore.ValuesBucket = {
'role': role,
'content': content,
'tools': tools || null,
'created_at': new Date().toISOString()
};
await this.store.insert('chat_records', bucket);
}
async getChatHistory(limit?: number): Promise<ChatMessage[]> {
if (!this.store) return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records');
predicates.orderByDesc('created_at');
if (limit) {
predicates.limitAs(limit);
}
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['role', 'content']);
const messages: ChatMessage[] = [];
while (resultSet.goToNextRow()) {
const msg: ChatMessage = {
role: resultSet.getString(resultSet.getColumnIndex('role')),
content: resultSet.getString(resultSet.getColumnIndex('content'))
};
messages.push(msg);
}
resultSet.close();
return messages.reverse();
}
async clearChatHistory(): Promise<void> {
if (!this.store) return;
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records');
await this.store.delete(predicates);
}
private async getAllNodes(): Promise<NodeData[]> {
if (!this.store) return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes');
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']);
const nodes: NodeData[] = [];
while (resultSet.goToNextRow()) {
const node: NodeData = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
label: resultSet.getString(resultSet.getColumnIndex('name')),
type: resultSet.getString(resultSet.getColumnIndex('type')),
mentions: resultSet.getLong(resultSet.getColumnIndex('mentions'))
};
nodes.push(node);
}
resultSet.close();
return nodes;
}
private async getAllEdges(): Promise<EdgeData[]> {
if (!this.store) return [];
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations');
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'relation', 'object_id', 'weight']);
const edges: EdgeData[] = [];
while (resultSet.goToNextRow()) {
const edge: EdgeData = {
from: resultSet.getLong(resultSet.getColumnIndex('subject_id')),
to: resultSet.getLong(resultSet.getColumnIndex('object_id')),
label: resultSet.getString(resultSet.getColumnIndex('relation')),
weight: resultSet.getDouble(resultSet.getColumnIndex('weight'))
};
edges.push(edge);
}
resultSet.close();
return edges;
}
}

View File

@ -0,0 +1,100 @@
import http from '@ohos.net.http';
import dataPreferences from '@ohos.data.preferences';
import { GraphDatabase } from '../model/GraphDatabase';
interface ChatMessage {
role: string;
content: string;
}
@Component
export struct ChatPage {
@State messages: ChatMessage[] = [];
@State inputText: string = '';
@Prop db: GraphDatabase;
private scrollController: Scroller = new Scroller();
async aboutToAppear() {
this.messages = await this.db.getChatHistory(50);
}
async sendMessage() {
if (!this.inputText.trim()) return;
const userMessage: string = this.inputText;
this.inputText = '';
await this.db.saveChatMessage('user', userMessage);
const userMsg: ChatMessage = { role: 'user', content: userMessage };
this.messages = [...this.messages, userMsg];
const pref: dataPreferences.Preferences =
await dataPreferences.getPreferences(getContext(this), 'trulymem_config');
const baseUrl: string = String(await pref.get('base_url', 'https://api.deepseek.com'));
const model: string = String(await pref.get('model', 'deepseek-chat'));
const apiKey: string = String(await pref.get('api_key', ''));
try {
const httpRequest: http.HttpRequest = http.createHttp();
const response: http.HttpResponse = await httpRequest.request(baseUrl + '/chat/completions', {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey
},
extraData: {
model: model,
messages: [{ role: 'user', content: userMessage }]
}
});
if (response.responseCode === 200) {
const data: object = JSON.parse(String(response.result));
const choicesArr: object[] = data['choices'] as object[];
if (choicesArr && choicesArr.length > 0) {
const firstChoice: object = choicesArr[0];
const msgObj: object = firstChoice['message'] as object;
const assistantMessage: string = String(msgObj['content']);
await this.db.saveChatMessage('assistant', assistantMessage);
const aiMsg: ChatMessage = { role: 'assistant', content: assistantMessage };
this.messages = [...this.messages, aiMsg];
}
}
} catch (err) {
console.error('HTTP request failed: ' + JSON.stringify(err));
}
}
build() {
Column() {
List() {
ForEach(this.messages, (msg: ChatMessage) => {
ListItem() {
Column() {
Text(msg.role).fontSize(12).fontColor('#666').width('100%')
Text(msg.content).fontSize(16).width('100%').margin({ top: 4 })
}
.padding(12)
.backgroundColor(msg.role === 'user' ? '#E3F2FD' : '#F5F5F5')
.borderRadius(8)
.margin({ left: 8, right: 8, bottom: 8 })
}
})
}
.width('100%')
.layoutWeight(1)
Row() {
TextArea({ text: this.inputText, placeholder: '输入消息...' })
.layoutWeight(1)
.onChange((v: string) => { this.inputText = v; })
Button('发送')
.onClick(() => this.sendMessage())
}
.width('100%')
.padding(8)
}
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,22 @@
import web_webview from '@ohos.web.webview';
import { GraphDatabase } from '../model/GraphDatabase';
@Component
export struct GraphPage {
private controller: web_webview.WebviewController = new web_webview.WebviewController();
@Prop db: GraphDatabase;
build() {
Column() {
Web({ src: $rawfile('graph.html'), controller: this.controller })
.javaScriptAccess(true)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.onAppear(() => {
this.controller.refresh();
})
}
}

View File

@ -0,0 +1,35 @@
import { MainPage } from './MainPage';
import { SettingsPage } from './SettingsPage';
import { GraphDatabase } from '../model/GraphDatabase';
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
private db: GraphDatabase = new GraphDatabase();
aboutToAppear() {
this.db.init(getContext(this));
}
build() {
Column() {
Tabs({ index: this.currentIndex, barPosition: BarPosition.End }) {
TabContent() {
MainPage({ db: this.db })
}
.tabBar('🌌 TrulyMEM')
TabContent() {
SettingsPage()
}
.tabBar('⚙ 设置')
}
.width('100%')
.height('100%')
.onChange((index: number) => { this.currentIndex = index; })
}
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,21 @@
import { GraphDatabase } from '../model/GraphDatabase';
import { GraphPage } from './GraphPage';
import { ChatPage } from './ChatPage';
@Component
export struct MainPage {
@Prop db: GraphDatabase;
build() {
GridRow({ columns: { sm: 1, md: 2, lg: 2 }, gutter: { x: 8, y: 8 } }) {
GridCol({ span: { sm: 1, md: 1, lg: 1 } }) {
GraphPage({ db: this.db })
}
GridCol({ span: { sm: 1, md: 1, lg: 1 } }) {
ChatPage({ db: this.db })
}
}
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,60 @@
import dataPreferences from '@ohos.data.preferences';
@Component
export struct SettingsPage {
@State baseUrl: string = '';
@State model: string = '';
@State apiKey: string = '';
private pref?: dataPreferences.Preferences;
async aboutToAppear() {
const ctx = getContext(this);
this.pref = await dataPreferences.getPreferences(ctx, 'trulymem_config');
this.baseUrl = String(await this.pref.get('base_url', 'https://api.deepseek.com'));
this.model = String(await this.pref.get('model', 'deepseek-chat'));
this.apiKey = String(await this.pref.get('api_key', ''));
}
async onBaseUrlChange(value: string) {
this.baseUrl = value;
await this.pref?.put('base_url', value);
await this.pref?.flush();
}
async onModelChange(value: string) {
this.model = value;
await this.pref?.put('model', value);
await this.pref?.flush();
}
async onApiKeyChange(value: string) {
this.apiKey = value;
await this.pref?.put('api_key', value);
await this.pref?.flush();
}
build() {
Column() {
Text('API 配置').fontSize(24).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 16 })
Text('Base URL').fontSize(14).width('100%').margin({ left: 16 })
TextInput({ placeholder: 'https://api.deepseek.com', text: this.baseUrl })
.onChange((v: string) => { this.onBaseUrlChange(v); })
.margin({ left: 16, right: 16, bottom: 12 })
Text('Model ID').fontSize(14).width('100%').margin({ left: 16 })
TextInput({ placeholder: 'deepseek-chat', text: this.model })
.onChange((v: string) => { this.onModelChange(v); })
.margin({ left: 16, right: 16, bottom: 12 })
Text('API Key').fontSize(14).width('100%').margin({ left: 16 })
TextInput({ placeholder: 'sk-...', text: this.apiKey })
.type(InputType.Password)
.onChange((v: string) => { this.onApiKeyChange(v); })
.margin({ left: 16, right: 16, bottom: 12 })
}
.width('100%')
.height('100%')
.padding(16)
}
}

View File

@ -0,0 +1,38 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet", "2in1"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO"
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,20 @@
{
"string": [
{
"name": "app_name",
"value": "TrulyMEM"
},
{
"name": "module_desc",
"value": "True Memory Engine"
},
{
"name": "EntryAbility_desc",
"value": "TrulyMEM Main Ability"
},
{
"name": "EntryAbility_label",
"value": "TrulyMEM"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" rx="30" fill="#6366f1"/>
<text x="100" y="130" font-family="Arial" font-size="80" fill="white" text-anchor="middle" font-weight="bold">T</text>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" rx="20" fill="#6366f1"/>
<text x="100" y="130" font-family="Arial" font-size="60" fill="white" text-anchor="middle" font-weight="bold">TM</text>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>记忆星图 - TrulyMEM</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Courier New', monospace; background: #0a0a1a; color: #ffffff; overflow: hidden; width: 100vw; height: 100vh; }
#canvas-container { width: 100%; height: 100%; position: relative; }
canvas { display: block; }
#stats { position: absolute; top: 20px; left: 20px; background: rgba(10, 10, 26, 0.8); padding: 15px 20px; border-radius: 8px; border: 1px solid rgba(100, 100, 255, 0.3); font-size: 14px; z-index: 100; backdrop-filter: blur(10px); }
#stats h3 { margin-bottom: 8px; color: #4488ff; font-size: 16px; }
#stats p { margin: 4px 0; color: #aaaacc; }
#stats span { color: #ffffff; font-weight: bold; }
#node-info { position: absolute; top: 20px; right: 20px; background: rgba(10, 10, 26, 0.9); padding: 15px 20px; border-radius: 8px; border: 1px solid rgba(100, 100, 255, 0.3); font-size: 14px; z-index: 100; display: none; backdrop-filter: blur(10px); max-width: 300px; }
#node-info h3 { color: #44ff88; margin-bottom: 8px; font-size: 16px; }
#node-info p { margin: 4px 0; color: #aaaacc; }
#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; }
.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>
<body>
<div id="canvas-container">
<div id="loading">正在加载星图数据...</div>
<div id="stats">
<h3>🌌 记忆星图</h3>
<p>节点: <span id="node-count">0</span></p>
<p>边: <span id="edge-count">0</span></p>
<p>状态: <span id="status">初始化中...</span></p>
</div>
<div id="node-info">
<h3 id="info-name"></h3>
<p><span class="label">类型:</span> <span id="info-type"></span></p>
<p><span class="label">提及次数:</span> <span id="info-mentions"></span></p>
<p><span class="label">连接数:</span> <span id="info-links"></span></p>
</div>
<div id="nav">
<button class="nav-btn active">🌌 星图</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
let scene, camera, renderer, controls;
let nodes = [], edges = [];
let nodeMeshes = [], edgeLines = [];
let starField, nebulaParticles;
let raycaster, mouse;
let hoveredNode = null, selectedNode = null;
let animationId;
let highlightPulse = 0;
const typeColors = { 'person': 0x4488ff, 'task': 0xff8844, 'ai': 0xaa44ff, 'concept': 0x44ff88, 'object': 0xff4444 };
const defaultColor = 0xcccccc;
const edgeColors = { '喜欢': 0xff6b6b, '学习': 0x4ecdc4, '属于': 0x45b7d1, '相关': 0x96ceb4, '使用': 0xfeca57, '创建': 0xff9ff3 };
const defaultEdgeColor = 0x444466;
function init() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.015);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(0, 30, 60);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x0a0a1a, 1);
document.getElementById('canvas-container').appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const ambientLight = new THREE.AmbientLight(0x444466, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
scene.add(directionalLight);
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
createStarField();
createNebula();
window.addEventListener('resize', onWindowResize);
renderer.domElement.addEventListener('mousemove', onMouseMove);
renderer.domElement.addEventListener('click', onMouseClick);
window.addEventListener('message', (event) => {
if (event.data.type === 'graph_data') {
window.__graphData = event.data.payload;
loadGraphData();
}
});
window.parent.postMessage('request_graph_data', '*');
animate();
}
function createStarField() {
const starCount = 3000;
const positions = new Float32Array(starCount * 3);
const colors = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const i3 = i * 3;
const radius = 400 + Math.random() * 600;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
positions[i3] = radius * Math.sin(phi) * Math.cos(theta);
positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
positions[i3 + 2] = radius * Math.cos(phi);
const colorChoice = Math.random();
if (colorChoice < 0.7) {
colors[i3] = 0.8 + Math.random() * 0.2;
colors[i3 + 1] = 0.8 + Math.random() * 0.2;
colors[i3 + 2] = 1.0;
} else {
colors[i3] = 1.0;
colors[i3 + 1] = 0.9 + Math.random() * 0.1;
colors[i3 + 2] = 0.8 + Math.random() * 0.2;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ size: 1.5, vertexColors: true, transparent: true, opacity: 0.8, sizeAttenuation: true });
starField = new THREE.Points(geometry, material);
scene.add(starField);
}
function createNebula() {
const nebulaCount = 500;
const positions = new Float32Array(nebulaCount * 3);
const colors = new Float32Array(nebulaCount * 3);
for (let i = 0; i < nebulaCount; i++) {
const i3 = i * 3;
positions[i3] = (Math.random() - 0.5) * 800;
positions[i3 + 1] = (Math.random() - 0.5) * 800;
positions[i3 + 2] = (Math.random() - 0.5) * 800;
const colorChoice = Math.random();
if (colorChoice < 0.33) {
colors[i3] = 0.5 + Math.random() * 0.3; colors[i3 + 1] = 0.2 + Math.random() * 0.2; colors[i3 + 2] = 0.7 + Math.random() * 0.3;
} else if (colorChoice < 0.66) {
colors[i3] = 0.2 + Math.random() * 0.2; colors[i3 + 1] = 0.3 + Math.random() * 0.3; colors[i3 + 2] = 0.8 + Math.random() * 0.2;
} else {
colors[i3] = 0.7 + Math.random() * 0.3; colors[i3 + 1] = 0.2 + Math.random() * 0.2; colors[i3 + 2] = 0.5 + Math.random() * 0.3;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ size: 8, vertexColors: true, transparent: true, opacity: 0.15, sizeAttenuation: true, blending: THREE.AdditiveBlending });
nebulaParticles = new THREE.Points(geometry, material);
scene.add(nebulaParticles);
}
window.loadGraphData = function(data) {
if (data && data.nodes && data.edges) {
nodes = data.nodes.map(n => ({ id: n.id, name: n.label || n.name, type: n.type, mention_count: n.mentions || 1 }));
edges = data.edges.map(e => ({ id: e.id, source: e.from || e.source, target: e.to || e.target, relation_type: e.label || e.relation }));
document.getElementById('node-count').textContent = nodes.length;
document.getElementById('edge-count').textContent = edges.length;
document.getElementById('status').textContent = '就绪';
createGraphVisualization();
document.getElementById('loading').style.display = 'none';
}
}
function createGraphVisualization() {
nodeMeshes.forEach(mesh => scene.remove(mesh));
edgeLines.forEach(line => scene.remove(line));
nodeMeshes = [];
edgeLines = [];
if (nodes.length === 0) return;
const nodeDegrees = {};
nodes.forEach(n => nodeDegrees[n.id] = 0);
edges.forEach(e => {
nodeDegrees[e.source] = (nodeDegrees[e.source] || 0) + 1;
nodeDegrees[e.target] = (nodeDegrees[e.target] || 0) + 1;
});
const 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++) {
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;
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;
}
});
});
edges.forEach(edge => {
const pos1 = positions[edge.source], pos2 = positions[edge.target];
if (!pos1 || !pos2) return;
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;
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;
}
});
}
nodes.forEach(node => {
const pos = positions[node.id];
if (!pos) return;
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 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);
});
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 line = new THREE.Line(geometry, material);
line.userData = { edgeId: edge.id, edgeData: edge };
scene.add(line);
edgeLines.push(line);
});
const allPositions = Object.values(positions);
if (allPositions.length > 0) {
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);
}
}
function onMouseMove(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 intersects = raycaster.intersectObjects(nodeMeshes);
if (intersects.length > 0) {
const node = intersects[0].object;
if (hoveredNode !== node) {
if (hoveredNode) hoveredNode.scale.set(1, 1, 1);
hoveredNode = node;
node.scale.set(1.2, 1.2, 1.2);
showNodeInfo(node.userData.nodeData);
}
} else {
if (hoveredNode) { hoveredNode.scale.set(1, 1, 1); hoveredNode = null; hideNodeInfo(); }
}
}
function onMouseClick(event) {
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 { selectedNode = node; showNodeInfo(node.userData.nodeData, true); }
}
}
function showNodeInfo(nodeData, isClick = false) {
document.getElementById('info-name').textContent = nodeData.name;
document.getElementById('info-type').textContent = nodeData.type;
document.getElementById('info-mentions').textContent = nodeData.mention_count;
const linkCount = edges.filter(e => e.source === nodeData.id || e.target === nodeData.id).length;
document.getElementById('info-links').textContent = linkCount;
if (isClick) document.getElementById('node-info').style.display = 'block';
}
function hideNodeInfo() { if (!selectedNode) document.getElementById('node-info').style.display = 'none'; }
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
animationId = requestAnimationFrame(animate);
const time = Date.now() * 0.001;
highlightPulse = (highlightPulse + 0.02) % (Math.PI * 2);
controls.update();
if (starField) starField.rotation.y += 0.0001;
if (hoveredNode) { const pulse = 1 + Math.sin(highlightPulse * 3) * 0.05; hoveredNode.scale.set(pulse * 1.2, pulse * 1.2, pulse * 1.2); }
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
{
"modelVersion": "5.0.5",
"dependencies": {
},
"execution": {
// "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */
// "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
// "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
// "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
// "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
// "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
// "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
// "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
}
}

6
harmony/hvigorfile.ts Normal file
View File

@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks,
plugins: []
};

2
harmony/local.properties Normal file
View File

@ -0,0 +1,2 @@
hwsdk.dir=/home/program/tools/command-line-tools/sdk/default
sdk.dir=/home/program/tools/command-line-tools/sdk/default

10
harmony/oh-package.json5 Normal file
View File

@ -0,0 +1,10 @@
{
"modelVersion": "5.0.5",
"description": "Please describe the basic information.",
"dependencies": {
},
"devDependencies": {
"@ohos/hypium": "1.0.24",
"@ohos/hamock": "1.0.0"
}
}

View File

@ -0,0 +1,30 @@
{
"app": {
"bundleName": "com.example.rconmc",
"debug": true,
"versionCode": 1000000,
"versionName": "1.0.0",
"minAPIVersion": 60001021,
"targetAPIVersion": 60001021,
"apiReleaseType": "Release",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"compileSdkVersion": "6.0.2.130",
"compileSdkType": "HarmonyOS",
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug"
},
"module": {
"name": "librcon",
"type": "har",
"requestPermissions": [],
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"packageName": "librcon",
"installationFree": false
}
}

View File

@ -0,0 +1,17 @@
/**
* Use these variables when you tailor your ArkTS code. They must be of the const type.
*/
export const HAR_VERSION = '1.0.0';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
export const TARGET_NAME = 'default';
/**
* BuildProfile Class is used only for compatibility purposes.
*/
export default class BuildProfile {
static readonly HAR_VERSION = HAR_VERSION;
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
static readonly DEBUG = DEBUG;
static readonly TARGET_NAME = TARGET_NAME;
}

View File

@ -0,0 +1,2 @@
// TrulyMEM Core - 导出核心功能
export { GraphDatabase } from './src/main/ets/GraphDatabase';

View File

@ -0,0 +1,41 @@
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
},
"consumerFiles": [
"./consumer-rules.txt"
]
}
},
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest"
}
]
}

Some files were not shown because too many files have changed in this diff Show More