axios 请求流式SSE返回接口 (实现打字机输出)

<template>
  <div class="chat-container">
    <div class="chat-messages">
      <div
        v-for="(msg, index) in messages"
        :key="index"
        class="message"
        :class="{ 'ai-message': msg.isAI }"
      >
        <div class="message-content">{{ msg.content }}</div>
      </div>
    </div>
    <div class="input-area">
      <input
        v-model="inputMessage"
        @keyup.enter="sendMessage"
        placeholder="输入消息..."
        :disabled="isLoading"
      />
      <button
        @click="sendMessage"
        :disabled="isLoading || !inputMessage.trim()"
      >
        {{ isLoading ? "发送中..." : "发送" }}
      </button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messages: [],
      inputMessage: "",
      isLoading: false,
      conversationId: "",
      user: "abc-123",
    };
  },
  methods: {
    async sendMessage() {
      if (!this.inputMessage.trim() || this.isLoading) return;

      // 添加用户消息
      const userMessage = {
        content: this.inputMessage,
        isAI: false,
        timestamp: new Date().getTime(),
      };
      this.messages.push(userMessage);

      // 添加初始AI消息占位
      const aiMessage = {
        content: "",
        isAI: true,
        timestamp: new Date().getTime(),
      };
      this.messages.push(aiMessage);

      // 清空输入框
      this.inputMessage = "";
      this.isLoading = true;

      try {
        const response = await fetch(
          "https://test-api-dify.asdadad/v1/chat-messages",
          {
            method: "POST",
            headers: {
              Authorization: "Bearer asaa",
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              inputs: {},
              query: userMessage.content,
              response_mode: "streaming",
              conversation_id: this.conversationId,
              user: this.user,
            }),
          }
        );

        if (!response.ok) {
          throw new Error(`请求失败: ${response.status}`);
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          buffer += decoder.decode(value, { stream: true });

          // 处理SSE格式数据
          let eventEndIndex;
          while ((eventEndIndex = buffer.indexOf("\n\n")) !== -1) {
            const eventData = buffer.slice(0, eventEndIndex);
            buffer = buffer.slice(eventEndIndex + 2);

            const lines = eventData.split("\n");
            for (const line of lines) {
              if (line.startsWith("data:")) {
                const jsonData = JSON.parse(line.slice(5).trim());
                if (jsonData.event === "message") {
                  // 更新对话ID
                  if (jsonData.conversation_id) {
                    this.conversationId = jsonData.conversation_id;
                  }
                  // 累加内容
                  if (jsonData.answer) {
                    aiMessage.content += jsonData.answer;
                  }
                } else if (jsonData.event === "error") {
                  throw new Error(jsonData.error);
                }
              }
            }
          }
        }
      } catch (error) {
        console.error("请求出错:", error);
        aiMessage.content = "请求出错: " + error.message;
      } finally {
        this.isLoading = false;
      }
    },
  },
};
</script>

<style>
.chat-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.chat-messages {
  height: 500px;
  border: 1px solid #ccc;
  margin-bottom: 20px;
  padding: 10px;
  overflow-y: auto;
}

.message {
  margin: 10px 0;
  padding: 8px 12px;
  border-radius: 15px;
  max-width: 70%;
}

.message-content {
  word-wrap: break-word;
  white-space: pre-wrap;
}

.ai-message {
  background-color: #f1f1f1;
  margin-right: auto;
}

.message:not(.ai-message) {
  background-color: #007bff;
  color: white;
  margin-left: auto;
}

.input-area {
  display: flex;
  gap: 10px;
}

input {
  flex: 1;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  padding: 8px 16px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}
</style>

 

posted @ 2025-02-26 01:08  凯宾斯基  阅读(1236)  评论(0)    收藏  举报