HarmonyOS 6(API 23)实战:基于 Face AR 情绪识别与 Body AR 手势控制的“情绪感知智能工作台“

文章目录

    • 每日一句正能量
    • 前言
    • 一、从被动交互到主动感知:AI助手的交互革命
      • [1.1 传统AI助手的痛点](#1.1 传统AI助手的痛点)
      • [1.2 "情绪感知"架构设计](#1.2 "情绪感知"架构设计)
    • 二、环境配置与AR引擎初始化
      • [2.1 模块依赖配置](#2.1 模块依赖配置)
      • [2.2 AI助手Ability配置(EmotionAIAbility.ets)](#2.2 AI助手Ability配置(EmotionAIAbility.ets))
    • 三、核心组件实战
      • [3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets)](#3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets))
      • [3.2 手势控制AI助手面板(GestureAIPanel.ets)](#3.2 手势控制AI助手面板(GestureAIPanel.ets))
      • [3.3 主工作台页面:情绪光效与内容整合](#3.3 主工作台页面:情绪光效与内容整合)
    • 四、关键技术总结
      • [4.1 情绪-UI自适应映射体系](#4.1 情绪-UI自适应映射体系)
      • [4.2 手势-AI指令映射](#4.2 手势-AI指令映射)
      • [4.3 性能与隐私优化](#4.3 性能与隐私优化)
    • 五、调试与部署建议
      • [5.1 真机调试要点](#5.1 真机调试要点)
      • [5.2 部署适配](#5.2 部署适配)
    • 六、总结与展望

每日一句正能量

别去计较你现在付出了什么,别去怀疑你现在有没有收获,当你全身心地去投入你喜欢的事业时,一切美好的结果,都会在你未来的时光给你最优美的答案!早安!加油!

前言

摘要:HarmonyOS 6(API 23)的 Face AR 与 Body AR 能力不仅适用于娱乐场景,更为生产力工具带来了全新的交互维度。本文将实战开发一款面向 HarmonyOS PC 的"情绪感知智能工作台",通过 Face AR 实时识别用户情绪状态并自适应调整界面光效,通过 Body AR 手势实现无接触式AI助手操控,结合悬浮导航与沉浸光感打造"懂你的"智能办公体验。


一、从被动交互到主动感知:AI助手的交互革命

1.1 传统AI助手的痛点

传统AI助手(如语音助手、聊天机器人)存在明显的交互断裂:

痛点 表现 用户成本
唤醒延迟 需要明确的唤醒词或点击 打断工作流
意图误解 无法感知用户当前情绪状态 反复修正指令
操作繁琐 复杂功能需多层菜单导航 记忆负担重
反馈单调 统一的视觉/听觉反馈 缺乏情感连接

HarmonyOS 6(API 23)的 Face ARBody AR 能力为AI助手带来了"感官升级"------通过摄像头实时捕捉用户面部表情和肢体动作,AI助手能够"看见"用户,从而做出更智能、更贴心的响应 。

1.2 "情绪感知"架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    用户感知层(AR Engine)                     │
│  ┌─────────────────────┐    ┌─────────────────────────────┐ │
│  │    Face AR 情绪识别  │    │     Body AR 手势控制         │ │
│  │  · 68点人脸Mesh      │    │  · 20+骨骼关键点            │ │
│  │  · 7种基础情绪分类   │    │  · 6种手势状态识别           │ │
│  │  · 疲劳度检测        │    │  · 姿态角度计算             │ │
│  │  · 注意力评估        │    │  · 空间位置追踪             │ │
│  └──────────┬──────────┘    └──────────────┬──────────────┘ │
└─────────────┼────────────────────────────────┼────────────────┘
              │                                │
              ▼                                ▼
┌─────────────────────────────────────────────────────────────┐
│                    AI决策引擎(端侧大模型)                     │
│  · 情绪-功能映射:焦虑→降噪模式,专注→深度工作,疲劳→休息提醒   │
│  · 手势-指令映射:单手抬起→唤醒AI,双手张开→全屏展示,捏合→缩放 │
│  · 上下文融合:结合时间、任务进度、历史习惯综合决策             │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│                    自适应UI层(ArkUI)                         │
│  ┌─────────────────────┐    ┌─────────────────────────────┐ │
│  │   沉浸光效自适应     │    │     悬浮导航动态调整         │ │
│  │  · 情绪色映射        │    │  · 根据手势位置智能避让       │ │
│  │  · 光强随疲劳度衰减  │    │  · 根据注意力集中度简化/丰富  │ │
│  │  · 呼吸灯提醒休息    │    │  · 预测性内容预加载          │ │
│  └─────────────────────┘    └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

二、环境配置与AR引擎初始化

2.1 模块依赖配置

json 复制代码
// oh-package.json5
{
  "dependencies": {
    "@hms.core.ar.arengine": "^6.1.0",
    "@kit.ArkUI": "^6.1.0",
    "@kit.AbilityKit": "^6.1.0",
    "@kit.SensorServiceKit": "^6.1.0",
    "@kit.AIDrawingKit": "^6.1.0"
  }
}

2.2 AI助手Ability配置(EmotionAIAbility.ets)

代码亮点:PC端AI助手需要常驻后台监听AR数据,同时支持多窗口浮动交互。配置为自由窗口模式,允许用户在工作时随时呼出AI助手悬浮球。

typescript 复制代码
// entry/src/main/ets/ability/EmotionAIAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { arEngine, ARConfig, ARFeatureType, ARMultiFaceMode } from '@hms.core.ar.arengine';

/**
 * 情绪状态枚举
 */
export enum EmotionState {
  NEUTRAL = 'neutral',      // 平静
  HAPPY = 'happy',          // 愉悦
  SAD = 'sad',              // 低落
  ANGRY = 'angry',          // 愤怒
  SURPRISED = 'surprised',  // 惊讶
  FEAR = 'fear',            // 焦虑
  DISGUST = 'disgust',      // 厌恶
  TIRED = 'tired'           // 疲劳(自定义状态)
}

/**
 * 注意力级别
 */
export enum AttentionLevel {
  FOCUSED = 'focused',      // 高度专注
  NORMAL = 'normal',        // 正常
  DISTRACTED = 'distracted', // 分心
  DROWSY = 'drowsy'         // 困倦
}

export default class EmotionAIAbility extends UIAbility {
  private arSession: arEngine.ARSession | null = null;
  private emotionHistory: Array<{ emotion: EmotionState; timestamp: number }> = [];
  private readonly HISTORY_WINDOW = 30000; // 30秒情绪窗口

  onWindowStageCreate(windowStage: window.WindowStage): void {
    this.setupAIWindow(windowStage);
  }

  /**
   * 配置AI助手窗口:浮动模式,常驻后台
   */
  private async setupAIWindow(windowStage: window.WindowStage): Promise<void> {
    const mainWindow = windowStage.getMainWindowSync();
    
    // PC端自由窗口:可拖动、可最小化为悬浮球
    await mainWindow.setWindowSizeType(window.WindowSizeType.FREE);
    await mainWindow.setWindowMode(window.WindowMode.FLOATING);
    await mainWindow.setWindowBackgroundColor('#00000000');
    await mainWindow.setWindowShadowEnabled(true);
    await mainWindow.setWindowCornerRadius(20);
    
    // 初始位置:右下角
    await mainWindow.moveWindowTo({ x: 1400, y: 800 });
    await mainWindow.resize(400, 600);

    windowStage.loadContent('pages/EmotionAIPage', (err) => {
      if (err.code) {
        console.error('Failed to load AI page:', JSON.stringify(err));
        return;
      }
      this.initializeAREngine();
    });

    AppStorage.setOrCreate('ai_window', mainWindow);
  }

  /**
   * 初始化AR引擎:情绪识别+手势控制双模态
   */
  private async initializeAREngine(): Promise<void> {
    try {
      const context = getContext(this);
      const isReady = await arEngine.isAREngineReady(context);
      if (!isReady) return;

      this.arSession = new arEngine.ARSession(context);
      
      const config = new ARConfig();
      // 启用Face AR(情绪识别)+ Body AR(手势控制)
      config.featureType = ARFeatureType.ARENGINE_FEATURE_TYPE_FACE | 
                           ARFeatureType.ARENGINE_FEATURE_TYPE_BODY;
      config.multiFaceMode = ARMultiFaceMode.MULTIFACE_ENABLE;
      config.maxDetectedBodyNum = 1;
      config.cameraLensFacing = arEngine.ARCameraLensFacing.FRONT;

      this.arSession.configure(config);
      await this.arSession.start();

      this.startEmotionAnalysisLoop();
      AppStorage.setOrCreate('ar_session', this.arSession);
      
    } catch (error) {
      console.error('AR Engine init failed:', error);
    }
  }

  /**
   * 情绪分析主循环:实时处理Face AR数据
   */
  private startEmotionAnalysisLoop(): void {
    const loop = () => {
      if (!this.arSession) return;

      const frame = this.arSession.acquireFrame();
      if (!frame) {
        requestAnimationFrame(loop);
        return;
      }

      // 处理Face AR:情绪识别
      const faceAnchors = frame.getFaceAnchors ? frame.getFaceAnchors() : [];
      if (faceAnchors.length > 0) {
        this.analyzeEmotion(faceAnchors[0]);
      }

      // 处理Body AR:手势+姿态
      const bodies = frame.acquireBodySkeleton ? frame.acquireBodySkeleton() : [];
      if (bodies.length > 0) {
        this.analyzeGestureAndPosture(bodies[0]);
      }

      frame.release();
      requestAnimationFrame(loop);
    };

    requestAnimationFrame(loop);
  }

  /**
   * 情绪分析核心算法
   * 基于BlendShape参数映射到7种基础情绪+疲劳度
   */
  private analyzeEmotion(faceAnchor: arEngine.ARFaceAnchor): void {
    const face = faceAnchor.getFace();
    if (!face) return;

    const blendShapes = face.getBlendShapes();
    const data = new Float32Array(blendShapes.getData());
    const types = blendShapes.getTypes();

    // 构建表情参数Map
    const expressions = new Map<string, number>();
    types.forEach((type, i) => expressions.set(type.toString(), data[i]));

    // 情绪识别规则引擎(简化版,实际应使用训练好的模型)
    const emotion = this.detectEmotion(expressions);
    const fatigue = this.calculateFatigue(expressions);
    const attention = this.assessAttention(expressions);

    // 更新情绪历史
    this.emotionHistory.push({ emotion, timestamp: Date.now() });
    this.emotionHistory = this.emotionHistory.filter(
      e => Date.now() - e.timestamp < this.HISTORY_WINDOW
    );

    // 计算稳定情绪(滑动窗口众数)
    const stableEmotion = this.calculateStableEmotion();

    // 同步到全局状态
    AppStorage.setOrCreate('current_emotion', stableEmotion);
    AppStorage.setOrCreate('fatigue_level', fatigue);
    AppStorage.setOrCreate('attention_level', attention);
    AppStorage.setOrCreate('face_detected', true);

    // 触发AI决策
    this.triggerAIDecision(stableEmotion, fatigue, attention);
  }

  /**
   * 基于BlendShape的情绪检测规则
   */
  private detectEmotion(expressions: Map<string, number>): EmotionState {
    const jawOpen = expressions.get('JAW_OPEN') || 0;
    const mouthSmile = expressions.get('MOUTH_SMILE_LEFT') || 0;
    const eyeWide = expressions.get('EYE_WIDE_LEFT') || 0;
    const browDown = expressions.get('BROW_DOWN_LEFT') || 0;
    const browUp = expressions.get('BROW_UP_LEFT') || 0;
    const noseWrinkle = expressions.get('NOSE_WRINKLE_LEFT') || 0;
    const mouthFrown = expressions.get('MOUTH_FROWN_LEFT') || 0;

    // 优先级判断
    if (mouthSmile > 0.6 && eyeWide > 0.3) return EmotionState.HAPPY;
    if (mouthFrown > 0.5 && browDown > 0.4) return EmotionState.SAD;
    if (browDown > 0.7 && noseWrinkle > 0.3) return EmotionState.ANGRY;
    if (eyeWide > 0.7 && jawOpen > 0.3) return EmotionState.SURPRISED;
    if (eyeWide > 0.6 && browUp > 0.4) return EmotionState.FEAR;
    if (noseWrinkle > 0.6) return EmotionState.DISGUST;
    
    return EmotionState.NEUTRAL;
  }

  /**
   * 疲劳度计算:基于眨眼频率和头部姿态
   */
  private calculateFatigue(expressions: Map<string, number>): number {
    const eyeBlink = expressions.get('EYE_BLINK_LEFT') || 0;
    // 简化:高眨眼频率=疲劳(实际应结合时间窗口统计)
    return Math.min(1.0, eyeBlink * 2);
  }

  /**
   * 注意力评估:基于瞳孔注视点稳定性
   */
  private assessAttention(expressions: Map<string, number>): AttentionLevel {
    // 简化实现,实际应追踪注视点移动速度
    const eyeLookUp = expressions.get('EYE_LOOK_UP_LEFT') || 0;
    if (eyeLookUp > 0.8) return AttentionLevel.DROWSY;
    return AttentionLevel.NORMAL;
  }

  /**
   * 计算稳定情绪(滑动窗口众数)
   */
  private calculateStableEmotion(): EmotionState {
    if (this.emotionHistory.length === 0) return EmotionState.NEUTRAL;
    
    const counts = new Map<EmotionState, number>();
    this.emotionHistory.forEach(e => {
      counts.set(e.emotion, (counts.get(e.emotion) || 0) + 1);
    });

    let maxCount = 0;
    let stableEmotion = EmotionState.NEUTRAL;
    counts.forEach((count, emotion) => {
      if (count > maxCount) {
        maxCount = count;
        stableEmotion = emotion;
      }
    });

    return stableEmotion;
  }

  /**
   * AI决策触发:根据情绪状态调整系统行为
   */
  private triggerAIDecision(emotion: EmotionState, fatigue: number, attention: AttentionLevel): void {
    // 疲劳度过高:提醒休息
    if (fatigue > 0.8) {
      AppStorage.setOrCreate('ai_suggestion', {
        type: 'rest',
        message: '检测到您比较疲劳,建议休息5分钟',
        priority: 'high'
      });
    }

    // 焦虑状态:开启降噪模式
    if (emotion === EmotionState.FEAR || emotion === EmotionState.ANGRY) {
      AppStorage.setOrCreate('ai_suggestion', {
        type: 'focus',
        message: '已为您开启专注模式,屏蔽非紧急通知',
        priority: 'medium'
      });
    }

    // 愉悦状态:记录高效时段
    if (emotion === EmotionState.HAPPY && attention === AttentionLevel.FOCUSED) {
      AppStorage.setOrCreate('ai_suggestion', {
        type: 'record',
        message: '您当前状态很好,已记录为高效时段',
        priority: 'low'
      });
    }
  }

  /**
   * 手势与姿态分析
   */
  private analyzeGestureAndPosture(body: arEngine.ARBody): void {
    const landmarks = body.getLandmarks2D();
    
    const leftWrist = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.LEFT_WRIST);
    const rightWrist = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.RIGHT_WRIST);
    const leftShoulder = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.LEFT_SHOULDER);
    const rightShoulder = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.RIGHT_SHOULDER);
    const nose = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.NOSE);

    // 手势识别
    const gesture = this.recognizeGesture(leftWrist, rightWrist, leftShoulder, rightShoulder);
    
    // 姿态识别
    const posture = this.recognizePosture(nose, leftShoulder, rightShoulder);

    AppStorage.setOrCreate('body_gesture', gesture);
    AppStorage.setOrCreate('body_posture', posture);
    AppStorage.setOrCreate('body_detected', true);

    // 手势触发AI指令
    this.executeGestureCommand(gesture, posture);
  }

  private findLandmark(landmarks: arEngine.ARBodyLandmark2D[], type: arEngine.ARBodyLandmarkType): arEngine.ARBodyLandmark2D | undefined {
    return landmarks.find(lm => lm.type === type && lm.isValid);
  }

  private recognizeGesture(
    leftWrist?: arEngine.ARBodyLandmark2D,
    rightWrist?: arEngine.ARBodyLandmark2D,
    leftShoulder?: arEngine.ARBodyLandmark2D,
    rightShoulder?: arEngine.ARBodyLandmark2D
  ): { type: string; confidence: number } {
    const leftUp = leftWrist && leftShoulder ? leftWrist.y < leftShoulder.y - 50 : false;
    const rightUp = rightWrist && rightShoulder ? rightWrist.y < rightShoulder.y - 50 : false;

    if (leftUp && rightUp) {
      const distance = leftWrist && rightWrist 
        ? Math.sqrt(Math.pow(leftWrist.x - rightWrist.x, 2) + Math.pow(leftWrist.y - rightWrist.y, 2))
        : 0;
      if (distance < 80) return { type: 'pinch', confidence: 0.9 };
      if (distance > 200) return { type: 'spread', confidence: 0.85 };
      return { type: 'both_hands_up', confidence: 0.8 };
    } else if (leftUp || rightUp) {
      return { type: 'point', confidence: 0.75 };
    }

    return { type: 'idle', confidence: 1.0 };
  }

  private recognizePosture(
    nose?: arEngine.ARBodyLandmark2D,
    leftShoulder?: arEngine.ARBodyLandmark2D,
    rightShoulder?: arEngine.ARBodyLandmark2D
  ): { type: string; angle: number } {
    if (!nose || !leftShoulder || !rightShoulder) return { type: 'neutral', angle: 0 };
    
    const shoulderCenterY = (leftShoulder.y + rightShoulder.y) / 2;
    const offset = nose.y - shoulderCenterY;
    
    if (offset > 60) return { type: 'lean_forward', angle: offset };
    if (offset < -40) return { type: 'lean_back', angle: offset };
    return { type: 'upright', angle: 0 };
  }

  /**
   * 执行手势指令
   */
  private executeGestureCommand(gesture: { type: string }, posture: { type: string }): void {
    const now = Date.now();
    const lastGesture = AppStorage.get<number>('last_gesture_time') || 0;
    
    // 防抖:500ms内不重复触发
    if (now - lastGesture < 500) return;

    switch (gesture.type) {
      case 'both_hands_up':
        // 双手举起:唤醒AI助手
        AppStorage.setOrCreate('ai_awaken', now);
        AppStorage.setOrCreate('last_gesture_time', now);
        break;
      case 'pinch':
        // 捏合:最小化AI窗口
        AppStorage.setOrCreate('ai_minimize', now);
        AppStorage.setOrCreate('last_gesture_time', now);
        break;
      case 'spread':
        // 张开:最大化AI窗口
        AppStorage.setOrCreate('ai_maximize', now);
        AppStorage.setOrCreate('last_gesture_time', now);
        break;
    }

    // 姿态指令
    if (posture.type === 'lean_forward') {
      AppStorage.setOrCreate('ai_focus_mode', true);
    } else if (posture.type === 'lean_back') {
      AppStorage.setOrCreate('ai_focus_mode', false);
    }
  }

  onWindowStageDestroy(): void {
    if (this.arSession) {
      this.arSession.stop();
      this.arSession = null;
    }
  }
}

三、核心组件实战

3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets)

代码亮点:导航栏根据用户情绪状态动态调整颜色、透明度和动画速度。焦虑时切换冷色调+低透明度减少干扰,愉悦时切换暖色调+动态光效增强反馈,疲劳时自动简化导航项并提醒休息 。

typescript 复制代码
// components/EmotionAdaptiveNav.ets
import { window } from '@kit.ArkUI';
import { EmotionState, AttentionLevel } from '../ability/EmotionAIAbility';

/**
 * 情绪-UI映射配置
 */
interface EmotionUIConfig {
  primaryColor: string;
  secondaryColor: string;
  navTransparency: number;
  animationSpeed: number;
  glowIntensity: number;
  showRestReminder: boolean;
  simplifyUI: boolean;
}

@Component
export struct EmotionAdaptiveNav {
  @State currentEmotion: EmotionState = EmotionState.NEUTRAL;
  @State fatigueLevel: number = 0;
  @State attentionLevel: AttentionLevel = AttentionLevel.NORMAL;
  @State navExpanded: boolean = false;
  @State bottomAvoidHeight: number = 0;
  @State aiSuggestion: { type: string; message: string; priority: string } | null = null;

  // 情绪-UI配置映射表
  private emotionConfigMap: Record<EmotionState, EmotionUIConfig> = {
    [EmotionState.NEUTRAL]: {
      primaryColor: '#4A90E2',
      secondaryColor: '#5BA0F2',
      navTransparency: 0.7,
      animationSpeed: 1.0,
      glowIntensity: 0.5,
      showRestReminder: false,
      simplifyUI: false
    },
    [EmotionState.HAPPY]: {
      primaryColor: '#FF9500',
      secondaryColor: '#FFB84D',
      navTransparency: 0.75,
      animationSpeed: 1.2,
      glowIntensity: 0.8,
      showRestReminder: false,
      simplifyUI: false
    },
    [EmotionState.SAD]: {
      primaryColor: '#5B8BD4',
      secondaryColor: '#7BA3E0',
      navTransparency: 0.6,
      animationSpeed: 0.8,
      glowIntensity: 0.3,
      showRestReminder: true,
      simplifyUI: true
    },
    [EmotionState.ANGRY]: {
      primaryColor: '#E74C3C',
      secondaryColor: '#FF6B6B',
      navTransparency: 0.85,
      animationSpeed: 0.5,
      glowIntensity: 0.9,
      showRestReminder: true,
      simplifyUI: true
    },
    [EmotionState.SURPRISED]: {
      primaryColor: '#9B59B6',
      secondaryColor: '#BB8FCE',
      navTransparency: 0.8,
      animationSpeed: 1.5,
      glowIntensity: 0.7,
      showRestReminder: false,
      simplifyUI: false
    },
    [EmotionState.FEAR]: {
      primaryColor: '#2ECC71',
      secondaryColor: '#58D68D',
      navTransparency: 0.55,
      animationSpeed: 0.6,
      glowIntensity: 0.4,
      showRestReminder: true,
      simplifyUI: true
    },
    [EmotionState.DISGUST]: {
      primaryColor: '#34495E',
      secondaryColor: '#5D6D7E',
      navTransparency: 0.65,
      animationSpeed: 0.7,
      glowIntensity: 0.3,
      showRestReminder: false,
      simplifyUI: false
    },
    [EmotionState.TIRED]: {
      primaryColor: '#95A5A6',
      secondaryColor: '#B2BABB',
      navTransparency: 0.5,
      animationSpeed: 0.4,
      glowIntensity: 0.2,
      showRestReminder: true,
      simplifyUI: true
    }
  };

  private navItems = [
    { id: 'home', icon: $r('app.media.ic_home'), label: '首页' },
    { id: 'tasks', icon: $r('app.media.ic_tasks'), label: '任务' },
    { id: 'ai', icon: $r('app.media.ic_ai'), label: 'AI助手' },
    { id: 'focus', icon: $r('app.media.ic_focus'), label: '专注' },
    { id: 'profile', icon: $r('app.media.ic_profile'), label: '我的' }
  ];

  aboutToAppear(): void {
    this.getBottomAvoidArea();
    this.setupEmotionListening();
  }

  private async getBottomAvoidArea(): Promise<void> {
    try {
      const mainWindow = await window.getLastWindow();
      const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      this.bottomAvoidHeight = avoidArea.bottomRect.height;
    } catch (error) {
      console.error('Failed to get avoid area:', error);
    }
  }

  private setupEmotionListening(): void {
    AppStorage.watch('current_emotion', (emotion: EmotionState) => {
      if (emotion !== this.currentEmotion) {
        this.currentEmotion = emotion;
        this.triggerEmotionTransition();
      }
    });

    AppStorage.watch('fatigue_level', (level: number) => {
      this.fatigueLevel = level;
    });

    AppStorage.watch('attention_level', (level: AttentionLevel) => {
      this.attentionLevel = level;
    });

    AppStorage.watch('ai_suggestion', (suggestion: { type: string; message: string }) => {
      this.aiSuggestion = suggestion;
      // 高优先级建议自动展开导航显示
      if (suggestion && suggestion.priority === 'high') {
        this.navExpanded = true;
        setTimeout(() => this.navExpanded = false, 5000);
      }
    });
  }

  /**
   * 情绪切换过渡动画
   */
  private triggerEmotionTransition(): void {
    // 触发全屏光效过渡
    AppStorage.setOrCreate('emotion_transition', {
      from: this.currentEmotion,
      to: this.currentEmotion,
      timestamp: Date.now()
    });
  }

  /**
   * 获取当前情绪对应的UI配置
   */
  private getCurrentConfig(): EmotionUIConfig {
    return this.emotionConfigMap[this.currentEmotion] || this.emotionConfigMap[EmotionState.NEUTRAL];
  }

  build() {
    const config = this.getCurrentConfig();

    Stack({ alignContent: Alignment.Bottom }) {
      // 内容区域
      Column() {
        this.contentBuilder()
      }
      .width('100%')
      .height('100%')
      .padding({ bottom: this.bottomAvoidHeight + 100 })

      // AI建议浮层(高优先级时显示)
      if (this.aiSuggestion && this.aiSuggestion.priority === 'high') {
        this.buildAISuggestionCard()
      }

      // 情绪自适应悬浮导航
      Column() {
        Stack() {
          // 情绪色光晕背景
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(config.primaryColor)
            .opacity(config.glowIntensity * 0.3)
            .blur(80)
            .animation({
              duration: 3000 / config.animationSpeed,
              curve: Curve.EaseInOut,
              iterations: -1,
              playMode: PlayMode.Alternate
            })

          // 毛玻璃主体
          Column()
            .width('100%')
            .height('100%')
            .backgroundBlurStyle(BlurStyle.COMPONENT_THICK)
            .opacity(config.navTransparency)

          // 高光层
          Column()
            .width('100%')
            .height('100%')
            .linearGradient({
              direction: GradientDirection.Top,
              colors: [
                ['rgba(255,255,255,0.2)', 0.0],
                ['rgba(255,255,255,0.05)', 0.5],
                ['transparent', 1.0]
              ]
            })
        }
        .width('100%')
        .height('100%')
        .borderRadius(24)
        .shadow({
          radius: 20,
          color: config.primaryColor + (Math.floor(config.glowIntensity * 255).toString(16).padStart(2, '0')),
          offsetX: 0,
          offsetY: -4
        })

        // 导航内容
        Column() {
          // 情绪状态指示器
          Row({ space: 8 }) {
            // 情绪色点(呼吸动画)
            Column()
              .width(8)
              .height(8)
              .backgroundColor(config.primaryColor)
              .borderRadius(4)
              .shadow({ radius: 6, color: config.primaryColor })
              .animation({
                duration: 2000 / config.animationSpeed,
                curve: Curve.EaseInOut,
                iterations: -1,
                playMode: PlayMode.Alternate
              })
              .scale({ x: 1.5, y: 1.5 })

            Text(this.getEmotionLabel(this.currentEmotion))
              .fontSize(11)
              .fontColor(config.primaryColor)
              .fontWeight(FontWeight.Medium)

            // 疲劳度指示条
            if (this.fatigueLevel > 0.3) {
              Row() {
                Column()
                  .width(`${this.fatigueLevel * 100}%`)
                  .height(4)
                  .backgroundColor(this.fatigueLevel > 0.7 ? '#FF4444' : '#FFAA00')
                  .borderRadius(2)
              }
              .width(40)
              .height(4)
              .backgroundColor('rgba(255,255,255,0.1)')
              .borderRadius(2)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
          .padding({ top: 8, bottom: 4 })

          // 导航项(根据情绪简化)
          Row({ space: 0 }) {
            ForEach(this.getVisibleNavItems(config.simplifyUI), (item: {id: string; icon: Resource; label: string}) => {
              Column({ space: 4 }) {
                Stack() {
                  // 选中光晕
                  if (this.getSelectedNavId() === item.id) {
                    Column()
                      .width(48)
                      .height(48)
                      .backgroundColor(config.primaryColor)
                      .borderRadius(16)
                      .opacity(0.25)
                      .blur(10)
                      .animation({
                        duration: 1500 / config.animationSpeed,
                        curve: Curve.EaseInOut,
                        iterations: -1,
                        playMode: PlayMode.Alternate
                      })
                  }

                  Image(item.icon)
                    .width(24)
                    .height(24)
                    .fillColor(this.getSelectedNavId() === item.id ? '#FFFFFF' : 'rgba(255,255,255,0.6)')
                }
                .width(48)
                .height(48)
                .justifyContent(FlexAlign.Center)

                if (!config.simplifyUI) {
                  Text(item.label)
                    .fontSize(10)
                    .fontColor(this.getSelectedNavId() === item.id ? '#FFFFFF' : 'rgba(255,255,255,0.5)')
                }
              }
              .width(60)
              .height(config.simplifyUI ? 56 : 68)
              .onClick(() => this.handleNavClick(item.id))
            })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceAround)
          .padding({ left: 8, right: 8, bottom: 8 })

          // 休息提醒(疲劳时显示)
          if (config.showRestReminder && this.fatigueLevel > 0.6) {
            this.buildRestReminder(config)
          }
        }
        .width('100%')
        .height('100%')
      }
      .width('92%')
      .height(this.navExpanded ? 160 : (config.showRestReminder && this.fatigueLevel > 0.6 ? 120 : 90))
      .margin({ 
        bottom: this.bottomAvoidHeight + 16, 
        left: '4%', 
        right: '4%' 
      })
      .animation({
        duration: 400,
        curve: Curve.Spring,
        iterations: 1
      })
      .gesture(
        LongPressGesture({ duration: 500 })
          .onAction(() => this.navExpanded = !this.navExpanded)
      )
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 根据情绪状态决定显示的导航项
   * 焦虑/疲劳时简化UI,只显示核心功能
   */
  private getVisibleNavItems(simplify: boolean): Array<{id: string; icon: Resource; label: string}> {
    if (simplify) {
      // 简化模式:只保留首页、AI助手、我的
      return this.navItems.filter(item => ['home', 'ai', 'profile'].includes(item.id));
    }
    return this.navItems;
  }

  private getSelectedNavId(): string {
    return AppStorage.get<string>('selected_nav_id') || 'home';
  }

  private handleNavClick(id: string): void {
    AppStorage.setOrCreate('selected_nav_id', id);
    
    // 触发选中反馈(根据情绪调整强度)
    const config = this.getCurrentConfig();
    this.triggerSelectFeedback(config.animationSpeed);
  }

  private triggerSelectFeedback(speed: number): void {
    try {
      import('@kit.SensorServiceKit').then(sensor => {
        sensor.vibrator.startVibration({
          type: 'time',
          duration: Math.round(30 / speed)
        }, { id: 0 });
      });
    } catch (error) {
      console.error('Haptic feedback failed:', error);
    }
  }

  private getEmotionLabel(emotion: EmotionState): string {
    const labels: Record<EmotionState, string> = {
      [EmotionState.NEUTRAL]: '平静',
      [EmotionState.HAPPY]: '愉悦',
      [EmotionState.SAD]: '低落',
      [EmotionState.ANGRY]: '烦躁',
      [EmotionState.SURPRISED]: '惊讶',
      [EmotionState.FEAR]: '焦虑',
      [EmotionState.DISGUST]: '不适',
      [EmotionState.TIRED]: '疲劳'
    };
    return labels[emotion] || '平静';
  }

  @Builder
  buildAISuggestionCard(): void {
    Column({ space: 8 }) {
      Row({ space: 8 }) {
        Image($r('app.media.ic_ai'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
        
        Text('AI助手建议')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Medium)
        
        Text('刚刚')
          .fontSize(10)
          .fontColor('rgba(255,255,255,0.5)')
          .layoutWeight(1)
      }
      .width('100%')

      Text(this.aiSuggestion?.message || '')
        .fontSize(13)
        .fontColor('#FFFFFF')
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })

      Row({ space: 8 }) {
        Button('立即执行')
          .type(ButtonType.Capsule)
          .fontSize(12)
          .fontColor('#FFFFFF')
          .backgroundColor('rgba(255,255,255,0.2)')
          .height(32)
          .onClick(() => this.executeAISuggestion())

        Button('忽略')
          .type(ButtonType.Capsule)
          .fontSize(12)
          .fontColor('rgba(255,255,255,0.6)')
          .backgroundColor('transparent')
          .height(32)
          .onClick(() => {
            this.aiSuggestion = null;
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.End)
    }
    .width('85%')
    .padding(16)
    .backgroundColor('rgba(74,144,226,0.9)')
    .borderRadius(16)
    .shadow({ radius: 16, color: 'rgba(74,144,226,0.4)' })
    .margin({ bottom: 12 })
    .position({ x: '7.5%', y: undefined })
    .animation({
      duration: 300,
      curve: Curve.Spring
    })
  }

  @Builder
  buildRestReminder(config: EmotionUIConfig): void {
    Row({ space: 8 }) {
      Image($r('app.media.ic_rest'))
        .width(16)
        .height(16)
        .fillColor('#FFAA00')
      
      Text(`疲劳度 ${Math.round(this.fatigueLevel * 100)}%,建议休息`)
        .fontSize(11)
        .fontColor('#FFAA00')
      
      Button('开始休息')
        .type(ButtonType.Capsule)
        .fontSize(10)
        .fontColor('#FFFFFF')
        .backgroundColor('#FFAA00')
        .height(24)
        .padding({ left: 12, right: 12 })
        .onClick(() => this.startRestMode())
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .padding({ top: 4, bottom: 4 })
  }

  private executeAISuggestion(): void {
    // 执行AI建议的具体逻辑
    this.aiSuggestion = null;
  }

  private startRestMode(): void {
    AppStorage.setOrCreate('rest_mode_active', true);
  }

  @BuilderParam contentBuilder: () => void = this.defaultContentBuilder;

  @Builder
  defaultContentBuilder(): void {
    Column() {
      Text('AI工作台内容区')
        .fontSize(16)
        .fontColor('#FFFFFF40')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3.2 手势控制AI助手面板(GestureAIPanel.ets)

代码亮点:AI助手面板响应Body AR手势指令,双手举起唤醒、捏合最小化、张开最大化,无需点击即可操控。面板位置智能避让手势活动区域 。

typescript 复制代码
// components/GestureAIPanel.ets
import { window } from '@kit.ArkUI';

@Component
export struct GestureAIPanel {
  @State panelVisible: boolean = false;
  @State panelMinimized: boolean = false;
  @State panelPosition: { x: number; y: number } = { x: 1200, y: 600 };
  @State panelSize: { width: number; height: number } = { width: 380, height: 520 };
  @State gestureState: string = 'idle';
  @State focusMode: boolean = false;

  private aiWindow: window.Window | null = null;

  aboutToAppear(): void {
    this.setupGestureListening();
    this.initializeAIWindow();
  }

  private setupGestureListening(): void {
    // 监听唤醒手势
    AppStorage.watch('ai_awaken', (timestamp: number) => {
      if (timestamp) {
        this.panelVisible = true;
        this.panelMinimized = false;
        this.animatePanelEntry();
      }
    });

    // 监听最小化手势
    AppStorage.watch('ai_minimize', (timestamp: number) => {
      if (timestamp) {
        this.panelMinimized = true;
      }
    });

    // 监听最大化手势
    AppStorage.watch('ai_maximize', (timestamp: number) => {
      if (timestamp) {
        this.panelMinimized = false;
      }
    });

    // 监听专注模式
    AppStorage.watch('ai_focus_mode', (active: boolean) => {
      this.focusMode = active;
      if (active) {
        // 专注模式:AI面板自动靠边,减少干扰
        this.panelPosition = { x: 1600, y: 200 };
      }
    });

    // 监听手势状态(用于UI反馈)
    AppStorage.watch('body_gesture', (gesture: { type: string; confidence: number }) => {
      this.gestureState = gesture.type;
    });
  }

  private async initializeAIWindow(): Promise<void> {
    try {
      const mainWindow = await window.getLastWindow();
      this.aiWindow = await mainWindow.createSubWindow('AIAssistant');
      
      await this.aiWindow.setWindowSizeType(window.WindowSizeType.FREE);
      await this.aiWindow.moveWindowTo(this.panelPosition);
      await this.aiWindow.resize(this.panelSize.width, this.panelSize.height);
      await this.aiWindow.setWindowBackgroundColor('#00000000');
      await this.aiWindow.setWindowShadowEnabled(true);
      await this.aiWindow.setWindowCornerRadius(20);
      await this.aiWindow.setWindowTopmost(true);

      // 加载AI助手内容
      await this.aiWindow.setUIContent('pages/AIChatPage');
      
    } catch (error) {
      console.error('AI window init failed:', error);
    }
  }

  private animatePanelEntry(): void {
    // 入场动画:从底部滑入+光效脉冲
    this.panelPosition = { x: this.panelPosition.x, y: 800 };
    setTimeout(() => {
      this.panelPosition = { x: 1200, y: 600 };
    }, 50);
  }

  build() {
    if (!this.panelVisible) {
      // 隐藏状态:显示悬浮唤醒按钮
      this.buildAwakenButton();
      return;
    }

    if (this.panelMinimized) {
      // 最小化状态:显示为悬浮球
      this.buildMinimizedBall();
      return;
    }

    // 正常状态:完整AI面板
    this.buildFullPanel();
  }

  @Builder
  buildAwakenButton(): void {
    Stack({ alignContent: Alignment.BottomEnd }) {
      Column()
        .width(56)
        .height(56)
        .backgroundColor('rgba(74,144,226,0.8)')
        .borderRadius(28)
        .shadow({ radius: 12, color: 'rgba(74,144,226,0.4)' })
        .margin({ right: 24, bottom: 120 })
        .onClick(() => {
          this.panelVisible = true;
        })
        .gesture(
          // 支持手势唤醒:在按钮区域做举起手势
          PanGesture()
            .onActionStart(() => {
              this.panelVisible = true;
            })
        )

      Image($r('app.media.ic_ai'))
        .width(28)
        .height(28)
        .fillColor('#FFFFFF')
        .margin({ right: 24, bottom: 120 })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildMinimizedBall(): void {
    Stack({ alignContent: Alignment.BottomEnd }) {
      Column()
        .width(48)
        .height(48)
        .backgroundColor('rgba(74,144,226,0.6)')
        .borderRadius(24)
        .shadow({ radius: 8, color: 'rgba(74,144,226,0.3)' })
        .margin({ right: 24, bottom: 120 })
        .onClick(() => {
          this.panelMinimized = false;
        })
        .animation({
          duration: 2000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .scale({ x: 1.1, y: 1.1 })

      // 手势状态指示环
      if (this.gestureState !== 'idle') {
        Column()
          .width(56)
          .height(56)
          .borderWidth(2)
          .borderColor('#00FF88')
          .borderRadius(28)
          .margin({ right: 20, bottom: 116 })
          .animation({
            duration: 1000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })
          .opacity({ x: 0.3, y: 1.0 })
      }
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildFullPanel(): void {
    Stack() {
      // 面板主体
      Column() {
        // 玻璃拟态背景
        Stack() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(this.focusMode ? '#2C3E50' : '#1a1a2e')
            .opacity(0.9)
            .blur(40)

          Column()
            .width('100%')
            .height('100%')
            .backgroundBlurStyle(BlurStyle.COMPONENT_THICK)

          // 顶部光效
          Column()
            .width('100%')
            .height(60)
            .linearGradient({
              direction: GradientDirection.Top,
              colors: [
                ['rgba(74,144,226,0.2)', 0.0],
                ['transparent', 1.0]
              ]
            })
        }
        .width('100%')
        .height('100%')
        .borderRadius(20)

        // 面板内容
        Column({ space: 0 }) {
          // 标题栏(支持拖拽移动)
          this.buildPanelHeader()

          // AI对话区域
          this.buildAIChatArea()

          // 手势状态指示器
          this.buildGestureIndicator()

          // 输入区域
          this.buildInputArea()
        }
        .width('100%')
        .height('100%')
        .padding(16)
      }
      .width(this.panelSize.width)
      .height(this.panelSize.height)
      .position({
        x: this.panelPosition.x,
        y: this.panelPosition.y
      })
      .shadow({
        radius: 24,
        color: 'rgba(0,0,0,0.3)',
        offsetX: 0,
        offsetY: 8
      })
      .animation({
        duration: 300,
        curve: Curve.Spring
      })
      .gesture(
        PanGesture()
          .onActionUpdate((event: GestureEvent) => {
            // 拖拽移动面板
            this.panelPosition = {
              x: this.panelPosition.x + event.offsetX,
              y: this.panelPosition.y + event.offsetY
            };
          })
      )
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildPanelHeader(): void {
    Row({ space: 8 }) {
      // AI头像(带情绪感知光晕)
      Stack() {
        Column()
          .width(40)
          .height(40)
          .backgroundColor('#4A90E2')
          .borderRadius(20)
          .opacity(0.3)
          .blur(8)
          .animation({
            duration: 2000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })

        Image($r('app.media.ic_ai_avatar'))
          .width(36)
          .height(36)
          .borderRadius(18)
      }

      Column() {
        Text('AI助手')
          .fontSize(15)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Medium)
        
        Text('情绪感知模式已开启')
          .fontSize(11)
          .fontColor('#4A90E2')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)

      // 窗口控制
      Row({ space: 8 }) {
        Button() { Text('−').fontColor('#FFFFFF').fontSize(14) }
          .type(ButtonType.Circle)
          .backgroundColor('rgba(255,255,255,0.1)')
          .width(28)
          .height(28)
          .onClick(() => this.panelMinimized = true)

        Button() { Text('×').fontColor('#FFFFFF').fontSize(14) }
          .type(ButtonType.Circle)
          .backgroundColor('rgba(255,255,255,0.1)')
          .width(28)
          .height(28)
          .onClick(() => this.panelVisible = false)
      }
    }
    .width('100%')
    .height(56)
    .padding({ bottom: 12 })
  }

  @Builder
  buildAIChatArea(): void {
    Column() {
      // 对话内容(简化示例)
      List({ space: 12 }) {
        ListItem() {
          this.buildAIMessage('检测到您当前状态比较专注,需要我帮您屏蔽干扰通知吗?', 'suggest')
        }
        
        ListItem() {
          this.buildUserMessage('好的,开启专注模式')
        }
        
        ListItem() {
          this.buildAIMessage('已为您开启专注模式,当前任务进度:文档编辑 65%,预计还需 12 分钟', 'info')
        }
      }
      .width('100%')
      .layoutWeight(1)
      .scrollBar(BarState.Auto)
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ top: 8, bottom: 8 })
  }

  @Builder
  buildAIMessage(content: string, type: string): void {
    Row({ space: 8 }) {
      Column()
        .width(32)
        .height(32)
        .backgroundColor('#4A90E2')
        .borderRadius(16)
      
      Column() {
        Text(content)
          .fontSize(13)
          .fontColor('#FFFFFF')
          .maxLines(5)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
        
        if (type === 'suggest') {
          Row({ space: 8 }) {
            Button('接受')
              .type(ButtonType.Capsule)
              .fontSize(12)
              .fontColor('#FFFFFF')
              .backgroundColor('#4A90E2')
              .height(28)
            
            Button('忽略')
              .type(ButtonType.Capsule)
              .fontSize(12)
              .fontColor('rgba(255,255,255,0.6)')
              .backgroundColor('transparent')
              .height(28)
          }
          .margin({ top: 8 })
        }
      }
      .layoutWeight(1)
      .padding(12)
      .backgroundColor('rgba(255,255,255,0.05)')
      .borderRadius(12)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding({ right: 40 })
  }

  @Builder
  buildUserMessage(content: string): void {
    Row({ space: 8 }) {
      Column()
        .layoutWeight(1)
      
      Column() {
        Text(content)
          .fontSize(13)
          .fontColor('#FFFFFF')
      }
      .padding(12)
      .backgroundColor('#4A90E2')
      .borderRadius(12)
    }
    .width('100%')
    .padding({ left: 40 })
  }

  @Builder
  buildGestureIndicator(): void {
    Row({ space: 8 }) {
      // 当前手势图标
      Text(this.getGestureIcon(this.gestureState))
        .fontSize(20)
      
      Text(this.getGestureLabel(this.gestureState))
        .fontSize(11)
        .fontColor('rgba(255,255,255,0.5)')
    }
    .width('100%')
    .height(32)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('rgba(0,0,0,0.2)')
    .borderRadius(8)
    .margin({ bottom: 8 })
    .visibility(this.gestureState !== 'idle' ? Visibility.Visible : Visibility.Hidden)
  }

  @Builder
  buildInputArea(): void {
    Row({ space: 8 }) {
      TextInput({ placeholder: '输入指令,或用手势控制...' })
        .placeholderColor('rgba(255,255,255,0.3)')
        .fontColor('#FFFFFF')
        .backgroundColor('rgba(255,255,255,0.05)')
        .borderRadius(20)
        .height(40)
        .layoutWeight(1)

      Button() {
        Image($r('app.media.ic_send'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
      }
      .type(ButtonType.Circle)
      .backgroundColor('#4A90E2')
      .width(40)
      .height(40)
    }
    .width('100%')
    .height(56)
  }

  private getGestureIcon(gesture: string): string {
    const icons: Record<string, string> = {
      'idle': '✋',
      'point': '👆',
      'pinch': '🤏',
      'spread': '🙌',
      'both_hands_up': '🙋'
    };
    return icons[gesture] || '✋';
  }

  private getGestureLabel(gesture: string): string {
    const labels: Record<string, string> = {
      'idle': '等待手势',
      'point': '单手指示',
      'pinch': '捏合缩放',
      'spread': '张开放大',
      'both_hands_up': '双手唤醒'
    };
    return labels[gesture] || '等待手势';
  }
}

3.3 主工作台页面:情绪光效与内容整合

typescript 复制代码
// pages/EmotionAIPage.ets
import { EmotionAdaptiveNav } from '../components/EmotionAdaptiveNav';
import { GestureAIPanel } from '../components/GestureAIPanel';
import { EmotionState } from '../ability/EmotionAIAbility';

@Entry
@Component
struct EmotionAIPage {
  @State currentEmotion: EmotionState = EmotionState.NEUTRAL;
  @State themeColor: string = '#4A90E2';
  @State restModeActive: boolean = false;

  aboutToAppear(): void {
    AppStorage.watch('current_emotion', (emotion: EmotionState) => {
      this.currentEmotion = emotion;
      this.updateThemeColor(emotion);
    });

    AppStorage.watch('rest_mode_active', (active: boolean) => {
      this.restModeActive = active;
    });
  }

  private updateThemeColor(emotion: EmotionState): void {
    const colorMap: Record<EmotionState, string> = {
      [EmotionState.NEUTRAL]: '#4A90E2',
      [EmotionState.HAPPY]: '#FF9500',
      [EmotionState.SAD]: '#5B8BD4',
      [EmotionState.ANGRY]: '#E74C3C',
      [EmotionState.SURPRISED]: '#9B59B6',
      [EmotionState.FEAR]: '#2ECC71',
      [EmotionState.DISGUST]: '#34495E',
      [EmotionState.TIRED]: '#95A5A6'
    };
    this.themeColor = colorMap[emotion] || '#4A90E2';
  }

  build() {
    Stack() {
      // 第一层:情绪环境光背景
      this.buildEmotionAmbientLight()

      // 第二层:休息模式覆盖层
      if (this.restModeActive) {
        this.buildRestModeOverlay()
      }

      // 第三层:主内容区
      EmotionAdaptiveNav({
        contentBuilder: () => {
          this.buildMainContent()
        }
      })

      // 第四层:手势控制AI面板
      GestureAIPanel()

      // 第五层:情绪状态浮层
      this.buildEmotionStatusOverlay()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a12')
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
    )
  }

  @Builder
  buildEmotionAmbientLight(): void {
    Column() {
      // 主光源(情绪色)
      Column()
        .width(800)
        .height(800)
        .backgroundColor(this.themeColor)
        .blur(250)
        .opacity(0.12)
        .position({ x: '50%', y: '40%' })
        .anchor('50%')
        .animation({
          duration: 8000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })

      // 辅助光点(情绪互补色)
      Column()
        .width(400)
        .height(400)
        .backgroundColor(this.getComplementaryColor(this.themeColor))
        .blur(150)
        .opacity(0.06)
        .position({ x: '20%', y: '70%' })
        .animation({
          duration: 12000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.AlternateReverse
        })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildRestModeOverlay(): void {
    Column() {
      Column() {
        Text('🌙 休息模式')
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .margin({ bottom: 16 })

        Text('您已连续工作 90 分钟,建议休息 5 分钟')
          .fontSize(16)
          .fontColor('rgba(255,255,255,0.7)')
          .margin({ bottom: 24 })

        // 呼吸指导动画
        Column()
          .width(120)
          .height(120)
          .backgroundColor('rgba(255,255,255,0.1)')
          .borderRadius(60)
          .animation({
            duration: 4000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })
          .scale({ x: 1.3, y: 1.3 })

        Text('跟随圆环呼吸')
          .fontSize(14)
          .fontColor('rgba(255,255,255,0.5)')
          .margin({ top: 24 })

        Button('结束休息')
          .type(ButtonType.Capsule)
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor('#4A90E2')
          .width(160)
          .height(48)
          .margin({ top: 32 })
          .onClick(() => {
            AppStorage.setOrCreate('rest_mode_active', false);
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.85)')
    .zIndex(100)
  }

  @Builder
  buildMainContent(): void {
    Column({ space: 16 }) {
      // 欢迎语(情绪感知)
      Text(this.getEmotionGreeting(this.currentEmotion))
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
        .alignSelf(ItemAlign.Start)

      // 今日任务概览
      Column({ space: 12 }) {
        Text('今日任务')
          .fontSize(18)
          .fontColor('#FFFFFF')
          .alignSelf(ItemAlign.Start)

        ForEach([1, 2, 3], (index: number) => {
          Row({ space: 12 }) {
            Column()
              .width(12)
              .height(12)
              .backgroundColor(index === 1 ? this.themeColor : 'rgba(255,255,255,0.2)')
              .borderRadius(6)

            Text(`任务 ${index}`)
              .fontSize(14)
              .fontColor(index === 1 ? '#FFFFFF' : 'rgba(255,255,255,0.5)')
              .layoutWeight(1)

            Text(index === 1 ? '进行中' : '待开始')
              .fontSize(12)
              .fontColor(index === 1 ? this.themeColor : 'rgba(255,255,255,0.3)')
          }
          .width('100%')
          .height(48)
          .padding({ left: 16, right: 16 })
          .backgroundColor('rgba(255,255,255,0.03)')
          .borderRadius(12)
        })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('rgba(255,255,255,0.02)')
      .borderRadius(16)

      // 效率统计
      Row({ space: 16 }) {
        Column({ space: 4 }) {
          Text('专注时长')
            .fontSize(12)
            .fontColor('rgba(255,255,255,0.5)')
          Text('2h 35m')
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
        }
        .layoutWeight(1)

        Column({ space: 4 }) {
          Text('情绪状态')
            .fontSize(12)
            .fontColor('rgba(255,255,255,0.5)')
          Text(this.getEmotionLabel(this.currentEmotion))
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.themeColor)
        }
        .layoutWeight(1)

        Column({ space: 4 }) {
          Text('AI互动')
            .fontSize(12)
            .fontColor('rgba(255,255,255,0.5)')
          Text('12次')
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
        }
        .layoutWeight(1)
      }
      .width('100%')
      .padding(16)
      .backgroundColor('rgba(255,255,255,0.02)')
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .padding(24)
  }

  @Builder
  buildEmotionStatusOverlay(): void {
    Column() {
      // 右上角情绪指示器
      Row({ space: 8 }) {
        Column()
          .width(8)
          .height(8)
          .backgroundColor(this.themeColor)
          .borderRadius(4)
          .shadow({ radius: 4, color: this.themeColor })
          .animation({
            duration: 2000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })

        Text(this.getEmotionLabel(this.currentEmotion))
          .fontSize(12)
          .fontColor(this.themeColor)
      }
      .padding({ left: 12, right: 12, top: 6, bottom: 6 })
      .backgroundColor('rgba(0,0,0,0.4)')
      .borderRadius(16)
    }
    .position({ x: '85%', y: 20 })
    .visibility(this.currentEmotion !== EmotionState.NEUTRAL ? Visibility.Visible : Visibility.Hidden)
  }

  private getEmotionGreeting(emotion: EmotionState): string {
    const greetings: Record<EmotionState, string> = {
      [EmotionState.NEUTRAL]: '你好,开始今天的工作吧',
      [EmotionState.HAPPY]: '看起来心情不错,效率会很高哦',
      [EmotionState.SAD]: '今天有点低落,需要我放点轻音乐吗',
      [EmotionState.ANGRY]: '检测到您有些烦躁,已开启降噪模式',
      [EmotionState.SURPRISED]: '有什么新发现吗?',
      [EmotionState.FEAR]: '别紧张,慢慢来,我帮您简化界面',
      [EmotionState.DISGUST]: '有什么不满意的地方吗?',
      [EmotionState.TIRED]: '您看起来很累,注意休息'
    };
    return greetings[emotion] || '你好';
  }

  private getEmotionLabel(emotion: EmotionState): string {
    const labels: Record<EmotionState, string> = {
      [EmotionState.NEUTRAL]: '平静',
      [EmotionState.HAPPY]: '愉悦',
      [EmotionState.SAD]: '低落',
      [EmotionState.ANGRY]: '烦躁',
      [EmotionState.SURPRISED]: '惊讶',
      [EmotionState.FEAR]: '焦虑',
      [EmotionState.DISGUST]: '不适',
      [EmotionState.TIRED]: '疲劳'
    };
    return labels[emotion] || '平静';
  }

  private getComplementaryColor(color: string): string {
    // 简化实现,返回固定互补色
    const complements: Record<string, string> = {
      '#4A90E2': '#E2A44A',
      '#FF9500': '#0095FF',
      '#5B8BD4': '#D4A85B',
      '#E74C3C': '#3CE7A8',
      '#9B59B6': '#59B69B',
      '#2ECC71': '#CC2E71',
      '#34495E': '#5E4434',
      '#95A5A6': '#A69595'
    };
    return complements[color] || '#FFFFFF';
  }
}

四、关键技术总结

4.1 情绪-UI自适应映射体系

情绪状态 主色调 导航透明度 动画速度 UI简化 特殊行为
平静 海洋蓝 #4A90E2 70% 1.0x 标准交互
愉悦 活力橙 #FF9500 75% 1.2x 增强反馈光效
低落 柔和蓝 #5B8BD4 60% 0.8x 显示鼓励提示
烦躁 警示红 #E74C3C 85% 0.5x 自动开启降噪
惊讶 神秘紫 #9B59B6 80% 1.5x 高亮新内容
焦虑 镇静绿 #2ECC71 55% 0.6x 简化界面+呼吸引导
不适 沉稳灰 #34495E 65% 0.7x 询问反馈
疲劳 柔和灰 #95A5A6 50% 0.4x 强制休息提醒

4.2 手势-AI指令映射

手势 触发条件 AI响应 视觉反馈
双手举起 双腕高于肩 唤醒AI面板 面板从底部滑入
单手抬起 单腕高于肩 选中/指向 光标跟随手势
捏合 双手靠近<80px 最小化面板 面板收缩为悬浮球
张开 双手分开>200px 最大化面板 面板展开全屏
前倾 鼻-肩距>60px 专注模式 面板自动靠边
后仰 鼻-肩距<-40px 全局视图 面板恢复默认

4.3 性能与隐私优化

typescript 复制代码
// 1. 情绪识别节流:每100ms处理一次,避免过度计算
private emotionThrottleMs = 100;

// 2. 端侧处理:所有AR数据本地处理,不上传云端
// AR Engine 6.1.0 端侧推理 

// 3. 情绪数据脱敏:只存储情绪分类,不保存原始面部图像
private storeEmotionSummaryOnly = true;

// 4. 用户可控:提供关闭AR感知的隐私开关
AppStorage.setOrCreate('ar_perception_enabled', true); // 用户可关闭

五、调试与部署建议

5.1 真机调试要点

  1. 摄像头权限:首次启动需引导用户授权摄像头访问
  2. 光线条件:避免逆光,确保面部和手部清晰可见
  3. 距离范围:建议用户距离摄像头0.5-1.5米
  4. 多窗口测试:验证AI面板与主窗口的层级关系

5.2 部署适配

设备类型 摄像头要求 功能适配
华为MateBook 内置720P 基础情绪+手势
华为MateStation 外接1080P 全功能高精度
第三方PC 需USB摄像头 降级为表情识别

六、总结与展望

本文基于 HarmonyOS 6(API 23)的 Face ARBody AR悬浮导航沉浸光感四大特性,完整实战了一款"情绪感知智能工作台"。核心创新点:

  1. 情绪驱动UI:通过Face AR实时识别7种基础情绪,自动调整界面色调、透明度、动画速度和内容复杂度,实现"UI懂你的心情"
  2. 手势零接触交互:通过Body AR识别6种手势,实现AI助手的唤醒、缩放、移动等操作,无需鼠标键盘
  3. 主动式AI建议:基于情绪历史滑动窗口,AI主动提出休息提醒、专注模式、降噪等建议,从被动响应升级为主动关怀
  4. 全链路沉浸光效:情绪色驱动环境光晕、导航光效、AI面板主题,打造视觉一致的情绪化体验

未来扩展方向

  • 接入 分布式软总线,实现跨设备情绪状态同步(手机检测到焦虑,PC自动开启降噪)
  • 结合 AI大模型,基于情绪状态生成个性化工作内容建议
  • 探索 眼动追踪(Face AR注视点),实现"看哪里高亮哪里"的精准交互
  • 引入 生物反馈闭环:检测心率变异性(HRV),与情绪识别交叉验证

转载自:https://blog.csdn.net/u014727709/article/details/147283885

欢迎 👍点赞✍评论⭐收藏,欢迎指正

相关推荐
Bruce_Liuxiaowei2 小时前
DeepSeek V4 × 华为昇腾:国产AI算力推理适配的实质性进展
人工智能·华为·算力·deepseek·v4
HwJack202 小时前
HarmonyOS 开发中Web 组件渲染进程崩溃后的“起死回生”
前端·华为·harmonyos
前端不太难2 小时前
鸿蒙游戏架构进阶:如何拆分 Store 与 System?
游戏·架构·harmonyos
liulian09162 小时前
【Flutter for OpenHarmony第三方库】Flutter for OpenHarmony 数据统计与用户行为分析功能适配与实现指南
flutter·华为·学习方法·harmonyos
想你依然心痛2 小时前
HarmonyOS 6(API 23)分布式实战:基于悬浮导航与沉浸光感的“光影协创“跨设备白板系统
分布式·wpf·harmonyos·悬浮导航·沉浸光感
在下胡三汉3 小时前
GLTF 与 USDZ:电商增强现实和虚拟现实的最佳3D模型格式
3d·ar·vr
nashane12 小时前
HarmonyOS 6学习:旋转动画优化与长截图性能调优——打造丝滑交互体验的深度实践
学习·交互·harmonyos·harmonyos 5
北京阿法龙科技有限公司17 小时前
真 AR 安防眼镜|Alpha S30:10 米远距识人 + 动态追踪,安防实战再升级
ar
南村群童欺我老无力.17 小时前
鸿蒙自定义组件接口设计的向后兼容陷阱
华为·harmonyos