mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-08-19 11:50:35 +08:00
Merging from main
This commit is contained in:
@@ -20,7 +20,8 @@ export const post = (url: string, { arg: data }) => api.post(url, data).then((re
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
throw new OasstError(error.message ?? error, error.error_code);
|
||||
const err = error?.response?.data;
|
||||
throw new OasstError(err?.message ?? error, err?.errorCode, error?.response?.httpStatusCode || -1);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ const withoutRole = (role: Role, handler: (arg0: NextApiRequest, arg1: NextApiRe
|
||||
* Wraps any API Route handler and verifies that the user has the appropriate
|
||||
* role before running the handler. Returns a 403 otherwise.
|
||||
*/
|
||||
const withRole = (role: Role, handler: (arg0: NextApiRequest, arg1: NextApiResponse) => void) => {
|
||||
const withRole = (role: Role, handler: (arg0: NextApiRequest, arg1: NextApiResponse, token: JWT) => void) => {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const token = await getToken({ req });
|
||||
if (!token || token.role !== role) {
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
return handler(req, res);
|
||||
return handler(req, res, token);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
useCreateAssistantReply,
|
||||
useCreateInitialPrompt,
|
||||
useCreatePrompterReply,
|
||||
} from "src/hooks/tasks/useCreateReply";
|
||||
import { useGenericTaskAPI } from "src/hooks/tasks/useGenericTaskAPI";
|
||||
import {
|
||||
useLabelAssistantReplyTask,
|
||||
useLabelInitialPromptTask,
|
||||
useLabelPrompterReplyTask,
|
||||
} from "src/hooks/tasks/useLabelingTask";
|
||||
import {
|
||||
useRankAssistantRepliesTask,
|
||||
useRankInitialPromptsTask,
|
||||
useRankPrompterRepliesTask,
|
||||
} from "src/hooks/tasks/useRankReplies";
|
||||
import { TaskApiHooks } from "src/types/Hooks";
|
||||
import { TaskType } from "src/types/Task";
|
||||
|
||||
export const ERROR_CODES = {
|
||||
TASK_REQUESTED_TYPE_NOT_AVAILABLE: 1006,
|
||||
TASK_INVALID_REQUEST_TYPE: 1000,
|
||||
TASK_ACK_FAILED: 1001,
|
||||
TASK_NACK_FAILED: 1002,
|
||||
TASK_INVALID_RESPONSE_TYPE: 1003,
|
||||
TASK_INTERACTION_REQUEST_FAILED: 1004,
|
||||
TASK_GENERATION_FAILED: 1005,
|
||||
TASK_AVAILABILITY_QUERY_FAILED: 1007,
|
||||
TASK_MESSAGE_TOO_LONG: 1008,
|
||||
};
|
||||
|
||||
export const taskApiHooks: TaskApiHooks = {
|
||||
[TaskType.random]: useGenericTaskAPI,
|
||||
[TaskType.assistant_reply]: useCreateAssistantReply,
|
||||
[TaskType.initial_prompt]: useCreateInitialPrompt,
|
||||
[TaskType.label_assistant_reply]: useLabelAssistantReplyTask,
|
||||
[TaskType.label_initial_prompt]: useLabelInitialPromptTask,
|
||||
[TaskType.label_prompter_reply]: useLabelPrompterReplyTask,
|
||||
[TaskType.prompter_reply]: useCreatePrompterReply,
|
||||
[TaskType.rank_assistant_replies]: useRankAssistantRepliesTask,
|
||||
[TaskType.rank_initial_prompts]: useRankInitialPromptsTask,
|
||||
[TaskType.rank_prompter_replies]: useRankPrompterRepliesTask,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const getTypeSafei18nKey = (key: string) => key as unknown as TemplateStringsArray;
|
||||
+151
-124
@@ -1,14 +1,14 @@
|
||||
import type { Message } from "src/types/Conversation";
|
||||
import type { EmojiOp, Message } from "src/types/Conversation";
|
||||
import { LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
import type { AvailableTasks } from "src/types/Task";
|
||||
import type { BackendUser, BackendUserCore } from "src/types/Users";
|
||||
import type { BackendUser, BackendUserCore, FetchUsersParams, FetchUsersResponse } from "src/types/Users";
|
||||
|
||||
export class OasstError {
|
||||
message: string;
|
||||
errorCode: number;
|
||||
httpStatusCode: number;
|
||||
|
||||
constructor(message: string, errorCode: number, httpStatusCode?: number) {
|
||||
constructor(message: string, errorCode: number, httpStatusCode: number) {
|
||||
this.message = message;
|
||||
this.errorCode = errorCode;
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
@@ -18,110 +18,35 @@ export class OasstError {
|
||||
export class OasstApiClient {
|
||||
oasstApiUrl: string;
|
||||
oasstApiKey: string;
|
||||
userHeaders: Record<string, string> = {};
|
||||
|
||||
constructor(oasstApiUrl: string, oasstApiKey: string) {
|
||||
constructor(oasstApiUrl: string, oasstApiKey: string, user?: BackendUserCore) {
|
||||
this.oasstApiUrl = oasstApiUrl;
|
||||
this.oasstApiKey = oasstApiKey;
|
||||
if (user) {
|
||||
this.userHeaders = {
|
||||
"X-OASST-USER": `${user.auth_method}:${user.id}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async post(path: string, body: any): Promise<any> {
|
||||
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": this.oasstApiKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (resp.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (resp.status >= 300) {
|
||||
const errorText = await resp.text();
|
||||
let error: any;
|
||||
try {
|
||||
error = JSON.parse(errorText);
|
||||
} catch (e) {
|
||||
throw new OasstError(errorText, 0, resp.status);
|
||||
}
|
||||
throw new OasstError(error.message ?? error, error.error_code, resp.status);
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
private async put(path: string): Promise<any> {
|
||||
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"X-API-Key": this.oasstApiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (resp.status >= 300) {
|
||||
const errorText = await resp.text();
|
||||
let error: any;
|
||||
try {
|
||||
error = JSON.parse(errorText);
|
||||
} catch (e) {
|
||||
throw new OasstError(errorText, 0, resp.status);
|
||||
}
|
||||
throw new OasstError(error.message ?? error, error.error_code, resp.status);
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
private async get(path: string): Promise<any> {
|
||||
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-API-Key": this.oasstApiKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (resp.status >= 300) {
|
||||
const errorText = await resp.text();
|
||||
let error: any;
|
||||
try {
|
||||
error = JSON.parse(errorText);
|
||||
} catch (e) {
|
||||
throw new OasstError(errorText, 0, resp.status);
|
||||
}
|
||||
throw new OasstError(error.message ?? error, error.error_code, resp.status);
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
// TODO return a strongly typed Task?
|
||||
// This method is used to store a task in RegisteredTask.task.
|
||||
// This is a raw Json type, so we can't use it to strongly type the task.
|
||||
async fetchTask(taskType: string, user: BackendUserCore): Promise<any> {
|
||||
async fetchTask(taskType: string, user: BackendUserCore, lang: string): Promise<any> {
|
||||
return this.post("/api/v1/tasks/", {
|
||||
type: taskType,
|
||||
user,
|
||||
lang,
|
||||
});
|
||||
}
|
||||
|
||||
async ackTask(taskId: string, messageId: string): Promise<void> {
|
||||
async ackTask(taskId: string, messageId: string): Promise<null> {
|
||||
return this.post(`/api/v1/tasks/${taskId}/ack`, {
|
||||
message_id: messageId,
|
||||
});
|
||||
}
|
||||
|
||||
async nackTask(taskId: string, reason: string): Promise<void> {
|
||||
async nackTask(taskId: string, reason: string): Promise<null> {
|
||||
return this.post(`/api/v1/tasks/${taskId}/nack`, {
|
||||
reason,
|
||||
});
|
||||
@@ -136,7 +61,8 @@ export class OasstApiClient {
|
||||
messageId: string,
|
||||
userMessageId: string,
|
||||
content: object,
|
||||
user: BackendUserCore
|
||||
user: BackendUserCore,
|
||||
lang: string
|
||||
): Promise<any> {
|
||||
return this.post("/api/v1/tasks/interaction", {
|
||||
type: updateType,
|
||||
@@ -144,6 +70,7 @@ export class OasstApiClient {
|
||||
task_id: taskId,
|
||||
message_id: messageId,
|
||||
user_message_id: userMessageId,
|
||||
lang,
|
||||
...content,
|
||||
});
|
||||
}
|
||||
@@ -151,8 +78,29 @@ export class OasstApiClient {
|
||||
/**
|
||||
* Returns the tasks availability information for given `user`.
|
||||
*/
|
||||
async fetch_tasks_availability(user: object): Promise<any> {
|
||||
return this.post("/api/v1/tasks/availability", user);
|
||||
async fetch_tasks_availability(user: object): Promise<AvailableTasks | null> {
|
||||
return this.post<AvailableTasks>("/api/v1/tasks/availability", user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `Message`s associated with `user_id` in the backend.
|
||||
*/
|
||||
async fetch_message(message_id: string, user: BackendUserCore): Promise<Message> {
|
||||
return this.get<Message>(`/api/v1/messages/${message_id}?username=${user.id}&auth_method=${user.auth_method}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a report about a message
|
||||
*/
|
||||
async send_report(message_id: string, user: BackendUserCore, text: string) {
|
||||
return this.post("/api/v1/text_labels", {
|
||||
type: "text_labels",
|
||||
message_id,
|
||||
labels: [], // Not yet implemented
|
||||
text,
|
||||
is_report: true,
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,46 +120,41 @@ export class OasstApiClient {
|
||||
/**
|
||||
* Returns the `BackendUser` associated with `user_id`
|
||||
*/
|
||||
async fetch_user(user_id: string): Promise<BackendUser> {
|
||||
async fetch_user(user_id: string): Promise<BackendUser | null> {
|
||||
return this.get(`/api/v1/users/${user_id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of `BackendUser`s stored by the backend.
|
||||
*
|
||||
* @param {number} max_count - The maximum number of users to fetch.
|
||||
* @param {string} cursor - The user's `display_name` to use when paginating.
|
||||
* @param {boolean} isForward - If true and `cursor` is not empty, pages
|
||||
* forward. If false and `cursor` is not empty, pages backwards.
|
||||
* @returns {Promise<BackendUser[]>} A Promise that returns an array of `BackendUser` objects.
|
||||
*/
|
||||
async fetch_users(max_count: number, cursor: string, isForward: boolean): Promise<BackendUser[]> {
|
||||
const params = new URLSearchParams();
|
||||
params.append("max_count", max_count.toString());
|
||||
|
||||
// The backend API uses different query parameters depending on the
|
||||
// pagination direction but they both take the same cursor value.
|
||||
// Depending on direction, pick the right query param.
|
||||
if (cursor !== "") {
|
||||
params.append(isForward ? "gt" : "lt", cursor);
|
||||
}
|
||||
const BASE_URL = `/api/v1/frontend_users`;
|
||||
const url = `${BASE_URL}/?${params.toString()}`;
|
||||
return this.get(url);
|
||||
async fetch_users({
|
||||
direction,
|
||||
limit,
|
||||
cursor,
|
||||
searchDisplayName,
|
||||
sortKey = "display_name",
|
||||
}: FetchUsersParams): Promise<FetchUsersResponse | null> {
|
||||
return this.get<FetchUsersResponse>(`/api/v1/users/cursor`, {
|
||||
search_text: searchDisplayName,
|
||||
sort_key: sortKey,
|
||||
max_count: limit,
|
||||
after: direction === "forward" ? cursor : undefined,
|
||||
before: direction === "back" ? cursor : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `Message`s associated with `user_id` in the backend.
|
||||
*/
|
||||
async fetch_user_messages(user_id: string): Promise<Message[]> {
|
||||
return this.get(`/api/v1/users/${user_id}/messages`);
|
||||
async fetch_user_messages(user_id: string): Promise<Message[] | null> {
|
||||
return this.get<Message[]>(`/api/v1/users/${user_id}/messages`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the backend's knowledge about the `user_id`.
|
||||
*/
|
||||
async set_user_status(user_id: string, is_enabled: boolean, notes): Promise<void> {
|
||||
return this.put(`/api/v1/users/users/${user_id}?enabled=${is_enabled}¬es=${notes}`);
|
||||
async set_user_status(user_id: string, is_enabled: boolean, notes: string): Promise<void> {
|
||||
await this.put(`/api/v1/users/users/${user_id}?enabled=${is_enabled}¬es=${notes}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,18 +167,102 @@ export class OasstApiClient {
|
||||
/**
|
||||
* Returns the current leaderboard ranking.
|
||||
*/
|
||||
async fetch_leaderboard(time_frame: LeaderboardTimeFrame): Promise<LeaderboardReply> {
|
||||
return this.get(`/api/v1/leaderboards/${time_frame}`);
|
||||
async fetch_leaderboard(
|
||||
time_frame: LeaderboardTimeFrame,
|
||||
{ limit = 20 }: { limit?: number }
|
||||
): Promise<LeaderboardReply | null> {
|
||||
return this.get<LeaderboardReply>(`/api/v1/leaderboards/${time_frame}`, { max_count: limit });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the counts of all tasks (some might be zero)
|
||||
*/
|
||||
async fetch_available_tasks(user: BackendUserCore): Promise<AvailableTasks> {
|
||||
return this.post(`/api/v1/tasks/availability`, user);
|
||||
async fetch_available_tasks(user: BackendUserCore, lang: string): Promise<AvailableTasks | null> {
|
||||
return this.post<AvailableTasks>(`/api/v1/tasks/availability?lang=${lang}`, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove an emoji on a message for a user
|
||||
*/
|
||||
async set_user_message_emoji(message_id: string, user: BackendUserCore, emoji: string, op: EmojiOp): Promise<void> {
|
||||
await this.post(`/api/v1/messages/${message_id}/emoji`, {
|
||||
user,
|
||||
emoji,
|
||||
op,
|
||||
});
|
||||
}
|
||||
|
||||
private async post<T>(path: string, body: unknown) {
|
||||
return this.request<T>("POST", path, {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
private async put<T>(path: string) {
|
||||
return this.request<T>("PUT", path);
|
||||
}
|
||||
|
||||
private async get<T>(path: string, query?: Record<string, string | number | boolean | undefined>) {
|
||||
if (!query) {
|
||||
return this.request<T>("GET", path);
|
||||
}
|
||||
|
||||
const filteredQuery = Object.fromEntries(
|
||||
Object.entries(query).filter(([, value]) => value !== undefined)
|
||||
) as Record<string, string>;
|
||||
|
||||
const params = new URLSearchParams(filteredQuery).toString();
|
||||
|
||||
return this.request<T>("GET", `${path}?${params}`);
|
||||
}
|
||||
|
||||
private async request<T>(method: "GET" | "POST" | "PUT", path: string, init?: RequestInit): Promise<T | null> {
|
||||
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
|
||||
method,
|
||||
...init,
|
||||
headers: {
|
||||
...init?.headers,
|
||||
...this.userHeaders,
|
||||
"X-API-Key": this.oasstApiKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (resp.status >= 300) {
|
||||
const errorText = await resp.text();
|
||||
let error;
|
||||
try {
|
||||
error = JSON.parse(errorText);
|
||||
} catch (e) {
|
||||
throw new OasstError(errorText, 0, resp.status);
|
||||
}
|
||||
throw new OasstError(error.message ?? error, error.error_code, resp.status);
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
fetch_my_messages(user: BackendUserCore) {
|
||||
const params = new URLSearchParams({
|
||||
username: user.id,
|
||||
auth_method: user.auth_method,
|
||||
});
|
||||
return this.get<Message[]>(`/api/v1/messages?${params}`);
|
||||
}
|
||||
|
||||
fetch_recent_messages() {
|
||||
return this.get<Message[]>(`/api/v1/messages`);
|
||||
}
|
||||
|
||||
fetch_message_children(messageId: string) {
|
||||
return this.get<Message[]>(`/api/v1/messages/${messageId}/children`);
|
||||
}
|
||||
|
||||
fetch_conversation(messageId: string) {
|
||||
return this.get(`/api/v1/messages/${messageId}/conversation`);
|
||||
}
|
||||
}
|
||||
|
||||
const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY);
|
||||
|
||||
export { oasstApiClient };
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { JWT } from "next-auth/jwt";
|
||||
import { OasstApiClient } from "src/lib/oasst_api_client";
|
||||
import { getBackendUserCore } from "src/lib/users";
|
||||
import { BackendUserCore } from "src/types/Users";
|
||||
|
||||
export const createApiClientFromUser = (user: BackendUserCore) =>
|
||||
new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY, user);
|
||||
|
||||
export const createApiClient = async (token: JWT) => createApiClientFromUser(await getBackendUserCore(token.sub));
|
||||
|
||||
export const userlessApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY);
|
||||
@@ -1,6 +1,32 @@
|
||||
import parser from "accept-language-parser";
|
||||
import type { NextApiRequest } from "next";
|
||||
import { i18n } from "src/../next-i18next.config";
|
||||
import prisma from "src/lib/prismadb";
|
||||
import type { BackendUserCore } from "src/types/Users";
|
||||
|
||||
const LOCALE_SET = new Set(i18n.locales);
|
||||
|
||||
/**
|
||||
* Returns the most appropriate user language using the following priority:
|
||||
*
|
||||
* 1. The `NEXT_LOCALE` cookie which is set by the client side and will be in
|
||||
* the set of supported locales.
|
||||
* 2. The `accept-language` header if it contains a supported locale as set by
|
||||
* the i18n module.
|
||||
* 3. "en" as a final fallback.
|
||||
*/
|
||||
const getUserLanguage = (req: NextApiRequest): string => {
|
||||
const cookieLanguage = req.cookies["NEXT_LOCALE"];
|
||||
if (cookieLanguage) {
|
||||
return cookieLanguage;
|
||||
}
|
||||
const headerLanguages = parser.parse(req.headers["accept-language"]);
|
||||
if (headerLanguages.length > 0 && LOCALE_SET.has(headerLanguages[0].code)) {
|
||||
return headerLanguages[0].code;
|
||||
}
|
||||
return "en";
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a `BackendUserCore` that can be used for interacting with the Backend service.
|
||||
*
|
||||
@@ -35,4 +61,4 @@ const getBackendUserCore = async (id: string) => {
|
||||
} as BackendUserCore;
|
||||
};
|
||||
|
||||
export { getBackendUserCore };
|
||||
export { getBackendUserCore, getUserLanguage };
|
||||
|
||||
Reference in New Issue
Block a user