在 HarmonyOS 上做一套真正能接入应用的 AI 对话组件:ArkGenie 项目介绍
https://gitcode.com/GeorgeGcs/ArkGenie
https://github.com/GeorgeGcs/ArkGenie
很多 AI 对话 Demo 的代码并不长。一个输入框、一个消息列表,再调用一次大模型接口,页面就能把回复显示出来。
真正把它放进应用之后,问题会很快出现:用户点击停止时,网络回调还在继续;上一轮请求晚到的内容混进了新消息;失败消息没有办法重试;切换会话之后,旧会话的内容还在刷新;流式文本每来一个字符就刷新一次页面,列表开始抖动。
ArkGenie 处理的就是这部分工作。它不是一个只负责画气泡的 UI 组件,而是一套面向 HarmonyOS ArkUI 的 AI 对话与生成式 UI 组件套件。项目把消息状态、传输层、会话管理、聊天界面和 A2UI 渲染拆成了几个独立模块,宿主应用可以按需要使用。
先跑起来:不用 API Key 的演示工程
仓库里的 entry 是完整的应用入口。打开仓库根目录,在 DevEco Studio 中同步依赖,运行 entry 模块即可看到聊天页面。
演示工程默认使用 MockTransport,所以不需要填写 API Key,也不需要先准备一个后端服务。它会把固定回复按词切开,间隔一小段时间交给上层,表现出和真实流式回复相同的 UI 行为。
最小用法大致是这样:
import { ChatController, MockTransport } from '@arkgenie/core';
import { ChatView } from '@arkgenie/chat-ui';
@Entry
@ComponentV2
struct Index {
private controller: ChatController = new ChatController(
new MockTransport(),
'你是一个简洁、友善的助手。'
);
build() {
ChatView({
controller: this.controller,
suggestedPrompts: [
'ArkGenie 能做什么?',
'如何接入真实的大模型?'
]
})
}
}
这个例子里没有把消息数组复制到页面组件中。ChatView 直接观察 ChatController,输入、停止、重试和滚动都由组件内部完成。宿主只需要决定使用哪个 Transport,以及是否接入自己的主题、会话存储和业务事件。
仓库里有哪些模块
ArkGenie 目前由三个 HAR 模块和一个可运行的示例入口组成。
@arkgenie/core
这是不依赖具体 UI 的核心层,负责:
- ChatMessage 消息模型和消息状态;
- ChatController 对话状态机;
- ChatTransport 传输抽象;
- SseTransport、MockTransport 和 OpenAI 兼容 Provider;
- SessionManager 多会话管理和持久化接口;
- ChatEventBus 对外发布消息发送、增量、完成、取消、错误和工具调用事件。
核心层只要求 Transport 实现 send 方法,并返回一个带 cancel() 的任务对象。这样,真实网络、Mock 数据、测试替身甚至 WebSocket 实现都可以放进同一套状态机。
@arkgenie/chat-ui
这个模块提供可以直接放进页面的聊天 UI:
- ChatView 整页聊天组件;
- MessageList 和 MessageBubble 消息列表与气泡;
- Composer 多行输入区,支持发送和停止切换;
- SuggestedPrompts 建议问题;
- ChatTheme 主题 Token;
- 一个不依赖第三方库的 Markdown 降级渲染器。
ChatView 的参数刻意保留了几个应用层出口,例如语音、附件、引用、反馈、工具操作和复制事件。组件负责显示和交互,文件选择、语音服务、埋点和业务处理仍由宿主应用决定。
@arkgenie/genui
genui 用来处理 A2UI 风格的声明式界面。它包含:
- JSON 解析器;
- 操作、组件类型和属性白名单校验;
- Surface 增量状态容器;
- 内置组件目录;
- ArkUI 渲染器和按钮、输入框、确认操作的事件回传。
远端消息只描述“显示什么”和“发生了什么操作”,不会把 ArkTS 代码传到客户端执行。未知组件会显示降级提示,不会通过 eval 或脚本执行的方式绕过目录限制。
entry
entry 是根工程的启动模块,主要用于演示和验收。它把三个库装配成一个可运行应用,也给出了 MockTransport 的默认配置。发布组件包时,宿主应用可以只依赖 core,也可以按页面需要引入 chat-ui 和 genui。
从 Mock 切换到真实模型
当页面和交互确认没有问题后,可以把 Transport 换成 OpenAiCompatibleProvider。它接收一个 OpenAI Chat Completions 风格的配置:
import {
ChatController,
OpenAiCompatibleProvider
} from '@arkgenie/core';
const provider = new OpenAiCompatibleProvider({
baseUrl: 'https://api.example.com/v1/chat/completions',
apiKey: 'YOUR_API_KEY',
model: 'your-model',
reconnectAttempts: 2,
reconnectDelayMs: 500,
heartbeatTimeoutMs: 30000
});
const controller = new ChatController(
provider,
'你是一个简洁、友善的助手。'
);
Provider 只做协议适配:把消息转换成请求体,添加 Authorization 请求头,解析 SSE 中的增量内容和工具调用。真正的消息生命周期仍由 ChatController 管理。这样做的好处是,换模型服务不会影响聊天界面,换界面也不会影响网络层。
生产应用里不建议把长期有效的 API Key 硬编码进客户端。更稳妥的方式是由应用后端提供短期凭据或统一代理,再让 ArkGenie 连接业务网关。示例工程只为了说明接口形态,默认仍然走本地 Mock。
会话和持久化不是写死的
ChatController 默认创建 SessionManager,而 SessionManager 默认使用内存存储。这对 Demo 足够,但应用退出后数据不会保留。
如果宿主已经有 Preferences 或数据库封装,只需实现 SessionStore,或者实现更小的 SessionValueStore 后使用 JsonSessionStore:
import {
JsonSessionStore,
SessionValueStore,
SessionManager,
ChatController
} from '@arkgenie/core';
class PreferencesValueStore implements SessionValueStore {
get(key: string): string | null {
// 从宿主的 Preferences 读取字符串
return null;
}
set(key: string, value: string): void {
// 写入宿主的 Preferences
}
remove(key: string): void {
// 删除宿主的 Preferences 键
}
}
const store = new JsonSessionStore(new PreferencesValueStore());
const sessions = new SessionManager(store);
const controller = new ChatController(new MockTransport(), '', sessions);
消息落盘时保存的是快照,不包含函数和运行时代码。宿主可以把快照写入自己的存储,也可以在退出前选择只保留最近几轮消息。
一套组件,几种使用方式
如果只需要调用模型并自己画 UI,可以只引入 @arkgenie/core:
const controller = new ChatController(new MockTransport());
controller.send('你好');
如果想快速搭一个聊天页面,使用 ChatView:
ChatView({
controller: this.controller,
theme: ChatTheme.dark(),
suggestedPrompts: ['帮我整理一份会议纪要']
})
如果应用需要让 Agent 动态生成卡片、表单或确认框,再把 genui 接上。三层并不是一整块不可替换的页面模板,而是从核心状态机向上逐层增加能力。
这个项目适合解决什么问题
ArkGenie 适合已经有 HarmonyOS 应用、但不想从头重复实现 AI 对话基础设施的团队。它把最容易互相缠绕的几部分拆开:网络层处理流,控制器处理状态,组件处理显示,宿主处理业务服务和数据存储。
这种拆分也意味着它不会替宿主应用决定所有事情。账号体系、API Key 的保管、语音服务、文件上传、消息审核和业务数据库,都应该留在应用自己的边界内。
如果你的需求只是一个静态问答页,直接写一个页面可能更快。如果需要停止、重试、多会话、流式输出、工具调用和生成式 UI,这些边界会让后续迭代轻松很多。
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
Do an AI dialogue component on HarmonyOS for real access applications: ArkGenie Project Introduction
https://gitcode.com/GeorgeGcs/ArkGenie
https://github.com/GeorgeGcs/ArkGenie
A lot of AI dialogue Demo's code is not long. An input box, a message list, and a LLM interface can be called again, so that the page can display the response.
When you really put it in the application, problems arise: when users stop clicking, web calls continue; the last round of late requests is mixed in with new messages; failed messages cannot be retry; the old sessions are refreshed after switching sessions; the current text is refreshed every single character and the list starts shaking.
ArkGenie handles this part of the job. It is not an UI component that only paints bubbles, but an AI dialogue and generation UI component package for HarmonyOS ArkUI. The project has several stand-alone modules for message status, transfer layer, session management, chat interface and A2UI rendering, and host applications can be used as needed.
Run first: no API Key demonstration project
The entry in the warehouse is the full application portal. Opens the repository root directory, depends on it in DevEco Studio, and runs an entry module to see chat pages.
The demonstration project is defaulted on the MockTransport, so there is no need to fill in API Key or to prepare a backend service. It cuts a fixed response by word and gives a short interval to the upper level, showing the same UI behaviour as the real flow response.
The minimum usage is roughly as follows:
import { ChatController, MockTransport } from '@arkgenie/core';
import { ChatView } from '@arkgenie/chat-ui';
@Entry
@ComponentV2
struct Index {
private controller: ChatController = new ChatController(
new MockTransport(),
'You're a simple, friendly assistant. ♪ I'm sorry ♪
);
build() {
ChatView({
controller: this.controller,
suggestedPrompts: [
'ArkGenie, what can you do? '...
'How to access the real LLM? ♪ I'm sorry ♪
]
})
}
}
This example does not copy message arrays to the page component. ChatView directly observes ChatController, input, stop, retry and rolling are all done within the component. The host needs to decide which transport to use and whether to access its theme, session storage and business events.
What are the modules in the warehouse?
ArkGenie currently consists of three HAR modules and a runable example entry.
@arkgenie/core
This is a core layer that does not depend on specific UIs, responsible for:
- ChatMessage message model and message status;
- ChatController dialogue status machine;
- ChatTransport transmission abstract;
- SseTransport, MockTransport and Open AI compatible Provider;
- SsessionManager multi-chamber management and permanent interface;
- ChateventBus external publish messages, increments, completion, cancellation, errors and tool call incidents.
The core level requires only that the Transport achieves the send method and returns a task object with cancel() So the real network, the mock data, the test double, and even the WebSocket realization can be put into the same set.
@arkgenie/chat-ui
UI:
- ChatView full page chat component;
- MessageList and MessageBubble message lists and bubbles;
- Composer multiline input area to support dispatch and stop switching;
- Suggested Prompts recommendations;
- ChatTheme Theme Token;
- A Markdown downgrader that doesn't depend on a third-party library.
ChatView's parameters deliberately preserve several layers of applications such as voice, accessories, references, feedback, tool operations and reproduction events. The components are shown and interactive, and document selection, voice services, burial sites and business processing are still determined by host applications.
@arkgenie/genui
ggenui handles the A2UI-style statement interface. It consists of:
- JSON solver;
- Verification of white lists of operations, types of components and attributes;
- Surface Incremental State Container;
- A catalogue of built-in components;
- The ArkUI renderer and button, the input box, the confirmation operation returns the event.
Remote messages describe only what to show and what to do, and do not transmit ArkTS code to the client for execution. Unknown component shows a downgrade hint and does not bypass the directory limit by eval or script execution.
entry
Entry is the start-up module of the root project, mainly for demonstration and acceptance check. It assembles three libraries into a runable application and gives a default configuration for LockTransport. When publish packages are used, the host application can rely on only core, or can introduce chat-ui and ggenui as page needs.
Switch from Lock to Real Model
Change to OpenAicompatibleProvider when page and interactive confirmation are clear. It receives an Open AI Chat strategies configuration:
import {
ChatController,
OpenAiCompatibleProvider
} from '@arkgenie/core';
const provider = new OpenAiCompatibleProvider({
baseUrl: 'https://api.example.com/v1/chat/completions',
apiKey: 'YOUR_API_KEY',
model: 'your-model',
reconnectAttempts: 2,
reconnectDelayMs: 500,
heartbeatTimeoutMs: 30000
});
const controller = new ChatController(
provider,
'You're a simple, friendly assistant. ♪ I'm sorry ♪
);
Provider only makes protocol appliance: converts messages into a request body, adds a request header for Autoration, interprets the incremental content of SSE and tool call. The true message life cycle is still managed by ChatController. The advantage of doing so is that changing the model service does not affect chat interfaces or the web layer.
The production application does not recommend the long-term validity of API Key hard encoding to the client. A more conservative approach would be to use the application backend to provide a short-term certificate or uniform agent and then to connect ArkGenie to the business gateway. The example project will only be used to describe the mode of the interface, and the default will still be the local Mock.
The conversation and durability are not written dead.
ChatController defaults to create SessionManager, while Session Manager defaults to use memory storage. This is enough for Demo, but the data will not be retained after the application exit.
If the host already has Preferences or database encapsulation, it will only be necessary to achieve SessionStore, or a smaller SessionValueStore, and then use JasonSsessionStore:
import {
JsonSessionStore,
SessionValueStore,
SessionManager,
ChatController
} from '@arkgenie/core';
class PreferencesValueStore implements SessionValueStore {
get(key: string): string | null {
// Read string from host Preferences
return null;
}
set(key: string, value: string): void {
/ Write host's Preferences
}
remove(key: string): void {
// Delete host Preferences
}
}
const store = new JsonSessionStore(new PreferencesValueStore());
const sessions = new SessionManager(store);
const controller = new ChatController(new MockTransport(), '', sessions);
The message drops with a snapshot and does not contain a function and running time code. The host can write a snapshot to its own storage or choose to keep only the latest rounds before exiting.
A set of components, several uses
If you just have to call the model and draw UI, you can introduce only @arkgenie/core:
const controller = new ChatController(new MockTransport());
I don't know.
If you want to make a quick chat page, use ChatView:
ChatView({
controller: this.controller,
theme: ChatTheme.dark(),
Surgested Prompts:
})
If the application requires that Agent dynamically generate a card, form or confirmation box, then connect to ggenui. The three floors are not an unreplaceable page template, but add capability from the core to the upper layer.
What's the problem with this project?
ArkGenie suits the team that already has HarmonyOS applications, but does not want to duplicate the AI dialogue infrastructure from scratch. It removes the most entanglements of each other: network layer processing streams, controller processing status, component processing displays, host processing business services and data storage.
This type of split also means that it does not decide everything for the host. The account system, API Key custody, voice services, document uploads, news clearance and operational databases should remain within the applicable borders.
If your need is just a static question and answer page, writing a page directly might be faster. If the need to stop, retry, multi-chamber, flow-out, tool call and the generated UI is needed, these boundaries will make subsequent overlaps much easier.
Auto-fetched and translated by AI Atlas. All rights belong to the original author.