Skip to content

Commit

Permalink
Merge branch 'main' into fix/move-modules
Browse files Browse the repository at this point in the history
  • Loading branch information
fjsj authored Jun 17, 2024
2 parents ce8fe42 + e7c1061 commit f59ce7a
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 4 deletions.
11 changes: 7 additions & 4 deletions example/assets/js/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Markdown from "react-markdown";
import {
ThreadMessagesSchemaOut,
ThreadSchema,
useAssistantList,
useAssistant,
useMessageList,
useThreadList,
} from "django-ai-assistant-client";
Expand Down Expand Up @@ -132,6 +132,8 @@ export function Chat({ assistantId }: { assistantId: string }) {
loadingDeleteMessage,
} = useMessageList({ threadId: activeThread?.id });

const { fetchAssistant, assistant } = useAssistant({ assistantId });

const loadingMessages =
loadingFetchMessages || loadingCreateMessage || loadingDeleteMessage;
const isThreadSelected = Boolean(activeThread);
Expand All @@ -153,10 +155,11 @@ export function Chat({ assistantId }: { assistantId: string }) {
[scrollViewport]
);

// Load threads when component mounts:
// Load threads and assistant details when component mounts:
useEffect(() => {
fetchThreads();
}, [fetchThreads]);
fetchAssistant();
}, [fetchThreads, fetchAssistant]);

// Load messages when threadId changes:
useEffect(() => {
Expand Down Expand Up @@ -192,7 +195,7 @@ export function Chat({ assistantId }: { assistantId: string }) {
<Container className={classes.chatContainer}>
<Stack className={classes.chat}>
<Title mt="md" order={2}>
Chat: {assistantId}
Chat: {assistant?.name || "Loading…"}
</Title>
<ScrollArea
pos="relative"
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./useAssistantList";
export * from "./useAssistant";
export * from "./useMessageList";
export * from "./useThreadList";
48 changes: 48 additions & 0 deletions frontend/src/hooks/useAssistant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useCallback } from "react";
import { useState } from "react";
import {
AssistantSchema,
djangoAiAssistantViewsGetAssistant,
} from "../client";

/**
* React hook to manage an Assistant.
*/
export function useAssistant({ assistantId }: {
assistantId: string;
}) {
const [assistant, setAssistant] = useState<AssistantSchema | null>(null);
const [loadingFetchAssistant, setLoadingFetchAssistant] =
useState<boolean>(false);

/**
* Fetches an AI assistant.
*
* @returns A promise that resolves with the fetched AI assistant.
*/
const fetchAssistant = useCallback(async (): Promise<AssistantSchema> => {
try {
setLoadingFetchAssistant(true);
const fetchedAssistant = await djangoAiAssistantViewsGetAssistant({ assistantId });
setAssistant(fetchedAssistant);
return fetchedAssistant;
} finally {
setLoadingFetchAssistant(false);
}
}, [assistantId]);

return {
/**
* Function to fetch an AI assistant from the server.
*/
fetchAssistant,
/**
* Fetched AI assistant.
*/
assistant,
/**
* Loading state of the fetch operation.
*/
loadingFetchAssistant,
};
}
66 changes: 66 additions & 0 deletions frontend/tests/useAssistant.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { act, renderHook } from "@testing-library/react";
import { useAssistant } from "../src/hooks";
import { djangoAiAssistantViewsGetAssistant } from "../src/client";

jest.mock("../src/client", () => ({
djangoAiAssistantViewsGetAssistant: jest
.fn()
.mockImplementation(() => Promise.resolve()),
}));

describe("useAssistant", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("should initialize with no assistant and loading false", () => {
const { result } = renderHook(() => useAssistant({ assistantId: 'weather_assistant' }));

expect(result.current.assistant).toBeNull();
expect(result.current.loadingFetchAssistant).toBe(false);
});

describe("fetchAssistant", () => {
it("should fetch assistant and update state correctly", async () => {
const mockAssistants = [
{ id: 'weather_assistant', name: "Assistant 1" },
{ id: 'movies_assistant', name: "Assistant 2" },
];
(djangoAiAssistantViewsGetAssistant as jest.Mock).mockResolvedValue(
mockAssistants
);

const { result } = renderHook(() => useAssistant({ assistantId: 'weather_assistant' }));

expect(result.current.assistant).toBeNull();
expect(result.current.loadingFetchAssistant).toBe(false);

await act(async () => {
await result.current.fetchAssistant();
});

expect(result.current.assistant).toEqual(mockAssistants);
expect(result.current.loadingFetchAssistant).toBe(false);
});

it("should set loading to false if fetch fails", async () => {
(djangoAiAssistantViewsGetAssistant as jest.Mock).mockRejectedValue(
new Error("Failed to fetch")
);

const { result } = renderHook(() => useAssistant({ assistantId: 'non_existent_assistant' }));

expect(result.current.assistant).toBeNull();
expect(result.current.loadingFetchAssistant).toBe(false);

await expect(async () => {
await act(async () => {
await result.current.fetchAssistant();
});
}).rejects.toThrow("Failed to fetch");

expect(result.current.assistant).toBeNull();
expect(result.current.loadingFetchAssistant).toBe(false);
});
});
});

0 comments on commit f59ce7a

Please sign in to comment.