Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useAssistant with Vue js #803

Closed
waynermaia opened this issue Nov 29, 2023 · 4 comments
Closed

useAssistant with Vue js #803

waynermaia opened this issue Nov 29, 2023 · 4 comments

Comments

@waynermaia
Copy link

waynermaia commented Nov 29, 2023

Feature Description

Similar to useChat, this implementation includes additional features that leverage the OpenAI assistant capabilities. If feasible, it enables data sharing through Streams.

Use Case

Using plug and play to management features of assistent, threads, messages, tools, etc.

Additional context

I use NuxtJs also.

@MaxLeiter
Copy link
Member

We definitely intend to support useAssistant in all the frameworks. PRs helping us achieve that would be appreciated!

@dosstx
Copy link
Contributor

dosstx commented May 21, 2024

I converted the React version of useAssistant to Vue. Can someone test this useAssistant composable for Vue ? I am unsure how to apply it to the AI SDK for testing.

import { isAbortError } from '@ai-sdk/provider-utils';
import { generateId } from '../shared/generate-id';
import { readDataStream } from '../shared/read-data-stream';
import {
  AssistantStatus,
  CreateMessage,
  Message,
  UseAssistantOptions,
} from '../shared/types';

export function useAssistant({
  api,
  threadId: threadIdParam,
  credentials,
  headers,
  body,
  onError,
}: UseAssistantOptions) {
  const state = reactive({
    messages: [] as Message[],
    input: '',
    threadId: undefined as string | undefined,
    status: 'awaiting_message' as AssistantStatus,
    error: undefined as unknown,
  });

  const abortControllerRef = ref<AbortController | null>(null);

  const handleInputChange = (event: Event) => {
    const target = event.target as HTMLInputElement | HTMLTextAreaElement;
    state.input = target.value;
  };

  const stop = () => {
    if (abortControllerRef.value) {
      abortControllerRef.value.abort();
      abortControllerRef.value = null;
    }
  };

  const append = async (
    message: Message | CreateMessage,
    requestOptions?: {
      data?: Record<string, string>;
    },
  ) => {
    state.status = 'in_progress';

    state.messages.push({
      ...message,
      id: message.id ?? generateId(),
    });

    state.input = '';

    const abortController = new AbortController();

    try {
      abortControllerRef.value = abortController;

      const result = await fetch(api, {
        method: 'POST',
        credentials,
        signal: abortController.signal,
        headers: { 'Content-Type': 'application/json', ...headers },
        body: JSON.stringify({
          ...body,
          threadId: threadIdParam ?? state.threadId ?? null,
          message: message.content,
          data: requestOptions?.data,
        }),
      });

      if (result.body == null) {
        throw new Error('The response body is empty.');
      }

      for await (const { type, value } of readDataStream(result.body.getReader())) {
        switch (type) {
          case 'assistant_message': {
            state.messages.push({
              id: value.id,
              role: value.role,
              content: value.content[0].text.value,
            });
            break;
          }

          case 'text': {
            const lastMessage = state.messages[state.messages.length - 1];
            lastMessage.content += value;
            break;
          }

          case 'data_message': {
            state.messages.push({
              id: value.id ?? generateId(),
              role: 'data',
              content: '',
              data: value.data,
            });
            break;
          }

          case 'assistant_control_data': {
            state.threadId = value.threadId;
            const lastMessage = state.messages[state.messages.length - 1];
            lastMessage.id = value.messageId;
            break;
          }

          case 'error': {
            state.error = new Error(value);
            break;
          }
        }
      }
    } catch (error) {
      if (isAbortError(error) && abortController.signal.aborted) {
        abortControllerRef.value = null;
        return;
      }

      if (onError && error instanceof Error) {
        onError(error);
      }

      state.error = error as Error;
    } finally {
      abortControllerRef.value = null;
      state.status = 'awaiting_message';
    }
  };

  const submitMessage = async (
    event?: Event,
    requestOptions?: {
      data?: Record<string, string>;
    },
  ) => {
    event?.preventDefault?.();

    if (state.input === '') {
      return;
    }

    append({ role: 'user', content: state.input }, requestOptions);
  };

  onUnmounted(() => {
    if (abortControllerRef.value) {
      abortControllerRef.value.abort();
    }
  });

  return {
    ...toRefs(state),
    handleInputChange,
    stop,
    append,
    submitMessage,
  };
}

@dosstx
Copy link
Contributor

dosstx commented May 22, 2024

Added a pull request...

@lgrammel
Copy link
Collaborator

duplicates #1678

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants