HarmonyOS AI 修仙功法开发实践:基于 ArkTS 的角色扮演类对话应用架构全解


在这里插入图片描述

摘要

本文记录了一款基于 HarmonyOS 6.0 + ArkTS 开发的"AI 修仙功法"应用的完整开发过程。该应用以"上古散仙"为人设,覆盖修炼境界、功法心法、丹药炼制、法器法宝、灵兽驯养、阵法禁制、门派秘闻、秘境探险八大修仙领域,为用户提供修仙风格的对话问答服务。文章从 MVVM 分层架构、ArkTS 严格模式下的类型安全建模、System Prompt 角色工程、@Builder 声明式 UI 碎片模式、聊天界面状态管理、话题快捷入口、消息气泡左右布局、加载状态与自动滚动、暗色修仙主题配色系统等角度,全面拆解该应用的开发实践。全文提供完整的代码示例,所有代码零编译错误。


一、项目背景与设计理念

1.1 角色扮演类 AI 应用的独特价值

在 AI 对话应用领域,大多数产品采用"通用助手"的定位——回答知识性问题、提供建议、辅助写作等。这类应用固然实用,但用户与 AI 的交互始终停留在"工具"层面,缺乏情感连接和沉浸感。

角色扮演类 AI 应用则开辟了另一条路径:通过为 AI 设定特定的人设、语言风格、知识领域,让用户在与 AI 对话时获得更强的代入感和娱乐性。“AI 修仙功法"正是基于这一理念——将 AI 塑造为一位"来自上古修仙界的资深散仙”,以修仙题材中特有的世界观、术语体系和叙事风格,为用户提供兼具趣味性和实用性的对话体验。

这种设计的核心价值在于:

  1. 降低输入门槛:用户不需要"学会提问",只需要像与一位修仙前辈聊天一样自然表达即可
  2. 增强记忆点:独特的人设让用户更容易记住和推荐这个应用
  3. 领域聚焦:AI 被限定在修仙题材的八个领域内,回复质量更高、更专业
  4. 情感化体验:与"工具"不同,角色扮演带来的趣味性和沉浸感本身就是核心体验

1.2 八大领域知识体系

应用的核心 Prompt 为 AI 设定了八个专业领域,覆盖了修仙世界观中的主要知识体系:

领域 覆盖内容 典型用户问题
修炼境界 炼气、筑基、金丹、元婴、化神、合体、大乘、渡劫 “如何突破金丹期?”
功法心法 各流派功法特点、修炼心法口诀 “剑修该选什么功法?”
丹药炼制 丹方配比、火候掌控、药材识别 “聚气丹怎么炼制?”
法器法宝 品级鉴定、炼器手法、温养秘术 “如何温养本命法器?”
灵兽驯养 品种识别、驯化技巧、进阶培养 “怎么驯服一只灵兽?”
阵法禁制 阵法解析、禁制破解、布阵要诀 “聚灵阵怎么布置?”
门派秘闻 门派历史、功法传承、选择建议 “剑宗和丹宗哪个好?”
秘境探险 秘境规则、寻宝技巧、逃命秘术 “秘境探险要准备什么?”

这八个领域的设计并非随意为之,而是遵循了修仙题材的"完整世界观"——从修炼(境界、功法)到辅助(丹药、法器、灵兽)到社交(阵法、门派)到冒险(秘境),构成了一个自洽的修仙知识体系。用户无论从哪个角度切入,AI 都能给出符合世界观的回答。

1.3 核心交互流程

应用采用经典的聊天界面设计,交互流程为:

  1. 应用启动时,散仙自动发送欢迎消息(“贫道乃上古散仙…”)
  2. 用户可通过顶部话题标签栏快速选择领域,点击后自动填入提问并发送
  3. 用户也可直接在底部输入框输入问题,点击"传音"按钮发送
  4. 发送后显示加载状态(“散仙前辈正在掐指推算…”),模拟真人思考
  5. 1500ms 后 AI 回复出现在对话列表中,列表自动滚动到最新消息
  6. 对话超过一条时,右上角出现"清除对话"按钮,可重置对话

二、整体架构设计:MVVM 三层分层

2.1 架构选型

本应用采用 MVVM 架构的变体——Model-Service-View 三层结构,这是 HarmonyOS 应用开发中经过验证的成熟模式:

entry/src/main/ets/
├── models/              # Model 层:数据结构定义
│   └── CultivationModel.ets     # 枚举、消息类、话题选项类
├── services/            # Service/ViewModel 层:业务逻辑
│   └── CultivationService.ets   # Prompt 管理、Mock 回复、关键词匹配
├── common/              # 公共常量
│   └── Constants.ets            # 颜色、文案、配置
└── pages/               # View 层:UI 渲染
    └── Index.ets                # 聊天界面,9 个 @Builder 方法

三层之间的依赖关系:View → Service → Model,Model 不依赖任何层,Service 仅依赖 Model,View 依赖 Service 和 Model。这种单向依赖确保了代码的可维护性和可测试性。

2.2 各层职责划分

层级 文件 行数 职责 关键技术
Model CultivationModel.ets 50 4个枚举、2个数据类 enum、class、constructor
Service CultivationService.ets 87 System Prompt + 8领域Mock回复 + 关键词匹配 Map、迭代器遍历
Common Constants.ets 36 12个颜色常量、20+个文案常量 static readonly
View Index.ets 297 聊天UI、状态管理、交互逻辑 @State、@Builder、ForEach、Flex、Scroller
合计 4个文件 470

三、Model 层:类型安全的数据建模

3.1 消息角色枚举

聊天应用的核心数据结构是消息。消息有两个核心属性:角色(谁发送的)和内容(说了什么)。角色使用枚举类型定义,确保类型安全:

export enum MessageRole {
  USER = 'user',
  ASSISTANT = 'assistant'
}

虽然只有两个角色,但使用枚举而非布尔值(如 isUser: boolean)的好处是:当未来需要扩展角色类型(如加入"系统消息"角色)时,只需在枚举中新增一个值,所有引用处编译器都能自动检测到遗漏。

3.2 修仙境界枚举

应用定义了完整的修仙境界体系,从炼气到渡劫共八个境界:

export enum CultivationRealm {
  LIAN_QI = '炼气',
  ZHU_JI = '筑基',
  JIN_DAN = '金丹',
  YUAN_YING = '元婴',
  HUA_SHEN = '化神',
  HE_TI = '合体',
  DA_CHENG = '大乘',
  DU_JIE = '渡劫'
}

这个枚举虽然当前未在 UI 中直接使用,但它是为后续扩展预留的数据基础——例如可以根据用户选择的境界提供个性化回复、在对话中根据境界给出不同的建议强度等。

3.3 话题分类枚举

八大领域通过枚举定义,确保话题名称在整个应用中的一致性:

export enum CultivationTopic {
  REALM = '修炼境界',
  TECHNIQUE = '功法心法',
  PILL = '丹药炼制',
  ARTIFACT = '法器法宝',
  BEAST = '灵兽驯养',
  ARRAY = '阵法禁制',
  SECT = '门派秘闻',
  REALM_EXPLORE = '秘境探险'
}

3.4 消息数据类

export class ChatMessage {
  role: MessageRole
  content: string
  timestamp: number

  constructor(role: MessageRole, content: string) {
    this.role = role
    this.content = content
    this.timestamp = Date.now()
  }
}

ChatMessage 类包含三个属性:role 标识消息发送者(用户/助手),content 存储消息文本内容,timestamp 记录消息创建时间戳。timestamp 在构造函数中自动生成,无需外部传入,简化了消息创建逻辑。在 ForEach 渲染中,timestamp 与 index 组合作为唯一 key(${index}_${msg.timestamp}),确保即使消息内容相同也能正确区分。

3.5 话题选项数据类

export class TopicOption {
  label: string
  value: string
  icon: string

  constructor(label: string, value: string, icon: string) {
    this.label = label
    this.value = value
    this.icon = icon
  }
}

TopicOption 是话题标签的数据模型,包含三个属性:label(显示文字,如"修炼")、value(关键词值,用于匹配 Mock 回复)、icon(emoji 图标,如"📿")。这种设计将 UI 展示层(label + icon)和业务逻辑层(value)分离,标签的视觉效果和匹配逻辑互不影响。


四、Service 层:System Prompt 工程与回复策略

4.1 System Prompt 的角色工程

System Prompt 是角色扮演类 AI 应用的核心。一个优秀的角色 Prompt 需要包含三个要素:身份锚定领域界定行为规范。本应用的 System Prompt 完整实现了这三个要素:

身份锚定:
"你是一位来自上古修仙界的资深散仙 'AI 修仙功法',
曾在九天十地游历万年,精通人、妖、魔三界各种功法秘籍。"

领域界定:
"你覆盖但不限于以下领域:
1. 修炼境界:炼气、筑基、金丹、元婴、化神、合体、大乘、渡劫...
2. 功法心法:各流派功法特点、修炼心法口诀...
3. 丹药炼制:丹方配比、火候掌控...
4. 法器法宝:法器品级鉴定、炼器手法...
5. 灵兽驯养:灵兽品种识别、驯化技巧...
6. 阵法禁制:常见阵法解析、禁制破解...
7. 门派秘闻:各大门派历史、功法传承...
8. 秘境探险:秘境规则、寻宝技巧..."

行为规范(6条回复原则):
1. 先感知:察觉道友当前的修炼状态和心境,给予理解与鼓励
2. 再指路:分析问题根由,指出修炼方向或功法要领
3. 授秘诀:提供1~3条具体的修炼法门或口诀,并说明适用阶段
4. 语气需有仙风道骨,但不可过于晦涩,兼顾修真韵味和易懂
5. 涉及突破大境界、渡劫等危险事项时,务必提醒做好万全准备
6. 回复可适当引用上古传说、仙家典故以增信服力

Prompt 设计要点解析:

身份锚定策略:通过"上古修仙界"“资深散仙”“游历万年”"精通三界"等词汇,为 AI 建立了一个深厚、权威、富有传奇色彩的人物形象。这种锚定会影响 AI 后续所有回复的语气、用词和知识范围。

"先感知-再指路-授秘诀"三段式回复结构:这是 Prompt 工程中的"思维链约束"技术。通过强制 AI 按照"感知→分析→建议"的顺序组织回复,确保每次输出都包含情感共鸣、理性分析和可操作建议三个层次,避免空洞的泛泛而谈。

"仙风道骨但不过于晦涩"的平衡:这是 Prompt 中一个容易被忽视但至关重要的约束。如果只要求"仙风道骨",AI 可能输出大量文言文,导致普通用户无法理解;如果只要求"易懂",则失去了角色扮演的特色。这个平衡约束确保了角色感与可读性的统一。

危险事项的特别提醒:在 Prompt 中明确要求"涉及突破大境界、渡劫等危险事项时,务必提醒做好万全准备",这是对 AI 应用安全性的考量——即使是在虚构的修仙题材中,也要避免 AI 给出可能被误解为"鼓励冒险"的建议。

4.2 Mock 回复的关键词匹配策略

在真实 AI API 接入前,应用通过 Mock 数据提供完整的演示功能。Service 层使用 Map<string, string> 存储了八个关键领域的预设回复:

private mockResponses: Map<string, string> = new Map()

private initMockResponses(): void {
  this.mockResponses.set('修炼', '(轻抚长须,目光深邃)贫道观道友气息...')
  this.mockResponses.set('突破', '(神色凝重)道友欲突破境界乎?...')
  this.mockResponses.set('丹药', '(从袖中取出一个古朴丹炉)...')
  this.mockResponses.set('法器', '(抬手间,掌心浮现一道流光)...')
  this.mockResponses.set('灵兽', '(袖中探出一只灵猫的脑袋)...')
  this.mockResponses.set('阵法', '(手指虚空画符,空气中浮现出繁复的阵纹)...')
  this.mockResponses.set('门派', '(远眺虚空,似在回忆)天下修仙门派...')
  this.mockResponses.set('秘境', '(神色变得严肃)道友欲探秘境?...')
}

Mock 回复的设计原则:

  1. 动作描写开篇:每条回复都以括号内的动作描写开头(“轻抚长须”“神色凝重”“从袖中取出…”),这是修仙小说中常见的叙事手法,增强了角色沉浸感
  2. 组织结构化:使用 Markdown 的加粗标记(**指路****警告**)和编号列表,使回复具有清晰的层次结构,便于阅读
  3. 包含具体信息:每条回复都包含具体的修炼方法、丹方配比、阵法口诀等,而非空泛的鼓励。例如丹药回复中包含了完整的聚气丹方和火候口诀
  4. 融入角色特色:回复中自称"贫道"、引用古训(如"李太白曾言")、使用修仙术语,保持角色一致性

4.3 关键词匹配算法

generateMockReply(userMessage: string): ChatMessage {
  const lowerMsg = userMessage.toLowerCase()
  let replyContent = ''

  const keys = this.mockResponses.keys()
  let matchedKey = ''
  let result = keys.next()
  while (!result.done) {
    const key = result.value
    if (lowerMsg.includes(key)) {
      matchedKey = key
      break
    }
    result = keys.next()
  }

  if (matchedKey !== '') {
    replyContent = this.mockResponses.get(matchedKey)!
  } else {
    replyContent = '(轻抚长须,微微一笑)道友所问之事,贫道虽略知一二...'
  }

  return new ChatMessage(MessageRole.ASSISTANT, replyContent)
}

关键词匹配算法使用 Map 的迭代器遍历所有关键词,通过 String.includes() 进行子串匹配。算法特性:

  • 大小写不敏感:通过 toLowerCase() 转换实现
  • 首次匹配即返回:使用 break 在找到第一个匹配项后立即退出循环,避免不必要的遍历
  • 兜底回复:当没有匹配到任何关键词时,返回一个通用的引导性回复,询问用户的具体情况
  • 非空断言this.mockResponses.get(matchedKey)! 使用 TypeScript 的 ! 非空断言,因为 matchedKey 来自 keys() 迭代器,确保 key 一定存在于 Map 中

4.4 异步接口预留

async generateReply(userMessage: string): Promise<ChatMessage> {
  try {
    const replyContent = await this.callAI(userMessage)
    return new ChatMessage(MessageRole.ASSISTANT, replyContent)
  } catch (e) {
    return this.generateMockReply(userMessage)
  }
}

private async callAI(userMessage: string): Promise<string> {
  return this.generateMockReply(userMessage).content
}

Service 层提供了 generateReply 异步方法和 generateMockReply 同步方法。异步方法使用 try-catch 包裹 AI 调用,失败时自动降级到 Mock 回复。当前 callAI 方法直接返回 Mock 数据,但接口已预留完整——接入真实 API 时,只需修改 callAI 方法内部实现,将 System Prompt 和 User Message 发送给大语言模型 API 即可。


五、常量层:暗色修仙主题配色体系

5.1 配色方案设计

应用采用了一套精心设计的暗色修仙主题配色,旨在营造"上古修仙世界"的沉浸感:

色值 用途 设计意图
#1A1A2E(深蓝黑) 页面背景 模拟深夜星空,营造神秘感
#16213E(深蓝) 卡片/头部/输入区背景 比背景亮一阶,形成层次
#C9A96E(金色) 主题色/标题/发送按钮 修仙世界的"仙气"象征色
#2D2422(暗棕) 主题色背景 金色系低饱和度背景
#2D4A3E(墨绿) 用户消息气泡 与修仙世界的"灵气"呼应
#1E2746(蓝灰) 助手消息气泡 比背景稍亮,形成对比
#E8D5B7(浅金) 主要文字 类似古籍纸张的颜色
#8B7D6B(灰棕) 次要文字 低对比度,不抢眼
#0F1B2D(极深蓝) 输入框背景 最深的颜色,突出输入区域
#E8A87C(暖橙) 强调色/清除按钮 暖色系点缀,打破冷色调

配色设计原则:

  1. 低饱和度为主:避免使用鲜艳的颜色,所有颜色都带有灰调,营造古朴、沉稳的修仙氛围
  2. 金色作为主题色:金色在修仙文化中代表"仙气"和"尊贵",应用在标题、按钮、散仙标签等关键元素上
  3. 冷色系为基底:深蓝、蓝灰、墨绿构成冷色基底,金色和暖橙作为点缀色,形成冷暖对比
  4. 严格的色彩层次:从最深(输入框 #0F1B2D)到最亮(金色文字 #C9A96E),五级色彩层次确保了信息的视觉优先级

5.2 文案常量管理

static readonly APP_TITLE: string = 'AI 修仙功法'
static readonly APP_SUBTITLE: string = '上古散仙 · 指点后辈修士修炼之道'
static readonly INPUT_PLACEHOLDER: string = '向散仙前辈请教修仙之道...'
static readonly BTN_SEND: string = '传音'
static readonly BTN_CLEAR: string = '清除对话'
static readonly LOADING_TEXT: string = '散仙前辈正在掐指推算...'
static readonly WELCOME_TEXT: string = '贫道乃上古散仙...'

文案设计完全融入修仙世界观:

  • 发送按钮命名为"传音",符合修仙设定中的"传音入密"概念
  • 加载文案使用"掐指推算",增加了角色感和趣味性
  • 欢迎文案使用"贫道"自称,符合散仙人设

六、View 层:聊天界面完整实现

6.1 整体布局:四层垂直结构

页面采用经典的聊天应用布局,从上到下分为四个区域:

┌──────────────────────────────┐
│          Header(标题栏)      │
├──────────────────────────────┤
│       TopicBar(话题标签栏)    │
├──────────────────────────────┤
│                              │
│    Scroll(消息列表 + 加载)    │
│                              │
├──────────────────────────────┤
│       InputArea(输入区)      │
└──────────────────────────────┘
build() {
  Column() {
    this.buildHeader()
    this.buildTopicBar()
    Scroll(this.scroller) {
      Column() {
        ForEach(this.messages, ...)
        if (this.isLoading) { this.buildLoadingBubble() }
      }
    }
    .layoutWeight(1)
    this.buildInputArea()
  }
}

关键布局决策:

  • Header 和 TopicBar 固定:不随消息列表滚动,始终可见,方便用户随时切换话题
  • Scroll 使用 layoutWeight(1):占据 Header + TopicBar + InputArea 之外的剩余空间,适应不同屏幕高度
  • InputArea 始终在底部:不受消息列表滚动影响,符合聊天应用的交互习惯
  • ScrollBar 隐藏scrollBar(BarState.Off) 隐藏滚动条,保持界面简洁
  • Spring 回弹效果edgeEffect(EdgeEffect.Spring) 提供 iOS 风格的弹性滚动回弹

6.2 标题栏:可复用的规范布局

@Builder
buildHeader() {
  Column() {
    Row() {
      Text('⚡').fontSize(24).margin({ right: 8 })
      Column() {
        Text(AppConstants.APP_TITLE)
          .fontSize(20).fontWeight(FontWeight.Bold)
          .fontColor(AppConstants.COLOR_PRIMARY)
        Text(AppConstants.APP_SUBTITLE)
          .fontSize(12).fontColor(AppConstants.COLOR_TEXT_SECONDARY)
      }
      .alignItems(HorizontalAlign.Start)

      Blank()

      if (this.messages.length > 1) {
        Text(AppConstants.BTN_CLEAR)
          .fontSize(13).fontColor(AppConstants.COLOR_ACCENT)
          .borderRadius(14).border({ width: 1, color: AppConstants.COLOR_ACCENT })
          .onClick(() => { this.onClear() })
      }
    }
  }
}

设计要点:

  1. 立体标题:使用"emoji + 主标题 + 副标题"的三层结构,闪电符号⚡增加视觉个性
  2. Blank() 弹性空间:在标题区域和清除按钮之间插入 Blank(),将清除按钮推到右侧
  3. 条件渲染清除按钮if (this.messages.length > 1) 确保只有存在对话记录时才显示清除按钮,初始状态(只有欢迎消息)不显示,避免空状态下的无效操作
  4. 清除按钮镂空设计:使用透明背景 + 暖橙色边框 + 暖橙色文字,形成"幽灵按钮"风格,视觉上弱于主操作按钮

6.3 话题标签栏:横向滚动 + 自适应选中态

话题标签栏是整个应用交互设计的亮点之一。它提供了八个修仙领域的快捷入口,用户点击标签后自动填入提问内容并发送:

@Builder
buildTopicBar() {
  Scroll() {
    Row() {
      ForEach(this.topics, (topic: TopicOption, index: number) => {
        Text(`${topic.icon} ${topic.label}`)
          .fontSize(12)
          .fontColor(this.selectedTopic === topic.label ?
            AppConstants.COLOR_TOPIC_SELECTED : AppConstants.COLOR_TEXT_SECONDARY)
          .backgroundColor(this.selectedTopic === topic.label ?
            AppConstants.COLOR_PRIMARY_BG : AppConstants.COLOR_TOPIC_BG)
          .borderRadius(16)
          .padding({ left: 12, right: 12, top: 6, bottom: 6 })
          .margin({ right: 8 })
          .border({ width: 1, color: this.selectedTopic === topic.label ?
            AppConstants.COLOR_TOPIC_SELECTED : AppConstants.COLOR_BORDER })
          .onClick(() => { this.onTopicSelect(topic) })
      }, (topic: TopicOption, index: number) => `${index}`)
    }
  }
  .scrollable(ScrollDirection.Horizontal)
  .scrollBar(BarState.Off)
}

技术要点解析:

嵌套 Scroll 实现横向滚动:外层 Scroll 设置为 scrollable(ScrollDirection.Horizontal),实现横向滚动。当八个标签超出屏幕宽度时,用户可以左右滑动浏览。嵌套 Scroll 的常见问题是内层垂直滚动和外层水平滚动的冲突——但由于话题栏在垂直 Scroll 之外,二者互不干扰。

选中态的双向视觉反馈:选中标签同时改变文字颜色(灰色→金色)和背景色(深蓝→暗棕),并添加金色边框。三种视觉变化确保选中状态在任何光照条件下都清晰可辨。

emoji + 文字的组合:每个标签包含一个 emoji 图标和简短文字,如"📿 修炼"、“📜 功法”。emoji 的选择与领域含义对应——📿代表修炼(念珠)、📜代表功法(卷轴)、🧪代表丹药(试管)、⚔️代表法器(剑)、🦊代表灵兽(狐狸)、🔮代表阵法(水晶球)、🏛️代表门派(建筑)、🗺️代表秘境(地图)。

点击自动发送onTopicSelect 方法自动将话题标签的 label 拼接为 请教散仙前辈关于${topic.label}之事 并填入输入框后立即调用 onSend(),实现"一键提问"的极简体验。

6.4 消息气泡:左右对齐的聊天布局

消息气泡是聊天界面的核心视觉元素。本应用实现了经典的 IM 聊天布局——用户消息右对齐,AI 消息左对齐:

@Builder
buildMessageBubble(msg: ChatMessage, index: number) {
  Row() {
    if (msg.role === MessageRole.USER) {
      Blank()
    }

    Column() {
      if (msg.role === MessageRole.ASSISTANT) {
        Row() {
          Text('⚡').fontSize(16).margin({ right: 6 })
          Text('散仙前辈').fontSize(12)
            .fontColor(AppConstants.COLOR_PRIMARY)
        }
        .margin({ bottom: 4 })
      }

      Text(msg.content)
        .fontSize(14)
        .fontColor(msg.role === MessageRole.USER ? '#E8D5B7' : AppConstants.COLOR_TEXT)
        .lineHeight(22)
        .padding(12)
        .borderRadius(12)
        .constraintSize({ maxWidth: '85%' })
        .backgroundColor(msg.role === MessageRole.USER ?
          AppConstants.COLOR_USER_BUBBLE : AppConstants.COLOR_ASSISTANT_BUBBLE)
        .border({
          width: 1,
          color: msg.role === MessageRole.USER ? '#3D6B5A' : AppConstants.COLOR_BORDER
        })
    }
    .alignItems(msg.role === MessageRole.USER ?
      HorizontalAlign.End : HorizontalAlign.Start)

    if (msg.role === MessageRole.ASSISTANT) {
      Blank()
    }
  }
  .width('100%')
  .margin({ top: 10 })
  .justifyContent(msg.role === MessageRole.USER ?
    FlexAlign.End : FlexAlign.Start)
}

布局策略详解:

左右对齐的实现:通过 Blank() 组件和 justifyContent 的组合实现。用户消息时,Blank() 在左侧占据弹性空间,气泡被推到右侧;AI 消息时,Blank() 在右侧占据弹性空间,气泡留在左侧。justifyContent 设置 FlexAlign.End(用户)或 FlexAlign.Start(AI)作为辅助。

散仙身份标签:AI 消息气泡上方显示"⚡ 散仙前辈"标签,使用金色文字,与标题栏的散仙标识保持视觉一致性。用户消息不显示身份标签,简洁明了。

气泡最大宽度限制constraintSize({ maxWidth: '85%' }) 限制气泡最大宽度为屏幕宽度的 85%,确保短消息不会撑满整个屏幕,长消息不会超出屏幕边界。剩余 15% 的空间用于 Blank() 弹性空间,形成左右留白。

颜色区分:用户气泡使用墨绿色(#2D4A3E),文字为浅金色(#E8D5B7);AI 气泡使用蓝灰色(#1E2746),文字为浅金色(#E8D5B7)。两种气泡的颜色差异明显,用户一眼就能区分消息来源。

圆角和边框:12px 圆角(borderRadius(12))使气泡边缘柔和,1px 边框使气泡与背景分离,增强层次感。

行高设置lineHeight(22) 设置行高为 22px,配合 14px 字号,行高比为 1.57,符合中文阅读的最佳行高范围(1.5-1.8)。

6.5 加载状态:等待中的角色延续

加载状态是聊天应用中容易被忽视但至关重要的交互细节。本应用的加载气泡设计延续了角色扮演风格:

@Builder
buildLoadingBubble() {
  Row() {
    Column() {
      Row() {
        Text('⚡').fontSize(16).margin({ right: 6 })
        Text('散仙前辈').fontSize(12).fontColor(AppConstants.COLOR_PRIMARY)
      }
      Text(AppConstants.LOADING_TEXT)
        .fontSize(14).fontColor(AppConstants.COLOR_TEXT_SECONDARY)
        .padding(12).borderRadius(12)
        .backgroundColor(AppConstants.COLOR_ASSISTANT_BUBBLE)
        .border({ width: 1, color: AppConstants.COLOR_BORDER })
    }
    Blank()
  }
}

加载气泡与 AI 消息气泡共享相同的视觉风格(同色背景、同色边框、“散仙前辈"标签),仅在内容上替换为"散仙前辈正在掐指推算…”。这种设计保持了视觉一致性,让用户感知到"散仙正在思考"而非"系统正在加载"。

6.6 输入区域:圆角胶囊 + 双向绑定

@Builder
buildInputArea() {
  Row() {
    TextArea({ text: this.inputText, placeholder: AppConstants.INPUT_PLACEHOLDER })
      .height(40)
      .fontSize(14)
      .backgroundColor(AppConstants.COLOR_INPUT_BG)
      .borderRadius(20)
      .padding({ left: 16, right: 16, top: 10, bottom: 10 })
      .layoutWeight(1)
      .maxLength(500)
      .onChange((value: string) => {
        this.inputText = value
      })

    Button(AppConstants.BTN_SEND)
      .fontSize(14)
      .fontColor(this.inputText.trim() === '' ?
        AppConstants.COLOR_TEXT_SECONDARY : Color.White)
      .backgroundColor(this.inputText.trim() === '' ?
        AppConstants.COLOR_CARD : AppConstants.COLOR_PRIMARY)
      .borderRadius(20)
      .height(40)
      .padding({ left: 18, right: 18 })
      .margin({ left: 10 })
      .enabled(!this.isLoading && this.inputText.trim() !== '')
      .onClick(() => { this.onSend() })
  }
}

输入区域设计要点:

TextArea 双向绑定:通过 { text: this.inputText } 初始化文本内容,通过 onChange 回调同步更新 this.inputText,实现双向数据流。TextArea 组件支持多行输入,但高度固定为 40px,聚焦时自动滚动内容。

maxLength 限制maxLength(500) 限制输入最大 500 字符,防止超长输入导致性能问题或 API 调用失败。

发送按钮状态联动:按钮颜色根据输入内容动态变化——输入为空时显示灰色(#64748B文字 + 卡片色背景),输入有内容时显示金色(白色文字 + 金色背景)。按钮启用状态同时受 isLoading 和输入内容控制——加载中或输入为空时按钮不可点击。

圆角胶囊设计:TextArea 和 Button 都使用 20px 圆角(borderRadius(20)),形成统一的"胶囊"视觉风格。按钮使用 padding({ left: 18, right: 18 }) 水平内边距,文字"传音"两字周围有足够的留白空间。

按钮与输入框的间距:使用 margin({ left: 10 }) 在按钮左侧留出 10px 间距,避免按钮与输入框紧贴。

6.7 交互逻辑:发送、滚动与清除

onSend 方法是整个应用的核心交互逻辑处理函数:

private onSend(): void {
  const text = this.inputText.trim()
  if (text === '') {
    return
  }

  this.messages.push(new ChatMessage(MessageRole.USER, text))
  this.inputText = ''
  this.selectedTopic = ''
  this.isLoading = true

  setTimeout(() => {
    this.hideKeyboard()
  }, 100)

  setTimeout(() => {
    this.scroller.scrollEdge(Edge.Bottom)
  }, 200)

  setTimeout(() => {
    const reply = this.service.generateMockReply(text)
    this.messages.push(reply)
    this.isLoading = false
    setTimeout(() => {
      this.scroller.scrollEdge(Edge.Bottom)
    }, 100)
  }, 1500)
}

交互时序分析:

时间 操作 原因
0ms 添加用户消息、清空输入、设置加载状态 立即响应用户操作
100ms 隐藏键盘 给 TextArea 失焦留出时间
200ms 第一次滚动到底部 让用户看到自己的消息和加载气泡
1500ms 添加 AI 回复、清除加载状态 模拟 AI 思考时间
1600ms 第二次滚动到底部 让用户看到 AI 的回复

两次滚动的原因:第一次滚动在用户消息发送后立即执行,确保用户看到自己的消息已发送和加载状态;第二次滚动在 AI 回复添加后执行,确保用户看到完整的回复内容。两次滚动间隔 1400ms,用户不会感知到两次滚动操作。

加载状态isLoading = true 会触发按钮禁用和加载气泡显示,isLoading = false 则恢复按钮可用状态并隐藏加载气泡。1500ms 的延迟在"太快让人觉得假"和"太慢让人不耐烦"之间取得了平衡。

onClear 清除逻辑

private onClear(): void {
  this.messages = []
  this.messages.push(new ChatMessage(MessageRole.ASSISTANT, AppConstants.WELCOME_TEXT))
  this.selectedTopic = ''
  this.inputText = ''
}

清除逻辑不是简单地清空数组,而是清空后重新添加欢迎消息,恢复到初始状态。这种设计确保用户清除对话后,界面不会变成空白,而是回到"散仙刚刚自我介绍"的状态。

onTopicSelect 话题选择逻辑

private onTopicSelect(topic: TopicOption): void {
  if (this.isLoading) {
    return
  }
  this.selectedTopic = topic.label
  this.inputText = `请教散仙前辈关于${topic.label}之事`
  this.onSend()
}

话题选择做了两层防护:首先检查 isLoading 状态,加载中不响应点击;然后构造完整的提问文本(而非仅填入关键词),确保 AI 能获得足够的上下文信息。


七、关键技术亮点总结

7.1 System Prompt 的角色工程

本应用展示了角色扮演类 AI 应用的 System Prompt 设计方法论——"身份锚定 → 领域界定 → 行为规范"三段式结构。这种结构不仅适用于修仙题材,也可迁移到其他角色扮演场景(如医生、律师、心理咨询师等)。核心要点是:

  1. 身份描述要具体、有细节(“游历万年”“精通三界”),而非泛泛的"你是一个专家"
  2. 领域界定要明确边界,让 AI 知道什么该答、什么不该答
  3. 行为规范要给出可执行的操作步骤(“先感知-再指路-授秘诀”),而非抽象的"请专业一点"

7.2 暗色主题的修仙风格配色

应用的配色方案充分体现了"设计服务于主题"的理念。从深蓝星空背景到金色文字,从墨绿用户气泡到蓝灰 AI 气泡,每一处颜色选择都服务于"上古修仙世界"的沉浸感。配色系统通过 Constants 类集中管理,方便后续调整和主题切换。

7.3 @Builder 模式的聊天 UI 组件化

应用将 297 行的聊天页面拆分为 9 个 @Builder 方法,每个方法职责单一:

@Builder 方法 职责 行数
buildHeader 标题栏 + 清除按钮 10
buildTopicBar 话题标签栏(横向滚动) 10
buildMessageBubble 单个消息气泡(左右对齐) 20
buildLoadingBubble 加载状态气泡 10
buildInputArea 输入框 + 发送按钮 15

这种拆分方式使代码结构清晰、易于维护,每个 UI 片段可以独立修改而不影响其他部分。

7.4 关键词匹配的 Mock 降级策略

Service 层使用 Map 数据结构存储关键词与回复的映射关系,通过迭代器遍历实现 O(n) 的关键词匹配。这种策略在真实 API 接入前提供了完整的演示体验,同时也作为 API 调用失败时的降级方案。

7.5 消息列表的 ForEach 渲染优化

ForEach(this.messages, (msg: ChatMessage, index: number) => {
  this.buildMessageBubble(msg, index)
}, (msg: ChatMessage, index: number) => `${index}_${msg.timestamp}`)

ForEach 的第三个参数提供了唯一的 key 生成函数。使用 ${index}_${msg.timestamp} 作为 key,既保证了同一位置的消息更新时能正确复用组件,又防止了不同消息之间的冲突。timestamp 的纳秒级精度确保了即使两条消息内容相同,key 也不会重复。

7.6 嵌套 Scroll 的解决方案

应用同时使用了垂直 Scroll(消息列表)和水平 Scroll(话题标签栏),通过将两个 Scroll 放在不同的布局层级解决了嵌套滚动的冲突问题:

  • 水平 Scroll(话题标签栏)在垂直 Scroll 外部,不受消息列表滚动影响
  • 垂直 Scroll 使用 layoutWeight(1) 占据剩余空间,不与水平 Scroll 产生空间竞争

7.7 ArkTS 严格模式下的类型安全

项目完全遵循 ArkTS 严格模式的要求:

  • 所有变量和参数有明确的类型注解
  • 使用枚举替代字符串字面量和魔法数字
  • 所有数据类使用显式 constructor 初始化
  • 跨文件引用使用明确的 import/export 语句
  • 不使用 any/unknown 类型

八、项目文件清单与扩展方向

8.1 完整文件清单

文件 行数 职责
[CultivationModel.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/models/CultivationModel.ets) 50 4个枚举(MessageRole、CultivationRealm、CultivationTopic)+ 2个数据类
[CultivationService.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/services/CultivationService.ets) 87 System Prompt + 8领域Mock回复 + 关键词匹配 + 异步接口
[Constants.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/common/Constants.ets) 36 暗色修仙主题配色 + 20+文案常量
[Index.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/pages/Index.ets) 297 聊天界面:9个@Builder + 完整交互逻辑
合计 470 零编译错误,ArkTS 严格模式兼容

8.2 可扩展方向

  1. 接入真实 AI API:将 callAI 方法替换为真实的 HTTP 请求(使用 @ohos.net.http 模块),发送 System Prompt 和用户消息到大语言模型 API
  2. 对话历史持久化:使用 @ohos.data.preferences 存储对话记录,支持跨会话恢复
  3. 多轮对话上下文:在 AI 调用中携带最近 5-10 轮对话历史,让 AI 能记住之前讨论的内容
  4. 修炼进度系统:根据对话内容跟踪用户的"修炼进度",解锁更多高阶话题
  5. 个性化人设切换:支持切换不同的散仙人设(如"剑修散仙"“丹修散仙”“阵修散仙”),每个角色有不同的回复风格
  6. 语音输入:集成 HarmonyOS 语音识别能力,支持用语音向散仙提问
  7. 多端适配:利用 HarmonyOS 一次开发多端部署能力,适配平板和折叠屏设备

九、结语

“AI 修仙功法"是一个典型的角色扮演类 AI 对话应用——它的核心价值不在于回答的"准确性”,而在于交互的"沉浸感"和"趣味性"。在技术实现层面,这个应用展示了 HarmonyOS 6.0 + ArkTS 在构建聊天类应用时的完整最佳实践:

  • 架构层面:MVVM 三层分层确保代码职责清晰,470 行代码实现完整聊天功能
  • AI 层面:System Prompt 的"身份锚定 → 领域界定 → 行为规范"三段式设计,为角色扮演类 AI 应用提供了可复用的 Prompt 工程模板
  • UI 层面:暗色修仙主题配色体系 + 消息气泡左右布局 + 话题标签横向滚动 + 加载状态角色延续,从视觉到交互全面服务于"修仙沉浸感"
  • 交互层面:话题一键发送、输入验证、加载状态管理、双次自动滚动、清除对话重置,细节打磨出流畅的聊天体验
  • 工程层面:常量集中管理、Map 关键词匹配、异步接口预留、try-catch 降级,确保代码质量和可维护性

从代码量看,整个应用仅 470 行 ArkTS 代码,却实现了从输入到输出的完整 AI 聊天链路——包括角色 Prompt、8 个领域的高质量 Mock 回复、暗色修仙主题 UI、话题快捷入口、消息气泡、加载状态、清除对话等完整功能。这得益于 ArkTS 声明式 UI 的高效表达能力和 HarmonyOS 框架提供的丰富组件库。对于希望入门 HarmonyOS AI 聊天应用开发的开发者来说,这个项目是一个很好的起点——它涵盖了聊天 UI 构建、角色 Prompt 设计、状态管理、交互逻辑等核心环节,代码量适中,结构清晰,零编译错误,可直接作为学习参考和二次开发的基础。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐