Skip to content
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
2 changes: 0 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
# you can get your gemini api key from https://aistudio.google.com/app/apikey
API_KEY=your_gemini_api_key_here

VERCEL_URL="localhost:3000"
71 changes: 21 additions & 50 deletions action/index.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,51 @@
"use server";

import { model } from "@/utils";
import { handleModelResponse } from "@/utils";
import { generateSystemInstruction } from "@/utils/instructions";

export const commitChange = async ({
message,
isEmojiSupport,
}: {
type ActionProps = {
message: string | null;
isEmojiSupport: boolean;
}): Promise<{
};

type ActionPromise = {
data: {
text: string;
} | null;
error: string | null;
}> => {
};

export const commitChange = async ({
message,
isEmojiSupport,
}: ActionProps): Promise<ActionPromise> => {
if (!message || message.trim() === "") {
return { data: null, error: "Please enter a message" };
}

try {
const modelResponse = model.generateContent({
contents: [{ role: "user", parts: [{ text: message }] }],
systemInstruction: `\
You are an assistant that helps to provide Git commit messages based on https://www.conventionalcommits.org/en/v1.0.0/.

- Provide a Git commit message in a code block as txt.
- Do not include the full command (e.g., \`git commit -m "here git message"\`).
- Only provide the commit message itself.
- Suggest 3 different commit messages to give the user some options.
- For example, if the user input is "I change lib folder to utils folder", then the output should be:

${
isEmojiSupport &&
`\
- Use emojis in the commit message.
- For emojis, you can use https://gitmoji.dev/
- Don't provide a description, just the commit message.
- For example, if the user input is "I change lib folder to utils folder", then the output should be:
`
}

${
isEmojiSupport
? `
\`\`\`txt \n ♻️ refactor(lib): change lib folder to utils folder \n\`\`\`\n
\`\`\`txt \n βž• refactor(deps): rename lib folder to utils \n\`\`\`\n
\`\`\`txt \n ✏️ fix(deps): rename lib folder to utils \n\`\`\`\n
`
: `
\`\`\`txt \n refactor(lib): change lib folder to utils folder \n\`\`\`\n
\`\`\`txt \n refactor(deps): rename lib folder to utils \n\`\`\`\n
\`\`\`txt \n fix(deps): rename lib folder to utils \n\`\`\`\n
`
}

`,
});

const response = (await modelResponse).response.text();
const systemInstruction = generateSystemInstruction(isEmojiSupport);
const responseText = await handleModelResponse(systemInstruction, message);

console.log({
message: message,
response: response,
response: responseText,
data: {
time: new Date().toISOString(),
},
});

return {
data: {
text: response,
text: responseText,
},
error: null,
};
} catch (error) {
} catch (error: any) {
console.log("error on action", error);
return { data: null, error: "something went wrong!" };
return {
data: null,
error: error?.statusText ? error.statusText : "something went wrong!",
};
}
};
6 changes: 2 additions & 4 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/provider/theme-provider";
import { Toaster } from "@/components/ui/toaster";
Expand All @@ -13,8 +12,6 @@ import Image from "next/image";
import NotificationBanner from "@/components/notificationBanner";
import { TooltipProvider } from "@/components/ui/tooltip";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: {
default: siteConfig.name,
Expand Down Expand Up @@ -55,7 +52,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={`${inter.className}`}>
<body>
<ThemeProvider
attribute="class"
defaultTheme="dark"
Expand Down Expand Up @@ -93,6 +90,7 @@ export default function RootLayout({
</div>
<Link
href={"https://github.com/ruru-m07/commitly"}
aria-label="Github"
target="_blank"
className={cn(
buttonVariants({
Expand Down
193 changes: 3 additions & 190 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,192 +1,5 @@
"use client";
import Home from "@/components/home";

import { commitChange } from "@/action";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { toastVariants } from "@/components/ui/toast";
import { useToast } from "@/components/ui/use-toast";
import { cn } from "@/lib/utils";
import { CornerDownLeft } from "lucide-react";
import React from "react";
import dynamic from "next/dynamic";
import ListSuggestion from "@/components/listSuggestion";
import Loader from "@/components/loader";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";

const EmptyScreen = dynamic(() => import("@/components/emptyScreen"), {
ssr: false,
loading: () => <Loader />,
});

export default function Home() {
const [message, setMessage] = React.useState<string | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const [commitChanges, setcommitChanges] = React.useState<string | null>(null);
const [isEmojiSupport, setIsEmojiSupport] = React.useState<boolean>(false);
const [commitMessages, setcommitMessages] = React.useState<string | null>(
null
);

const { toast } = useToast();

const handelSubmit = async ({
suggestion,
force = false,
}: {
suggestion: string;
force?: boolean;
}) => {
if (!force && suggestion === commitChanges) {
toast({
variant: "destructive",
title: "Duplicate Message",
description: "Please enter a different message.",
});
return;
}

setIsLoading(true);
setError(null);

try {
const { data, error } = await commitChange({
message: suggestion,
isEmojiSupport: isEmojiSupport,
});

if (error) {
setError(error);
toast({
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: error,
action: (
<button
className={cn(
toastVariants({
variant: "destructive",
className: "w-fit m-0 p-2 text-xs hover:bg-[#815305]/35",
})
)}
onClick={() => handelSubmit({ suggestion })}
>
Try again
</button>
),
});
} else {
if (data) {
setcommitMessages(data.text);
console.log(data.text);
setcommitChanges(suggestion);
setMessage("");
}
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};

const submitForm = (
e: React.FormEvent<HTMLFormElement>,
message: string | null
): void => {
e.preventDefault();
handelSubmit({ suggestion: message || "" });
};

return (
<div className=" h-full py-10 flex items-center justify-center">
<ScrollArea className="h-screen w-full">
<div className=" flex h-screen mx-0 lg:mx-48 xl:mx-60 flex-col p-4 lg:col-span-2">
<div className="w-full h-5/6 mb-2 flex items-end">
<Card className="h-full w-full py-4 flex justify-center items-center bg-primary-foreground/25">
<ScrollArea className=" flex justify-center items-center w-full">
{error ? (
<div className="w-full flex items-center justify-center">
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
Oops! Something Went Wrong!{" "}
<span className="ml-2"> : ( </span>
</h3>
<p className="text-sm text-muted-foreground">{error}</p>
</div>
) : isLoading ? (
<div className="w-full flex items-center justify-center">
<Loader />
</div>
) : commitMessages ? (
<ListSuggestion
suggestions={commitMessages!}
commitChanges={commitChanges || ""}
submitForm={submitForm}
forceSubmit={handelSubmit}
/>
) : (
<div className="w-full flex items-center justify-center">
<EmptyScreen onSubmit={handelSubmit} />
</div>
)}
</ScrollArea>
</Card>
</div>

<div className="flex-1" />

<form
onSubmit={(e) => submitForm(e, message)}
className="relative overflow-hidden rounded-lg border bg-primary-foreground/25 focus-within:ring-1 focus-within:ring-ring shadow"
>
<Label htmlFor="message" className="sr-only">
Message
</Label>
<Input
id="message"
placeholder="Describe your changes here..."
className="min-h-12 resize-none border-0 p-3 shadow-none focus-visible:ring-0"
onChange={(e) => setMessage(e.target.value)}
value={message || ""}
disabled={isLoading}
autoComplete="off"
autoFocus
required
/>
<div className="flex items-center p-3 pt-0">
<div className="flex items-center space-x-2">
<Switch
onCheckedChange={(e) => setIsEmojiSupport(e)}
id="emoji-mode"
/>
<Label htmlFor="emoji-mode">Emoji Mode</Label>
</div>
<Button
type="submit"
size="sm"
className={cn("ml-auto gap-1.5", {
"bg-primary-foreground text-muted-foreground border cursor-not-allowed hover:bg-primary-foreground":
!message || isLoading,
})}
disabled={isLoading}
>
{isLoading && <Spinner className="size-4" />}
Generate
<CornerDownLeft className="size-3.5" />
</Button>
</div>
</form>
</div>
</ScrollArea>
</div>
);
export default function Page() {
return <Home />;
}
18 changes: 18 additions & 0 deletions components/errorComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";

interface ErrorComponentProps {
error: string;
}

const ErrorComponent: React.FC<ErrorComponentProps> = ({ error }) => {
return (
<div className="w-full flex flex-col items-center justify-center">
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
Oops! Something Went Wrong! <span className="ml-2"> : ( </span>
</h3>
<p className="text-sm text-muted-foreground mt-2">{error}</p>
</div>
);
};

export default ErrorComponent;
Loading