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
54 changes: 38 additions & 16 deletions action/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
"use server";

import { model, systemHistory } from "@/utils";
import { Content } from "@google/generative-ai";
import { model } from "@/utils";

export const commitMessage = async ({
export const commitChange = async ({
message,
history,
}: {
message: string;
history: Content[];
}) => {
const chat = model.startChat({
history: [...(await systemHistory()), ...history],
});
message: string | null;
}): Promise<{
data: {
text: string;
} | null;
error: string | null;
}> => {
if (!message || message.trim() === "") {
return { data: null, error: "Please enter a message" };
}

try {
const result = await chat.sendMessage(message);
const response = result.response;
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:

\`\`\`txt refactor(lib): change lib folder to utils folder \n\`\`\`\n
\`\`\`txt refactor(deps): rename lib folder to utils \n\`\`\`\n
\`\`\`txt fix(deps): rename lib folder to utils \n\`\`\`\n

`,
});

const text = response.text();
console.log("text", text);
const response = (await modelResponse).response.text();

return { success: true, text: text };
return {
data: {
text: response,
},
error: null,
};
} catch (error) {
return { success: false, error: error as string };
console.log("error on action", error);
return { data: null, error: "something went wrong!" };
}
};
27 changes: 0 additions & 27 deletions app/api/route.ts

This file was deleted.

39 changes: 38 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import "./globals.css";
import { ThemeProvider } from "@/components/provider/theme-provider";
import { Toaster } from "@/components/ui/toaster";
import { siteConfig } from "@/config/site";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import { ModeToggle } from "@/components/modeToggle";
import Image from "next/image";

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

Expand Down Expand Up @@ -54,7 +60,38 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
{children}
<main className="mx-10 lg:mx-20">
<section className="h-screen">
<div className="w-full flex justify-end absolute top-0 right-0 items-center mr-10 mt-3 z-10">
<div
className={cn(
buttonVariants({
variant: "outline",
size: "icon",
className: "rounded-full w-10 h-10 overflow-hidden",
})
)}
>
<Image src={'/logo.jpeg'} alt="logo" width={100} height={100} />
</div>
<Link
href={"https://github.com/ruru-m07/commitly"}
target="_blank"
className={cn(
buttonVariants({
variant: "outline",
size: "icon",
className: "rounded-full mx-2 w-10 h-10",
})
)}
>
<GitHubLogoIcon className="w-6 h-6" />
</Link>
<ModeToggle />
</div>
{children}
</section>
</main>
<Toaster />
</ThemeProvider>
</body>
Expand Down
189 changes: 114 additions & 75 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,157 @@
"use client";

import { commitMessage } from "@/action";
import AiLoading from "@/components/AiLoading";
import Navbar from "@/components/Navbar";
import ListChat from "@/components/listChat";
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 { Content } from "@google/generative-ai";
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";

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

export default function Home() {
const [chatHistory, setChatHistory] = React.useState<Content[] | []>([]);
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 [commitMessages, setcommitMessages] = React.useState<string | null>(
null
);

const { toast } = useToast();

const handelSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!message || message.trim() === "") {
const handelSubmit = async ({ suggestion }: { suggestion: string }) => {
if (suggestion === commitChanges) {
toast({
title: "Error",
description: "Please enter a message",
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: "error: Please enter a different message.",
});
return;
}

setIsLoading(true);
setError(null);

try {
setChatHistory((prevMsg) => [
...prevMsg,
{ role: "user", parts: [{ text: message! }] },
]);

const data = await commitMessage({
message: message!,
history: chatHistory,
const { data, error } = await commitChange({
message: suggestion,
});

if (data.success && data.text) {
setChatHistory((prevMsg) => [
...prevMsg,
{ role: "model", parts: [{ text: data.text }] },
]);
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 {
console.log(data.error);
if (data.error) {
toast({
description: data.error,
});
} else {
if (data) {
setcommitMessages(data.text);
setcommitChanges(suggestion);
setMessage("");
}
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
setMessage("");
}
};

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

return (
<main className="mx-10 lg:mx-20">
<section className="h-screen py-10">
<Navbar />
<div className=" h-full py-10 flex items-center justify-center">
<ScrollArea className="h-screen w-full">
<div className="relative flex h-screen mx-0 sm:mx-10 lg:mx-60 flex-col p-4 lg:col-span-2">
<ScrollArea className="w-full h-5/6 mb-2">
<ListChat chatHistory={chatHistory} />
{isLoading && <AiLoading />}
</ScrollArea>
<div className=" h-full py-10 flex items-center justify-center">
<ScrollArea className="h-screen w-full">
<div className=" flex h-screen mx-0 sm:mx-10 lg: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-96 w-full flex justify-center items-center bg-primary-foreground/50">
{error ? (
<div>
<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 ? (
<Loader />
) : commitMessages ? (
<ListSuggestion
suggestions={commitMessages!}
commitChanges={commitChanges || ""}
/>
) : (
<EmptyScreen onSubmit={handelSubmit} />
)}
</Card>
</div>

<div className="flex-1" />
<form
onSubmit={handelSubmit}
className="relative overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring"
<div className="flex-1" />

<form
onSubmit={(e) => submitForm(e, message)}
className="relative overflow-hidden rounded-lg border bg-primary-foreground/50 focus-within:ring-1 focus-within:ring-ring"
>
<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 bg-primary-foreground/50"
onChange={(e) => setMessage(e.target.value)}
value={message || ""}
disabled={isLoading}
autoComplete="off"
autoFocus
required
/>
<div className="flex items-center p-3 pt-0">
<Button
type="submit"
size="sm"
className="ml-auto gap-1.5"
disabled={isLoading}
>
<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">
<Button
type="submit"
size="sm"
className="ml-auto gap-1.5"
disabled={isLoading}
>
Send Message
<CornerDownLeft className="size-3.5" />
</Button>
</div>
</form>
Send Message
<CornerDownLeft className="size-3.5" />
</Button>
</div>
</ScrollArea>
</form>
</div>
</section>
</main>
</ScrollArea>
</div>
);
}
13 changes: 0 additions & 13 deletions components/AiLoading.tsx

This file was deleted.

Loading