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

♻️ Improve Error Messages [Part 2] #626

Merged
merged 10 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions next/__tests__/message-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,43 @@ describe("sendErrorMessage", () => {

instance.sendErrorMessage(axiosError);
expect(renderMessage).toHaveBeenCalledWith({
type: "system",
value: "ERROR_ACCESSING_OPENAI_API_KEY",
type: "error",
value: "ERROR_API_KEY_QUOTA",
});
});

it("should handle platform errors", () => {
const axiosError = {
isAxiosError: true,
response: {
status: 409,
data: {
detail: {
error: "invalid_request",
detail: "You have exceeded the maximum number of requests allowed for your API key.",
code: 429,
},
},
},
};

instance.sendErrorMessage(axiosError);
expect(renderMessage).toHaveBeenCalledWith({
type: "error",
value: axiosError.response.data.detail.detail,
});
});

it("should handle unknown platform errors", () => {
const axiosError = {
isAxiosError: true,
response: { status: 409 },
};

instance.sendErrorMessage(axiosError);
expect(renderMessage).toHaveBeenCalledWith({
type: "error",
value: "An Unknown Error Occurred, Please Try Again!",
});
});

Expand All @@ -29,15 +64,15 @@ describe("sendErrorMessage", () => {

instance.sendErrorMessage(error);
expect(renderMessage).toHaveBeenCalledWith({
type: "system",
type: "error",
value: error,
});
});

it("should handle unknown errors", () => {
instance.sendErrorMessage({});
expect(renderMessage).toHaveBeenCalledWith({
type: "system",
type: "error",
value: "ERROR_RETRIEVE_INITIAL_TASKS",
});
});
Expand Down
47 changes: 27 additions & 20 deletions next/src/components/utils/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
import {
FaBrain,
FaCheckCircle,
FaCircleNotch,
FaExclamationTriangle,
FaRegCheckCircle,
FaCheckCircle,
FaStar,
FaStopCircle,
FaThumbtack,
} from "react-icons/fa";
import type { Message } from "../../types/agentTypes";
import {
getTaskStatus,
isTask,
TASK_STATUS_STARTED,
TASK_STATUS_EXECUTING,
TASK_STATUS_COMPLETED,
TASK_STATUS_FINAL,
MESSAGE_TYPE_ERROR,
MESSAGE_TYPE_GOAL,
MESSAGE_TYPE_THINKING,
getTaskStatus,
TASK_STATUS_COMPLETED,
TASK_STATUS_EXECUTING,
TASK_STATUS_FINAL,
TASK_STATUS_STARTED,
} from "../../types/agentTypes";

import type { Message } from "../../types/agentTypes";

export const getMessageContainerStyle = (message: Message) => {
if (!isTask(message)) {
return "border-white/10 hover:border-white/40";
switch (message.type) {
case "error":
return "border-red-500";
default:
return "border-white/10 hover:border-white/40";
}
}

switch (message.status) {
Expand All @@ -46,11 +52,16 @@ export const getTaskStatusIcon = (
const taskStatusIconClass = "mr-1 mb-1 inline-block";
const { isAgentStopped } = config;

if (message.type === MESSAGE_TYPE_GOAL) {
return <FaStar className="text-yellow-300" />;
} else if (message.type === MESSAGE_TYPE_THINKING) {
return <FaBrain className="mt-[0.1em] text-pink-400" />;
} else if (getTaskStatus(message) === TASK_STATUS_STARTED) {
switch (message.type) {
case MESSAGE_TYPE_GOAL:
return <FaStar className="text-yellow-300" />;
case MESSAGE_TYPE_THINKING:
return <FaBrain className="mt-[0.1em] text-pink-400" />;
case MESSAGE_TYPE_ERROR:
return <FaExclamationTriangle className="text-red-500" />;
}

if (getTaskStatus(message) === TASK_STATUS_STARTED) {
return <FaThumbtack className={`${taskStatusIconClass} -rotate-45`} />;
} else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) {
return isAgentStopped ? (
Expand All @@ -60,15 +71,11 @@ export const getTaskStatusIcon = (
);
} else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) {
return (
<FaRegCheckCircle
className={`${taskStatusIconClass} text-green-500 hover:text-green-400`}
/>
<FaRegCheckCircle className={`${taskStatusIconClass} text-green-500 hover:text-green-400`} />
);
} else if (getTaskStatus(message) === TASK_STATUS_FINAL) {
return (
<FaCheckCircle
className={`${taskStatusIconClass} text-green-500 hover:text-green-400`}
/>
<FaCheckCircle className={`${taskStatusIconClass} text-green-500 hover:text-green-400`} />
);
}
};
29 changes: 22 additions & 7 deletions next/src/services/agent/message-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { translate } from "../../utils/translations";
import type { Analysis } from "./analysis";
import axios from "axios";
import { isPlatformError } from "../../types/errors";

class MessageService {
private isRunning: boolean;
Expand Down Expand Up @@ -80,15 +81,29 @@ class MessageService {
sendErrorMessage(e: unknown) {
let message = "ERROR_RETRIEVE_INITIAL_TASKS";

if (axios.isAxiosError(e)) {
if (e.response?.status === 429) message = "ERROR_API_KEY_QUOTA";
if (e.response?.status === 404) message = "ERROR_OPENAI_API_KEY_NO_GPT4";
else message = "ERROR_ACCESSING_OPENAI_API_KEY";
} else if (typeof e == "string") {
message = e;
if (typeof e == "string") message = e;
else if (axios.isAxiosError(e)) {
switch (e.response?.status) {
case 409:
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const data = (e.response?.data?.detail as object) || {};
message = isPlatformError(data)
? data.detail
: "An Unknown Error Occurred, Please Try Again!";
break;
case 429:
message = "ERROR_API_KEY_QUOTA";
break;
case 404:
message = "ERROR_OPENAI_API_KEY_NO_GPT4";
break;
default:
message = "ERROR_ACCESSING_OPENAI_API_KEY";
break;
}
}

this.sendMessage({ type: MESSAGE_TYPE_SYSTEM, value: translate(message, "errors") });
this.sendMessage({ type: "error", value: translate(message, "errors") });
}
}

Expand Down
15 changes: 5 additions & 10 deletions next/src/types/agentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,22 @@ export const [
MESSAGE_TYPE_TASK,
MESSAGE_TYPE_ACTION,
MESSAGE_TYPE_SYSTEM,
MESSAGE_TYPE_ERROR,
] = [
"goal" as const,
"thinking" as const,
"task" as const,
"action" as const,
"system" as const,
"error" as const,
];

export const [
TASK_STATUS_STARTED,
TASK_STATUS_EXECUTING,
TASK_STATUS_COMPLETED,
TASK_STATUS_FINAL,
] = [
"started" as const,
"executing" as const,
"completed" as const,
"final" as const,
];
] = ["started" as const, "executing" as const, "completed" as const, "final" as const];

const TaskStatusSchema = z.union([
z.literal(TASK_STATUS_STARTED),
Expand Down Expand Up @@ -57,6 +54,7 @@ export const nonTaskScehma = z
z.literal(MESSAGE_TYPE_THINKING),
z.literal(MESSAGE_TYPE_ACTION),
z.literal(MESSAGE_TYPE_SYSTEM),
z.literal(MESSAGE_TYPE_ERROR),
]),
})
.merge(messageSchemaBase);
Expand All @@ -68,10 +66,7 @@ export type Message = z.infer<typeof messageSchema>;

/* Agent Type */
// Agent Mode
export const [AUTOMATIC_MODE, PAUSE_MODE] = [
"Automatic Mode" as const,
"Pause Mode" as const,
];
export const [AUTOMATIC_MODE, PAUSE_MODE] = ["Automatic Mode" as const, "Pause Mode" as const];
export type AgentMode = typeof AUTOMATIC_MODE | typeof PAUSE_MODE;

// Agent Playback Control
Expand Down
21 changes: 21 additions & 0 deletions next/src/types/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";

const platformErrorSchema = z.object({
error: z.enum([
"invalid_request",
"invalid_api_key",
"engine_not_found",
"permission_denied",
"server_error",
"timeout",
"too_many_requests",
]),
detail: z.string(),
code: z.number(),
});

export type PlatformError = z.infer<typeof platformErrorSchema>;

export const isPlatformError = (e: object): e is PlatformError => {
return platformErrorSchema.safeParse(e).success;
};