⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

12.3.4 用户体验优化——加载状态与错误处理:优雅的 AI 交互设计

一句话破题

AI 应用的用户体验不仅取决于模型能力,更取决于如何处理加载、错误和边缘情况——这是区分"能用"和"好用"的关键。

核心价值

优秀的 AI 应用需要处理以下场景:

  1. 加载状态:让用户知道 AI 正在思考
  2. 流式显示:逐字显示,而非突然出现
  3. 错误恢复:出错时提供重试选项
  4. 取消操作:允许用户中途停止
  5. 空状态:初始状态的引导

加载状态设计

tsx
function LoadingIndicator() {
  return (
    <div className="flex items-center gap-2 text-gray-500">
      <div className="flex gap-1">
        <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
        <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
        <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
      </div>
      <span>AI 正在思考...</span>
    </div>
  );
}

function ChatUI() {
  const { messages, isLoading, ... } = useChat();

  return (
    <div>
      {messages.map((m) => (
        <MessageBubble key={m.id} message={m} />
      ))}
      
      {isLoading && <LoadingIndicator />}
    </div>
  );
}

打字机效果优化

默认的流式输出已经是逐字显示,但可以进一步优化:

tsx
function StreamingMessage({ content }: { content: string }) {
  return (
    <div className="relative">
      <span>{content}</span>
      <span className="inline-block w-2 h-4 bg-gray-800 animate-pulse ml-1" />
    </div>
  );
}

错误处理与重试

tsx
function ChatWithErrorHandling() {
  const { messages, error, reload, isLoading } = useChat({
    onError: (error) => {
      // 可以接入错误监控服务
      console.error('Chat error:', error);
    },
  });

  if (error) {
    return (
      <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
        <div className="flex items-center gap-2 text-red-700">
          <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
            <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
          </svg>
          <span>出错了:{error.message}</span>
        </div>
        <button
          onClick={() => reload()}
          className="mt-2 px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700"
        >
          重试
        </button>
      </div>
    );
  }

  // ... 正常渲染
}

取消生成

tsx
function ChatWithStop() {
  const { isLoading, stop, handleSubmit, ... } = useChat();

  return (
    <form onSubmit={handleSubmit}>
      {/* ... 输入框 */}
      
      {isLoading ? (
        <button
          type="button"
          onClick={stop}
          className="px-4 py-2 bg-red-500 text-white rounded"
        >
          停止生成
        </button>
      ) : (
        <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
          发送
        </button>
      )}
    </form>
  );
}

空状态引导

tsx
function EmptyState() {
  const suggestions = [
    '帮我写一段自我介绍',
    '解释什么是 React Hooks',
    '给我讲个笑话',
  ];

  return (
    <div className="flex flex-col items-center justify-center h-full text-center p-8">
      <h2 className="text-2xl font-bold mb-4">有什么可以帮你的?</h2>
      <p className="text-gray-500 mb-6">试试以下问题:</p>
      <div className="flex flex-wrap gap-2 justify-center">
        {suggestions.map((s) => (
          <button
            key={s}
            onClick={() => append({ role: 'user', content: s })}
            className="px-4 py-2 bg-gray-100 rounded-full hover:bg-gray-200"
          >
            {s}
          </button>
        ))}
      </div>
    </div>
  );
}

function Chat() {
  const { messages, append, ... } = useChat();

  return (
    <div>
      {messages.length === 0 ? (
        <EmptyState />
      ) : (
        // ... 消息列表
      )}
    </div>
  );
}

AI 协作指南

  • 核心意图:让 AI 帮你设计完善的交互状态。
  • 需求定义公式"请帮我实现一个 AI 聊天界面的完整状态处理,包括加载动画、错误提示、重试按钮和停止生成功能。"
  • 关键术语isLoadingerrorreloadstop空状态 (empty state)

避坑指南

  • 加载状态要有视觉反馈:不要让用户猜测是否在工作。
  • 错误信息要可理解:将技术错误转换为用户友好的提示。
  • 提供逃生出口:用户应该能够取消长时间运行的操作。