跳至正文

React 与 Vue 聊天界面 ​

默认使用各自入口的 ChatPanel,提供会话列表、历史、发送、停止、确认、错误和恢复。客户端只配置业务后台地址与业务认证;组件卸载不取消运行。保持 client 实例稳定,切换主体时重新挂载。

默认组件 ​

tsx
import {createChatClient} from '@sparktide/frontend-sdk';
import {ChatPanel} from '@sparktide/frontend-sdk/react';
const client=createChatClient({baseUrl:'/api/ai',token:()=>businessToken});
<ChatPanel client={client} />
ts
import {defineComponent,h} from 'vue';
import {createChatClient} from '@sparktide/frontend-sdk';
import {ChatPanel} from '@sparktide/frontend-sdk/vue';
const client=createChatClient({baseUrl:'/api/ai',token:()=>businessToken});
export default defineComponent({setup:()=>()=>h(ChatPanel,{client})});

自定义状态层 ​

下面保留已有 ChatSession 与 UI Renderer 的组合例子,用于定制布局。两种适配共享同一个 ChatSession。示例接收由业务层创建的 session,不包含管理员凭据;同源网关和用户 token 函数按前端 SDK配置。

在页面生命周期内保持 session 实例稳定,例如在父组件 useMemo 中创建。下面组件支持发送、状态、错误、审批和取消:

tsx
import { useEffect, useState } from 'react'
import { ChatSession } from '@sparktide/frontend-sdk'
import { useChatSession } from '@sparktide/frontend-sdk/react'

export function AgentChat({ session }: { session: ChatSession }) {
  const state = useChatSession(session)
  const [message, setMessage] = useState('')
  const [busy, setBusy] = useState(false)
  const [error, setError] = useState('')
  useEffect(() => () => session.disconnect(), [session])
  async function act(work: () => Promise<void>) {
    try { setError(''); await work(); return true }
    catch (e) { setError(e instanceof Error ? e.message : String(e)); return false }
  }
  async function send() {
    if (!message.trim() || busy) return
    setBusy(true)
    try { if (await act(() => session.send(message))) setMessage('') }
    finally { setBusy(false) }
  }
  return <section aria-label="智能助手">
    <p role="status">{state.status}</p>
    <p style={{ whiteSpace: 'pre-wrap' }}>{state.text}</p>
    {(error || state.error) && <p role="alert">{error || state.error}</p>}
    {state.confirmations.map(c => <article key={String(c.confirmationId)}>
      <h3>确认执行 {String(c.toolId)}</h3>
      <pre>{JSON.stringify(c.arguments, null, 2)}</pre>
      <button onClick={() => void act(() => session.confirm(String(c.confirmationId), 'approve'))}>批准</button>
      <button onClick={() => void act(() => session.confirm(String(c.confirmationId), 'reject'))}>拒绝</button>
    </article>)}
    <form onSubmit={e => { e.preventDefault(); void send() }}>
      <label>消息<textarea value={message} onChange={e => setMessage(e.target.value)} disabled={busy}/></label>
      <button disabled={busy || !message.trim()}>发送</button>
      <button type="button" disabled={!state.runId || !busy} onClick={() => void act(() => session.cancel())}>取消任务</button>
    </form>
  </section>
}
vue
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { ChatSession } from '@sparktide/frontend-sdk'
import { useChatSession } from '@sparktide/frontend-sdk/vue'

const props = defineProps<{ session: ChatSession }>()
const state = useChatSession(props.session)
const message = ref('')
const busy = ref(false)
const error = ref('')
onUnmounted(() => props.session.disconnect())
async function act(work: () => Promise<void>) {
  try { error.value = ''; await work(); return true }
  catch (e) { error.value = e instanceof Error ? e.message : String(e); return false }
}
async function send() {
  if (!message.value.trim() || busy.value) return
  busy.value = true
  try { if (await act(() => props.session.send(message.value))) message.value = '' }
  finally { busy.value = false }
}
</script>

<template>
  <section aria-label="智能助手">
    <p role="status">{{ state.status }}</p>
    <p style="white-space: pre-wrap">{{ state.text }}</p>
    <p v-if="error || state.error" role="alert">{{ error || state.error }}</p>
    <article v-for="c in state.confirmations" :key="String(c.confirmationId)">
      <h3>确认执行 {{ c.toolId }}</h3>
      <pre>{{ JSON.stringify(c.arguments, null, 2) }}</pre>
      <button @click="act(() => props.session.confirm(String(c.confirmationId), 'approve'))">批准</button>
      <button @click="act(() => props.session.confirm(String(c.confirmationId), 'reject'))">拒绝</button>
    </article>
    <form @submit.prevent="send">
      <label>消息<textarea v-model="message" :disabled="busy" /></label>
      <button :disabled="busy || !message.trim()">发送</button>
      <button type="button" :disabled="!state.runId || !busy" @click="act(() => props.session.cancel())">取消任务</button>
    </form>
  </section>
</template>

React: 这是界面接入模式;业务设计系统可替换展示组件。审批按钮保持可交互,即使 send 正在等待终态。

Vue: Vue 适配自动解除状态订阅,页面仍应调用 session.disconnect 关闭网络订阅。若多个组件共享 session,只由拥有其生命周期的页面负责断开。

接入后的完善项 ​

把 citation.created 显示为可访问的引用,把 ui.created 交给组件注册表。明确区分断线和执行失败;需要恢复时保存 runId 并提供 resume 入口。用户切换账号或租户时创建对应新客户端,避免继续显示上一身份的数据。

回答引用与来源 ​

引用核验与当前授权预览提供编号核验、来源片段、所有权与实时撤权、SDK 方法及前端状态使用方法。

适用版本:0.1 发布线 · 最近核对:2026-10-09 · SparkTide 产品文档

汇聚智能,驱动涌现。