Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Feature/assistants UI (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
multipletwigs authored and tobySolutions committed Jun 17, 2024
1 parent 426cb65 commit 4c26e86
Show file tree
Hide file tree
Showing 19 changed files with 739 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/akeru-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@elysiajs/bearer": "^1.0.2",
"@elysiajs/cors": "^1.0.2",
"@elysiajs/stream": "^1.0.2",
"@elysiajs/swagger": "^1.0.4",
"dayjs": "^1.11.10",
Expand Down
51 changes: 51 additions & 0 deletions packages/akeru-server/scripts/createHumanUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
createApiToken,
createHumanUser,
} from "@/core/application/controllers/userController";
import { createAssistant } from "@/core/application/services/assistantService";
import { createThread } from "@/core/application/services/threadService";
import { ulid } from "ulid";

async function createHumanUserWithApiKey() {
const userData = {
name: "Mr. Akeru Human User",
email: "[email protected]",
};

// Creating the human user for id
const userId = await createHumanUser(userData);
if (!userId) {
console.error("Failed to create super admin user");
return;
}

// creating user bearer token
const apiKey = await createApiToken(userId);
if (!apiKey) {
console.error("Failed to create API key for super admin user");
return;
}

// creating assistants associated with the user, creating three assistants
const { id: id_1 } = await createAssistant({ userId, id: ulid(), model: "gpt-4", name: "Homework assistant", fileIds: [], tools: [], instruction: "You are a homework helper. Do not answer irrelevant questions." });
const { id: id_2 } = await createAssistant({ userId, id: ulid(), model: "gpt-4", name: "Tropical Plant Expert", fileIds: [], tools: [], instruction: "You specialise in tropical plants. Do not answer irrelevant questions." });
const { id: id_3 } = await createAssistant({ userId, id: ulid(), model: "gpt-4", name: "Marine Biologist", fileIds: [], tools: [], instruction: "You specialise in Marine Biology. Do not answer irrelevant questions." });

// creating a few threads associated with said assistants
await createThread({ id: ulid(), createdBy: userId, participants: [], messageIds: [], name: 'Homework on Science' })
await createThread({ id: ulid(), createdBy: userId, participants: [], messageIds: [], name: 'What are fishes?' })
await createThread({ id: ulid(), createdBy: userId, participants: [], messageIds: [], name: 'Why do plants commonly die?' })

console.log(`human user created with ID: ${userId}`);
console.log(`API key for human user: ${apiKey}`);

console.log(`assistant_id 1 created with ID: ${id_1}`);
console.log(`assistant_id 2 created with ID: ${id_2}`);
console.log(`assistant_id 3 created with ID: ${id_3}`);

process.exit();
}

createHumanUserWithApiKey().catch((err) => {
console.error("Error creating super admin user with API key:", err);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
assignRole,
createUser,
} from "@/core/application/services/userService";
import { createAssistant } from "@/core/application/services/assistantService";
import {
createAssistant,
getAssistantData,
} from "@/core/application/services/assistantService";
import { AuthMiddleware } from "../../middlewares/authorizationMiddleware";
import bearer from "@elysiajs/bearer";

Expand Down Expand Up @@ -35,7 +38,7 @@ assistants.post(
name,
fileIds: [],
tools: [],
instruction: body.instruction
instruction: body.instruction,
});

return {
Expand All @@ -48,8 +51,36 @@ assistants.post(
body: t.Object({
name: t.String(),
model: t.Literal("gpt-4"), // add more models here
instruction: t.String()
instruction: t.String(),
}),
beforeHandle: AuthMiddleware(["create_assistant", "*"]),
}
);

/**
* Based on the query for the endpoint fetch the appropriate details about the users assistants
*/
assistants.get(
"/assistant",
async ({ query, bearer }) => {
console.info("hit")
// only support ALL query for now
const decodedToken = await parseToken(bearer!);
if (!decodedToken) return;

const { userId } = decodedToken;

const assistantData = await getAssistantData({
query: query.query,
userId: userId,
});

return assistantData;
},
{
query: t.Object({
query: t.Literal("ALL"),
}),
beforeHandle: AuthMiddleware(["view_assistants", "*"]),
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ export const threads = new Elysia<"/thread">().use(bearer());

threads.post(
"/thread",
async ({ bearer }) => {
async ({ bearer, body }) => {
const decodedToken = await parseToken(bearer!);

if (decodedToken) {
const { userId } = decodedToken;
// // Create a new thread
const threadId = await createThread({
name: body.thread_name,
id: ulid(), // Generate a unique ID for the thread
createdBy: userId,
participants: [], // Initialize the participants list
Expand All @@ -44,6 +45,9 @@ threads.post(
}
},
{
body: t.Object({
thread_name: t.String()
}),
beforeHandle: AuthMiddleware(["create_thread", "*"]),
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe.only("threadController", async () => {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
thread_name: "Akeru thread",
}),
});

const response = await app.handle(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,38 @@ export async function createSuperAdmin(
}
}

/**
* Creates a new user with a random ID and assigns them the "super admin" role.
* Also creates the user in Redis.
* WARNING: This function should only be used in seeding scripts.
* @param {Object} userData - The data of the user to create.
* @returns {Promise<string>} A promise that resolves to the ID of the new user if the user was created and assigned the role successfully, or null otherwise.
*/
export async function createHumanUser(
userData: Object
): Promise<string | null> {
try {
const userId = ulid();
const userCreated = await createUser(userId, userData);
if (!userCreated) {
return null;
}

const roleAssigned = await assignRole(userId, "user");
if (!roleAssigned) {
return null;
}

// Create the user in Redis
await redis.set(`user:${userId}`, JSON.stringify(userData));

return userId;
} catch (err) {
console.error("Error creating super admin:", err);
return null;
}
}

/**
* Creates a new API token for a user.
* WARNING: This function should only be used in seeding scripts.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Assistant } from "@/core/domain/assistant";
import { User } from "@/core/domain/user";
import { redis } from "@/infrastructure/adapters/redisAdapter";

/**
Expand All @@ -15,7 +16,10 @@ export async function createAssistant(args: Assistant & { userId: string }) {
const pipeline = redis.pipeline();

// Store the assistant data
pipeline.set(`assistant:${id}`, JSON.stringify({ tools, model, name, instruction }));
pipeline.set(
`assistant:${id}`,
JSON.stringify({ tools, model, name, instruction, id })
);

// Store the relationship between the assistant and the user
pipeline.sadd(`user:${userId}:assistants`, id);
Expand All @@ -31,11 +35,30 @@ export async function createAssistant(args: Assistant & { userId: string }) {
}

// Parse the assistant data from JSON
return JSON.parse(assistantData);
return JSON.parse(assistantData) as Assistant;
}

export async function getAssistantData(assistant_id: Assistant["id"]) {
const assistantData = await redis.get(`assistant:${assistant_id}`);
export async function getAssistantData(args: {
query: Assistant["id"] | "ALL";
userId: User["id"];
}) {
const { query, userId } = args;

// if query is ALL then fetch all relevant assistant data
if (query === "ALL") {
const allAssistantData = await redis.smembers(`user:${userId}:assistants`);
return Promise.all(
allAssistantData.map(async (assistantId) => {
const assistantData = await redis.get(`assistant:${assistantId}`);
if (!assistantData) {
throw new Error("Failed to get assistant");
}
return JSON.parse(assistantData) as Assistant;
})
);
}

const assistantData = await redis.get(`user:${userId}:assistant:${query}`);
if (!assistantData) {
throw new Error("Failed to get assistant");
}
Expand Down
1 change: 1 addition & 0 deletions packages/akeru-server/src/core/domain/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const roles: Record<Role, { permissions: Permission[] }> = {
"view_own_threads",
"create_message_in_own_thread",
"create_assistant",
"view_assistants"
],
},
assistant: {
Expand Down
1 change: 1 addition & 0 deletions packages/akeru-server/src/core/domain/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { User } from "./user";

export type Thread = {
id: string; // The ID of the thread
name: string;
createdBy: string; // The ID of the user who created the thread
startDate?: Date; // The start date of the thread
participants: User['id'][]; // The IDs of the users participating in the thread
Expand Down
2 changes: 2 additions & 0 deletions packages/akeru-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Elysia } from "elysia";
import { threads } from "@/core/application/controllers/thread/threadController";
import { assistants } from "@/core/application/controllers/assistant/assistantController";
import { users } from "./core/application/controllers/user/userController";
import cors from "@elysiajs/cors";

export const name = "Akeru";

Expand All @@ -17,6 +18,7 @@ export const healthCheck = async () => {
};

export const app = new Elysia()
.use(cors())
.use(assistants)
.use(threads)
.use(users)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export const redisConfig = {
? { password: process.env.REDIS_PASSWORD }
: {}), // Redis password
db: process.env.REDIS_DB ? parseInt(process.env.REDIS_DB) : 0, // Redis DB
tls: {},
// tls: {},
};
7 changes: 6 additions & 1 deletion packages/app-akeru/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-menubar": "^1.0.4",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"axios": "^1.7.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"geist": "^1.3.0",
"lucide-react": "^0.378.0",
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"sonner": "^1.4.41",
"swr": "^2.2.5",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
Loading

0 comments on commit 4c26e86

Please sign in to comment.