refactor: 按 sample_in_harmonyos 多层模块化架构重构工程结构
- 新增 common/ 公共模块:模型、服务、组件、常量 - 新增 features/ 功能模块:graph(星图)、chat(聊天)、settings(设置)、commonbusiness - 新增 products/phone 产品入口层:EntryAbility + Index + MainPage - Index.ets 简化为纯路由入口,TabNavigation 移至 MainPage 统一管理 - 更新 build-profile.json5 注册所有新模块 - 迁移原始 entry/ 代码到分层架构,保持功能完整
This commit is contained in:
1
features/chat/Index.ets
Normal file
1
features/chat/Index.ets
Normal file
@ -0,0 +1 @@
|
||||
export { ChatPage } from './src/main/ets/pages/ChatPage';
|
||||
8
features/chat/build-profile.json5
Normal file
8
features/chat/build-profile.json5
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
features/chat/oh-package.json5
Normal file
11
features/chat/oh-package.json5
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@ohos/chat",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM chat feature module",
|
||||
"main": "Index.ets",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@ohos/common": "file:../../common"
|
||||
}
|
||||
}
|
||||
171
features/chat/src/main/ets/pages/ChatPage.ets
Normal file
171
features/chat/src/main/ets/pages/ChatPage.ets
Normal file
@ -0,0 +1,171 @@
|
||||
import { GraphDatabase } from '@ohos/common';
|
||||
import { GraphMemoryService } from '@ohos/common';
|
||||
import { AIAgentService, ChatMessage, AgentResponse } from '@ohos/common';
|
||||
|
||||
@Component
|
||||
export struct ChatPage {
|
||||
@State messages: ChatMessage[] = [];
|
||||
@State inputText: string = '';
|
||||
@Prop db: GraphDatabase;
|
||||
@State toolCallLog: string = '';
|
||||
@State isThinking: boolean = false;
|
||||
private scrollController: Scroller = new Scroller();
|
||||
private agentService?: AIAgentService;
|
||||
|
||||
async aboutToAppear() {
|
||||
// 初始化图记忆服务和 Agent
|
||||
const memoryService = new GraphMemoryService(this.db);
|
||||
this.agentService = new AIAgentService(memoryService, getContext(this));
|
||||
|
||||
// 加载历史消息(兼容旧数据:无 session_id 时加载全部)
|
||||
const rawHistory = await this.db.getChatHistory(50, this.agentService.getSessionId());
|
||||
if (rawHistory.length === 0) {
|
||||
// 新 session,尝试加载旧消息
|
||||
const legacyHistory = await this.db.getChatHistory(50);
|
||||
this.messages = legacyHistory.map(m => {
|
||||
const msg: ChatMessage = { role: m.role, content: m.content };
|
||||
return msg;
|
||||
});
|
||||
} else {
|
||||
this.messages = rawHistory.map(m => {
|
||||
const msg: ChatMessage = { role: m.role, content: m.content };
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
if (!this.inputText.trim() || !this.agentService) return;
|
||||
|
||||
const userMessage: string = this.inputText;
|
||||
this.inputText = '';
|
||||
|
||||
// 添加用户消息
|
||||
await this.db.saveChatMessage('user', userMessage, '', this.agentService.getSessionId());
|
||||
this.messages = [...this.messages, { role: 'user', content: userMessage }];
|
||||
|
||||
// 显示 loading
|
||||
this.isThinking = true;
|
||||
this.toolCallLog = '';
|
||||
|
||||
try {
|
||||
// 通过 Agent 发送消息
|
||||
const agentResponse: AgentResponse = await this.agentService.sendMessage(userMessage);
|
||||
|
||||
// 记录工具调用日志
|
||||
if (agentResponse.toolCalls.length > 0) {
|
||||
const logs: string[] = agentResponse.toolCalls.map(tc => `🛠 ${tc.name}: ${tc.message}`);
|
||||
this.toolCallLog = logs.join('\n');
|
||||
}
|
||||
|
||||
// 保存并显示 AI 回复
|
||||
await this.db.saveChatMessage('assistant', agentResponse.content, this.toolCallLog, this.agentService.getSessionId());
|
||||
this.messages = [...this.messages, { role: 'assistant', content: agentResponse.content }];
|
||||
} catch (err) {
|
||||
console.error('Agent request failed: ' + JSON.stringify(err));
|
||||
this.messages = [...this.messages, { role: 'assistant', content: `⚠️ 请求失败: ${err.message || JSON.stringify(err)}` }];
|
||||
} finally {
|
||||
this.isThinking = false;
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 聊天列表
|
||||
List() {
|
||||
ForEach(this.messages, (msg: ChatMessage) => {
|
||||
ListItem() {
|
||||
Column() {
|
||||
// 角色标识
|
||||
Text(msg.role === 'user' ? '🧑 你' : '🤖 AI')
|
||||
.fontSize(11)
|
||||
.fontColor(msg.role === 'user' ? '#7C4DFF' : '#999')
|
||||
.width('100%')
|
||||
|
||||
// 消息内容
|
||||
Text(msg.content)
|
||||
.fontSize(15)
|
||||
.width('100%')
|
||||
.margin({ top: 4 })
|
||||
.fontColor('#FFFFFF')
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor(msg.role === 'user' ? 'rgba(124,77,255,0.15)' : 'rgba(245,245,245,0.1)')
|
||||
.borderRadius(12)
|
||||
.border({ width: 1, color: msg.role === 'user' ? 'rgba(124,77,255,0.3)' : 'rgba(255,255,255,0.1)' })
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ left: 8, right: 8, bottom: 8 })
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
})
|
||||
|
||||
// loading 指示
|
||||
if (this.isThinking) {
|
||||
ListItem() {
|
||||
Row() {
|
||||
LoadingProgress()
|
||||
.width(20)
|
||||
.height(20)
|
||||
.margin({ right: 8 })
|
||||
.color('#7C4DFF')
|
||||
Text('AI 思考中...')
|
||||
.fontSize(13)
|
||||
.fontColor('#7C4DFF')
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor('rgba(124,77,255,0.1)')
|
||||
.borderRadius(12)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.2)' })
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ left: 8, right: 8, bottom: 8 })
|
||||
}
|
||||
}
|
||||
|
||||
// 工具调用日志
|
||||
if (this.toolCallLog && !this.isThinking) {
|
||||
ListItem() {
|
||||
Text(this.toolCallLog)
|
||||
.fontSize(10)
|
||||
.fontColor('#FF9800')
|
||||
.backgroundColor('rgba(255,152,0,0.1)')
|
||||
.padding(8)
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(255,152,0,0.2)' })
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ left: 8, right: 8, bottom: 4 })
|
||||
.lineHeight(16)
|
||||
}
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.backgroundColor('rgba(0,0,0,0.1)')
|
||||
|
||||
// 输入区
|
||||
Row() {
|
||||
TextArea({ text: this.inputText, placeholder: '输入消息...' })
|
||||
.layoutWeight(1)
|
||||
.onChange((v: string) => { this.inputText = v; })
|
||||
.height(40)
|
||||
.backgroundColor('rgba(255,255,255,0.1)')
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.3)' })
|
||||
|
||||
Button('发送')
|
||||
.enabled(!this.isThinking)
|
||||
.onClick(() => this.sendMessage())
|
||||
.backgroundColor('#7C4DFF')
|
||||
.borderRadius(8)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(8)
|
||||
.backgroundColor('rgba(255,255,255,0.05)')
|
||||
.backgroundBlurStyle(BlurStyle.Regular)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.2)', style: BorderStyle.Solid })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('rgba(26,27,46,0.95)')
|
||||
}
|
||||
}
|
||||
1
features/commonbusiness/Index.ets
Normal file
1
features/commonbusiness/Index.ets
Normal file
@ -0,0 +1 @@
|
||||
// Common business module - placeholder for shared business logic
|
||||
8
features/commonbusiness/build-profile.json5
Normal file
8
features/commonbusiness/build-profile.json5
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
features/commonbusiness/oh-package.json5
Normal file
11
features/commonbusiness/oh-package.json5
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@ohos/commonbusiness",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM common business module",
|
||||
"main": "Index.ets",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@ohos/common": "file:../../common"
|
||||
}
|
||||
}
|
||||
1
features/graph/Index.ets
Normal file
1
features/graph/Index.ets
Normal file
@ -0,0 +1 @@
|
||||
export { GraphPage } from './src/main/ets/pages/GraphPage';
|
||||
8
features/graph/build-profile.json5
Normal file
8
features/graph/build-profile.json5
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
features/graph/oh-package.json5
Normal file
11
features/graph/oh-package.json5
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@ohos/graph",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM graph feature module",
|
||||
"main": "Index.ets",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@ohos/common": "file:../../common"
|
||||
}
|
||||
}
|
||||
313
features/graph/src/main/ets/pages/GraphPage.ets
Normal file
313
features/graph/src/main/ets/pages/GraphPage.ets
Normal file
@ -0,0 +1,313 @@
|
||||
/**
|
||||
* GraphPage — 记忆星图页面
|
||||
* 使用 WebView 显示 Three.js 3D 图可视化
|
||||
* 通过 javaScriptProxy 与 WebView 双向通信
|
||||
*/
|
||||
import web_webview from '@ohos.web.webview';
|
||||
import { GraphDatabase, RecallEntity } from '@ohos/common';
|
||||
import { GraphMemoryService, ConnectionItem, NodeDetailInfo } from '@ohos/common';
|
||||
|
||||
// ========= 图数据结构定义 =========
|
||||
|
||||
interface GraphNodeItem {
|
||||
id: number;
|
||||
label: string;
|
||||
type: string;
|
||||
mentions: number;
|
||||
}
|
||||
|
||||
interface GraphEdgeItem {
|
||||
id: number;
|
||||
source: number;
|
||||
target: number;
|
||||
label: string;
|
||||
relation: string;
|
||||
}
|
||||
|
||||
// NodeDetailInfo 已从 GraphMemoryService 导入
|
||||
|
||||
// ========= WebView 原生桥接 =========
|
||||
|
||||
class NativeBridge {
|
||||
private controller: web_webview.WebviewController;
|
||||
private onRequestGraphData: () => void;
|
||||
private onNodeClickCallback: (nodeId: number, nodeName: string) => void;
|
||||
private onSearchCallback: (query: string) => void;
|
||||
|
||||
constructor(
|
||||
controller: web_webview.WebviewController,
|
||||
onRequestGraphData: () => void,
|
||||
onNodeClickCallback: (nodeId: number, nodeName: string) => void,
|
||||
onSearchCallback: (query: string) => void
|
||||
) {
|
||||
this.controller = controller;
|
||||
this.onRequestGraphData = onRequestGraphData;
|
||||
this.onNodeClickCallback = onNodeClickCallback;
|
||||
this.onSearchCallback = onSearchCallback;
|
||||
}
|
||||
|
||||
onNodeClick(nodeId: number, nodeName: string): void {
|
||||
console.info('Node clicked: id=' + nodeId + ', name=' + nodeName);
|
||||
if (this.onNodeClickCallback) {
|
||||
this.onNodeClickCallback(nodeId, nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(query: string): void {
|
||||
console.info('Search from WebView: ' + query);
|
||||
if (this.onSearchCallback) {
|
||||
this.onSearchCallback(query);
|
||||
}
|
||||
}
|
||||
|
||||
requestGraphData(): void {
|
||||
console.info('requestGraphData called from WebView');
|
||||
if (this.onRequestGraphData) {
|
||||
this.onRequestGraphData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========= GraphPage 组件 =========
|
||||
|
||||
@Component
|
||||
export struct GraphPage {
|
||||
private controller: web_webview.WebviewController = new web_webview.WebviewController();
|
||||
@Prop db: GraphDatabase;
|
||||
@State nodeCount: number = 0;
|
||||
@State edgeCount: number = 0;
|
||||
@State selectedNodeDetail: NodeDetailInfo | null = null;
|
||||
@State showNodeDetail: boolean = false;
|
||||
@State searchText: string = '';
|
||||
private graphService: GraphMemoryService = new GraphMemoryService(this.db);
|
||||
|
||||
// 初始化桥接对象
|
||||
private bridge: NativeBridge = new NativeBridge(
|
||||
this.controller,
|
||||
(): void => { this.pushGraphDataToWebView(); },
|
||||
(nodeId: number, nodeName: string): void => { this.handleNodeClick(nodeId, nodeName); },
|
||||
(query: string): void => { this.handleSearchFromWeb(query); }
|
||||
);
|
||||
|
||||
/**
|
||||
* 外部触发刷新图数据(聊天写入新记忆后调用)
|
||||
*/
|
||||
public async refreshGraphData(): Promise<void> {
|
||||
await this.pushGraphDataToWebView();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理节点点击 - 查询详细信息并显示浮层
|
||||
*/
|
||||
private async handleNodeClick(nodeId: number, nodeName: string): Promise<void> {
|
||||
try {
|
||||
// 从数据库查询节点详细信息
|
||||
const detail = await this.graphService.getNodeDetail(nodeName);
|
||||
if (detail) {
|
||||
this.selectedNodeDetail = detail;
|
||||
this.showNodeDetail = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('handleNodeClick error: ' + JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理来自 WebView 的搜索请求
|
||||
*/
|
||||
private handleSearchFromWeb(query: string): void {
|
||||
this.searchText = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理搜索输入 - 通知 WebView 过滤
|
||||
*/
|
||||
private onSearchInput(value: string): void {
|
||||
this.searchText = value;
|
||||
const jsCode = `window.dispatchEvent(new MessageEvent('message', { data: { type: 'search_nodes', query: '${value}' } }));`;
|
||||
this.controller.runJavaScript(jsCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭节点详情浮层
|
||||
*/
|
||||
private closeNodeDetail(): void {
|
||||
this.showNodeDetail = false;
|
||||
this.selectedNodeDetail = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取全量图数据,通过 runJavaScript 推送给 WebView
|
||||
*/
|
||||
private async pushGraphDataToWebView(): Promise<void> {
|
||||
try {
|
||||
// 获取所有节点
|
||||
const allNodes: GraphNodeItem[] = await this.getAllNodesData();
|
||||
// 获取所有活跃关系
|
||||
const allEdges: GraphEdgeItem[] = await this.getAllEdgesData();
|
||||
|
||||
// 通过 JavaScript Bridge 推送数据
|
||||
if (this.controller) {
|
||||
const jsCode: string =
|
||||
`window.loadGraphData(${JSON.stringify({ nodes: allNodes, edges: allEdges })});`;
|
||||
this.controller.runJavaScript(jsCode);
|
||||
}
|
||||
|
||||
this.nodeCount = allNodes.length;
|
||||
this.edgeCount = allEdges.length;
|
||||
} catch (err) {
|
||||
console.error('pushGraphDataToWebView error: ' + JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库查询所有节点
|
||||
*/
|
||||
private async getAllNodesData(): Promise<GraphNodeItem[]> {
|
||||
const result = await this.db.search('');
|
||||
const items: GraphNodeItem[] = result.map((r, idx): GraphNodeItem => {
|
||||
const item: GraphNodeItem = {
|
||||
id: idx + 1,
|
||||
label: r.name,
|
||||
type: r.type,
|
||||
mentions: r.mentions
|
||||
};
|
||||
return item;
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库查询所有活跃关系
|
||||
*/
|
||||
private async getAllEdgesData(): Promise<GraphEdgeItem[]> {
|
||||
const recallResult = await this.db.recall('', [], 3);
|
||||
const nameToId: Record<string, number> = {};
|
||||
recallResult.entities.forEach((e: RecallEntity, idx: number): void => {
|
||||
nameToId[e.name as string] = idx + 1;
|
||||
});
|
||||
const edgeItems: GraphEdgeItem[] = [];
|
||||
for (let i = 0; i < recallResult.relations.length; i++) {
|
||||
const r = recallResult.relations[i];
|
||||
const sourceId: number | undefined = nameToId[r.source];
|
||||
const targetId: number | undefined = nameToId[r.target];
|
||||
if (sourceId !== undefined && targetId !== undefined) {
|
||||
const edgeItem: GraphEdgeItem = {
|
||||
id: i + 1,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
label: r.type,
|
||||
relation: r.type
|
||||
};
|
||||
edgeItems.push(edgeItem);
|
||||
}
|
||||
}
|
||||
return edgeItems;
|
||||
}
|
||||
|
||||
build() {
|
||||
Stack() {
|
||||
// WebView 显示 3D 星图
|
||||
Web({ src: $rawfile('graph.html'), controller: this.controller })
|
||||
.javaScriptAccess(true)
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.zoomAccess(true) // 启用缩放(自带双指捏合)
|
||||
.onPageEnd(() => {
|
||||
this.pushGraphDataToWebView();
|
||||
})
|
||||
// 注册原生桥接对象,供 WebView JavaScript 调用
|
||||
.javaScriptProxy({
|
||||
object: this.bridge,
|
||||
name: 'nativeBridge',
|
||||
methodList: ['onNodeClick', 'onSearch'],
|
||||
asyncMethodList: ['requestGraphData'],
|
||||
controller: this.controller
|
||||
})
|
||||
|
||||
// 搜索框组件 - 在 WebView 上方
|
||||
Column() {
|
||||
TextInput({ placeholder: '搜索节点...', text: this.searchText })
|
||||
.width('80%')
|
||||
.height(40)
|
||||
.backgroundColor('rgba(10, 10, 26, 0.8)')
|
||||
.fontColor('#ffffff')
|
||||
.placeholderColor('#666688')
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' })
|
||||
.margin({ top: 20 })
|
||||
.onChange((value: string) => {
|
||||
this.onSearchInput(value);
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.position({ x: 0, y: 0 })
|
||||
.zIndex(10)
|
||||
|
||||
// 节点详情浮层
|
||||
if (this.showNodeDetail && this.selectedNodeDetail !== null) {
|
||||
Column() {
|
||||
Column() {
|
||||
Text(this.selectedNodeDetail.name)
|
||||
.fontSize(18)
|
||||
.fontColor('#44ff88')
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 10 })
|
||||
|
||||
Text('类型: ' + this.selectedNodeDetail.type)
|
||||
.fontSize(14)
|
||||
.fontColor('#aaaacc')
|
||||
|
||||
Text('提及次数: ' + this.selectedNodeDetail.mention_count)
|
||||
.fontSize(14)
|
||||
.fontColor('#aaaacc')
|
||||
|
||||
Text('连接数: ' + this.selectedNodeDetail.connection_count)
|
||||
.fontSize(14)
|
||||
.fontColor('#aaaacc')
|
||||
|
||||
if (this.selectedNodeDetail.connections && this.selectedNodeDetail.connections.length > 0) {
|
||||
Text('连接关系:')
|
||||
.fontSize(14)
|
||||
.fontColor('#8888aa')
|
||||
.margin({ top: 10, bottom: 5 })
|
||||
List() {
|
||||
ForEach(this.selectedNodeDetail.connections, (conn: ConnectionItem) => {
|
||||
ListItem() {
|
||||
Text(conn.type + ': ' + conn.target_name)
|
||||
.fontSize(12)
|
||||
.fontColor('#aaaacc')
|
||||
}
|
||||
})
|
||||
}
|
||||
.height(100)
|
||||
}
|
||||
|
||||
Button('关闭')
|
||||
.width(80)
|
||||
.height(30)
|
||||
.margin({ top: 15 })
|
||||
.backgroundColor('rgba(100, 100, 255, 0.3)')
|
||||
.fontColor('#ffffff')
|
||||
.onClick(() => {
|
||||
this.closeNodeDetail();
|
||||
})
|
||||
}
|
||||
.padding(20)
|
||||
.backgroundColor('rgba(10, 10, 26, 0.95)')
|
||||
.borderRadius(12)
|
||||
.border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' })
|
||||
.width(300)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('rgba(0, 0, 0, 0.5)')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.zIndex(20)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
}
|
||||
1
features/settings/Index.ets
Normal file
1
features/settings/Index.ets
Normal file
@ -0,0 +1 @@
|
||||
export { SettingsPage } from './src/main/ets/pages/SettingsPage';
|
||||
8
features/settings/build-profile.json5
Normal file
8
features/settings/build-profile.json5
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
features/settings/oh-package.json5
Normal file
11
features/settings/oh-package.json5
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@ohos/settings",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM settings feature module",
|
||||
"main": "Index.ets",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@ohos/common": "file:../../common"
|
||||
}
|
||||
}
|
||||
126
features/settings/src/main/ets/pages/SettingsPage.ets
Normal file
126
features/settings/src/main/ets/pages/SettingsPage.ets
Normal file
@ -0,0 +1,126 @@
|
||||
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() {
|
||||
Stack() {
|
||||
// 主题色光晕背景
|
||||
Column()
|
||||
.width(200)
|
||||
.height(200)
|
||||
.backgroundColor('rgba(124,77,255,0.15)')
|
||||
.blur(40)
|
||||
.borderRadius(100)
|
||||
.position({ x: '10%', y: '20%' })
|
||||
|
||||
Column() {
|
||||
Text('API 配置')
|
||||
.fontSize(24)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor('#FFFFFF')
|
||||
.margin({ top: 20, bottom: 16 })
|
||||
|
||||
// Base URL 设置项
|
||||
Column() {
|
||||
Text('Base URL')
|
||||
.fontSize(14)
|
||||
.fontColor('#FFFFFF')
|
||||
.width('100%')
|
||||
.margin({ bottom: 8 })
|
||||
|
||||
TextInput({ placeholder: 'https://api.deepseek.com', text: this.baseUrl })
|
||||
.onChange((v: string) => { this.onBaseUrlChange(v); })
|
||||
.backgroundColor('rgba(255,255,255,0.1)')
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.3)' })
|
||||
.height(40)
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor('rgba(255,255,255,0.05)')
|
||||
.borderRadius(12)
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ bottom: 12 })
|
||||
|
||||
// Model ID 设置项
|
||||
Column() {
|
||||
Text('Model ID')
|
||||
.fontSize(14)
|
||||
.fontColor('#FFFFFF')
|
||||
.width('100%')
|
||||
.margin({ bottom: 8 })
|
||||
|
||||
TextInput({ placeholder: 'deepseek-chat', text: this.model })
|
||||
.onChange((v: string) => { this.onModelChange(v); })
|
||||
.backgroundColor('rgba(255,255,255,0.1)')
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.3)' })
|
||||
.height(40)
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor('rgba(255,255,255,0.05)')
|
||||
.borderRadius(12)
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ bottom: 12 })
|
||||
|
||||
// API Key 设置项
|
||||
Column() {
|
||||
Text('API Key')
|
||||
.fontSize(14)
|
||||
.fontColor('#FFFFFF')
|
||||
.width('100%')
|
||||
.margin({ bottom: 8 })
|
||||
|
||||
TextInput({ placeholder: 'sk-...', text: this.apiKey })
|
||||
.type(InputType.Password)
|
||||
.onChange((v: string) => { this.onApiKeyChange(v); })
|
||||
.backgroundColor('rgba(255,255,255,0.1)')
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: 'rgba(124,77,255,0.3)' })
|
||||
.height(40)
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor('rgba(255,255,255,0.05)')
|
||||
.borderRadius(12)
|
||||
.backgroundBlurStyle(BlurStyle.Thin)
|
||||
.margin({ bottom: 12 })
|
||||
}
|
||||
.padding(16)
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('rgba(26,27,46,0.95)')
|
||||
.backgroundBlurStyle(BlurStyle.Regular)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user