Files
HomeAgent/cmd/ohos/HologramAgent/entry/src/main/ets/pages/PluginsPage.ets
JianFeeeee f256098c9f refactor: 全面改名为 HologramAgent + 提示词技术内容下沉到 skill/工具描述 + 修 21 处乱码
## ① 全面改名 HomeAgent → HologramAgent

仓库已改名,但代码与文档里仍有大量旧名。本次按"用户能看到的优先"全量替换:

- 用户可见:登录页标题/页脚、默认人格第一句、CLI 状态输出、启动日志、
  plugin_install 的工具描述、AI 图片插件的 User-Agent
- 文档与脚本:README、assets/docs、docs/zh、deploy/*.sh、.github/workflows
- 鸿蒙工程:cmd/ohos/HomeAgent → cmd/ohos/HologramAgent(git mv)
  目录内的 module/app 描述同步;**证书路径原样保留**
  (default_HomeAgent_xxx.cer/p7b/p12 是签名材料的实际文件名,改了会找不到证书)
- 插件元数据:plugin.json 的 author

★ 必须保持原样的两处(改了会坏):
  · legacySeededPromptSignature = "你是 HomeAgent 的看板娘「小宅」…"
    —— 那是识别历史播种人格卡的**原文锚点**,改了就识别不出旧数据
  · 公开 SDK 的 ProxyAuthHomeAgent = "homeagent"
    —— 那是 SDK 契约里的值,属独立仓库,本仓不改

## ② 修 21 处乱码(U+FFFD)

这些是历史编辑留下的编码破坏,全在注释里,例如:
  「赌注重下���遍」→「赌注重下一遍」
  「「不��滤」」  →「「不过滤」」
  「**待��退场**」→「**待退场**」

★ 保留 providers/chineseclip/tokenizer.go 里的 2 处:那是**故意**用
  U+FFFD 演示"非法 UTF-8 字节被归一成 U+FFFD",不是乱码。

## ③ 提示词技术内容下沉(用户:技术性内容应该收敛到 skill 与 -help 中)

实测系统提示词 2309 字,其中两段几乎全是**模型无法据以行动的实现细节**:

  【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、batchRunnable 判据…)
  【三层记忆】   1069 字(L1/L2/L3 编号、消费式删除、向量空间、指纹…)

收敛后 **2309 → 1723 字(-25%)**,保留的全是"模型据此做什么不同的事":

  工具顺序:默认并行 / 不要依赖先后 / 同通道保序 / 写类工具可能串行
  三层记忆:时序里能看最近 / doc_query 把历史事件按原始时间戳插回时序 /
           关系用 memory_recall / 成文知识用 knowledge_search

判断口径写进判据:新加一段提示词前先问"模型会据此做什么不同的事",
答不上来就该进 skill 或工具描述。

## 判据

prompt_memory_test.go 新增 2 条:
- TestSystemPromptHasNoImplementationJargon
  黑名单:ParallelSafe / batchRunnable / 消费式 / DenseCosine / fingerprint /
  向量空间 / 批内降级 / 自激
- TestSystemPromptStaysCompact:上限 2000 字(当前 1723)

变异:把 "ParallelSafe 声明、批内降级" 加回工具顺序段 ⇒ 第一条立刻红。

## 验证

- go build ./... / go vet ./... 干净
- go test ./... -count=1:58 包 0 失败
- make check-client-versions:客户端与内核版本一致
2026-10-22 20:49:44 +08:00

375 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { apiClient } from '../common/ApiClient';
import { userMessage } from '../common/UserError';
import { handleNavOnScroll } from '../common/NavBarController';
import { registerNavStack, unregisterNavStack } from '../common/NavStackRegistry';
import { PluginRow, PluginDetail, emptyPluginDetail } from '../model/Model';
import { ThemePalette, DARK_PALETTE, LIGHT_PALETTE, WIDE_NAV_BAR_WIDTH, WIDE_MIN_CONTENT } from '../common/Constants';
import { ANIM_NORMAL, ANIM_ENTER } from '../common/Constants';
import { PageTopBar, NavFloatOverlay, NavFloatRow, FloatIconButton } from '../components/PageTopBar';
import { SubPageLayer, markSubPageOpen, subPageParam } from '../components/SubPage';
import { PluginDetailPane } from '../components/PluginDetailPane';
import { PluginListView } from '../components/PluginListView';
import { PluginInstallForm } from '../components/PluginsOverlays';
import { ToastBar } from '../components/ToastBar';
import { fetchPluginRows, fetchPluginDetail } from '../common/PluginApi';
/** 二级页面标识:插件详情 */
const SUB_NONE: string = '';
const SUB_DETAIL: string = 'detail';
interface InstallBody {
url: string;
}
@Component
export struct PluginsPage {
@StorageProp('themeIsDark') private isDark: boolean = true;
@StorageProp('navVisible') private navVisible: boolean = true;
@StorageProp('currentTab') private currentTab: number = 0;
/** 宽屏:左边插件列表(含底部导航栏),右边插件详情 */
@StorageProp('isWideScreen') private isWide: boolean = false;
@State plugins: PluginRow[] = [];
@State loading: boolean = false;
@State showInstallForm: boolean = false;
@State installUrl: string = '';
@State toastMsg: string = '';
@State toastIsError: boolean = false;
/** 当前查看详情的插件名,用于宽屏下高亮左侧列表项 */
@State activeName: string = '';
/** 详情页数据(GET /plugins/{name}) */
@State detail: PluginDetail = emptyPluginDetail();
@State detailLoading: boolean = false;
@State detailError: string = '';
/** 二级页面导航栈:系统返回手势/三键返回直接作用于它 */
private navStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
// 登记导航栈:返回手势由 Index.onBackPress 按当前 Tab 精确派发过来
registerNavStack(1, this.navStack, () => {
this.activeName = '';
});
this.loadPlugins();
}
aboutToDisappear(): void {
unregisterNavStack(1);
}
private palette(): ThemePalette {
// 引用 this.isDark 建立响应式依赖:主题切换时整个组件树重渲染
return this.isDark ? DARK_PALETTE : LIGHT_PALETTE;
}
/** 取数口径见 common/PluginApi.ets;这里只负责把结果落到 @State。 */
private async loadPlugins(): Promise<void> {
if (!apiClient.hasConnection()) {
return;
}
this.loading = true;
try {
const rows: PluginRow[] = await fetchPluginRows();
// 列表整体重建(ForEach key 含 loaded/disabled,启停会整行重挂载):
// 放进 animateTo 让新旧行走 transition 交叉淡入,而不是硬切一帧。
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.plugins = rows;
});
} catch (e) {
this.showToast(userMessage('plugins.load', e), true);
}
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.loading = false;
});
}
private async togglePlugin(plugin: PluginRow): Promise<void> {
const name: string = plugin.name;
const action: string = plugin.disabled ? 'enable' : 'disable';
try {
await apiClient.post('/plugins/' + name + '/' + action, null);
this.showToast('已' + (plugin.disabled ? '启用' : '禁用') + '插件: ' + name, false);
this.loadPlugins();
} catch (e) {
this.showToast(userMessage('plugins.toggle', e), true);
}
}
private async removePlugin(plugin: PluginRow): Promise<void> {
const name: string = plugin.name;
try {
await apiClient.request('/plugins/' + name, 'DELETE', '', 8000);
this.showToast('已卸载插件: ' + name, false);
this.loadPlugins();
} catch (e) {
this.showToast(userMessage('plugins.remove', e), true);
}
}
private async installPlugin(): Promise<void> {
const url: string = this.installUrl.trim();
if (url.length === 0) {
return;
}
try {
const bodyObj: InstallBody = { url: url };
// 后端没有 /plugins/install 这个路由:安装就是 POST /plugins,body 带 url。
await apiClient.post('/plugins', bodyObj);
this.showToast('安装请求已发送', false);
this.installUrl = '';
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.showInstallForm = false;
});
this.loadPlugins();
} catch (e) {
this.showToast(userMessage('plugins.install', e), true);
}
}
// ===================== 二级页面:插件详情 =====================
/**
* 打开插件详情。
*
* 窄屏(Stack):push 一层整屏覆盖,系统侧滑返回可退。
* 宽屏(Split):左栏列表常驻,右栏换页而不叠栈。
*/
private openDetail(name: string): void {
this.activeName = name;
this.loadDetail(name);
if (this.isWide && this.navStack.size() > 0) {
this.navStack.replacePathByName(SUB_DETAIL, subPageParam(SUB_DETAIL), false);
} else {
this.navStack.pushPathByName(SUB_DETAIL, subPageParam(SUB_DETAIL), true);
}
}
private closeDetail(): void {
this.navStack.pop();
this.activeName = '';
}
/**
* 详情数据落地。
* 内置插件不在 /plugins 里,取不到详情时退回用列表已有的信息。
*/
private async loadDetail(name: string): Promise<void> {
this.detailLoading = true;
this.detailError = '';
try {
const d: PluginDetail = await fetchPluginDetail(name);
// 详情字段一次性落地:条件卡片在 animateTo 帧内挂载,V1 给它们默认透明度过渡
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.detail = d;
});
} catch (e) {
// 内置插件没有清单,属于预期情况,不当成错误刷红
const row: PluginRow | null = this.findRow(name);
const d: PluginDetail = emptyPluginDetail();
d.name = name;
if (row !== null) {
d.version = row.version ?? '';
d.description = row.description ?? '';
}
const errText: string = (row !== null && row.external) ? userMessage('plugins.detail', e) : '';
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.detail = d;
this.detailError = errText;
});
}
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.detailLoading = false;
});
}
private findRow(name: string): PluginRow | null {
for (let i = 0; i < this.plugins.length; i++) {
if (this.plugins[i].name === name) {
return this.plugins[i];
}
}
return null;
}
/** 详情页正在展示的那一行(供二级页面里的操作按钮使用) */
private activeRow(): PluginRow | null {
return this.findRow(this.activeName);
}
private async reloadPlugins(): Promise<void> {
try {
await apiClient.post('/plugins/reload', null);
this.showToast('插件已重载', false);
this.loadPlugins();
} catch (e) {
this.showToast(userMessage('plugins.reload', e), true);
}
}
private showToast(msg: string, isError: boolean): void {
// 颜色标志必须在 animateTo 闭包之外先落地,否则第一帧配色是上一次的
this.toastIsError = isError;
this.getUIContext().animateTo({ duration: ANIM_NORMAL, curve: Curve.EaseOut }, () => {
this.toastMsg = msg;
});
setTimeout(() => {
this.getUIContext().animateTo({ duration: ANIM_NORMAL, curve: Curve.EaseOut }, () => {
this.toastMsg = '';
});
}, 2500);
}
build() {
// 宽屏(>=600vp)走 Split:navBar(左栏 = 插件列表 + 底部导航栏)常驻,
// NavDestination(右栏 = 插件详情)与之并列;窄屏则是整屏覆盖的二级页面。
Navigation(this.navStack) {
Stack({ alignContent: Alignment.Bottom }) {
Column() {
Scroll() {
Column() {
PluginListView({
plugins: this.plugins,
busy: this.loading,
activeName: this.activeName,
hasConn: apiClient.hasConnection(),
onSelect: (name: string) => {
this.openDetail(name);
},
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 76, bottom: 174 })
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
.align(Alignment.Top)
.onDidScroll((xOffset: number, yOffset: number, state: ScrollState) => {
handleNavOnScroll(state);
})
}
.width('100%')
.height('100%')
// 顶栏遮罩(自身撑满并顶部对齐,全链路 hitTest None,触摸完全穿透到滚动区)
PageTopBar({ title: '插件' })
// 悬浮操作区:安装(悬浮主按钮)+ 重载。
// 安装表单以悬浮卡形式浮在按钮上方,不再占用列表顶部一行。
NavFloatOverlay({ tab: 1 }) {
if (this.showInstallForm) {
PluginInstallForm({
url: this.installUrl,
onUrlChange: (v: string) => {
this.installUrl = v;
},
onInstall: () => {
this.installPlugin();
},
})
}
NavFloatRow() {
FloatIconButton({
icon: $r('app.media.ic_reload'),
onTap: () => {
this.reloadPlugins();
},
})
Button() {
Image($r('app.media.ic_plus'))
.width(20)
.height(20)
.fillColor(Color.White)
.rotate({ angle: this.showInstallForm ? 45 : 0 })
.animation({ duration: ANIM_NORMAL, curve: Curve.EaseOut })
}
.width(50)
.height(50)
.type(ButtonType.Circle)
.backgroundColor(this.palette().accent)
.shadow({ radius: 24, color: this.palette().shadow, offsetY: 8 })
.onClick(() => {
this.getUIContext().animateTo({ duration: ANIM_ENTER, curve: Curve.EaseOut }, () => {
this.showInstallForm = !this.showInstallForm;
});
})
}
}
ToastBar({ msg: this.toastMsg, isError: this.toastIsError, bordered: true })
}
.width('100%')
.height('100%')
.backgroundColor(Color.Transparent)
}
.navDestination(this.SubDestination)
.mode(this.isWide ? NavigationMode.Split : NavigationMode.Stack)
.navBarPosition(NavBarPosition.Start)
.navBarWidth(WIDE_NAV_BAR_WIDTH)
.minContentWidth(WIDE_MIN_CONTENT)
.hideTitleBar(true)
.hideToolBar(true)
.hideBackButton(true)
.width('100%')
.height('100%')
// Split 模式下 navBar 常驻,此回调不再触发;宽屏一律锁住主 Tab 横滑
.onNavBarStateChange((isVisible: boolean) => {
markSubPageOpen(this.isWide || !isVisible);
})
// 模式初始化与切换:Split 右栏不能空白,自动选中第一个插件;
// 退回 Stack 时清栈,否则会残留一个整屏覆盖的详情页。
.onNavigationModeChange((mode: NavigationMode) => {
if (mode === NavigationMode.Split) {
markSubPageOpen(true);
if (this.navStack.size() === 0 && this.plugins.length > 0) {
this.openDetail(this.plugins[0].name);
}
} else {
this.navStack.clear(false);
this.activeName = '';
markSubPageOpen(false);
}
})
}
/** 二级页面路由表:插件详情 */
@Builder
SubDestination(name: string, param: object) {
NavDestination() {
if (name === SUB_DETAIL) {
SubPageLayer({
title: this.activeName.length > 0 ? this.activeName : '插件详情',
tab: 1,
onBack: () => {
this.closeDetail();
},
showRefresh: true,
onRefresh: () => {
this.loadDetail(this.activeName);
},
}) {
PluginDetailPane({
detail: this.detail,
activeName: this.activeName,
busy: this.detailLoading,
errorText: this.detailError,
row: this.activeRow() ?? undefined,
onToggle: () => {
const r: PluginRow | null = this.activeRow();
if (r !== null) {
this.togglePlugin(r);
}
},
onRemove: () => {
const r: PluginRow | null = this.activeRow();
if (r !== null) {
this.removePlugin(r);
}
},
})
}
}
}
.hideTitleBar(true)
.backgroundColor(Color.Transparent)
}
}