Skip to content

Commit

Permalink
feat: linkedin pipe v2
Browse files Browse the repository at this point in the history
* deleting old files

* updated pipe

* updated pipe

* remove messages.json from git tracking

* set up guide

* templates

* storage flow

* click first connect

* updated click message

* extract profiles

* go to search

* connect button

* click message

* harvest connections

* updated flow

* apis for harvesting

* saving templates apis

* validate search apis

* page

* settings

* main workflow

* package

* harvest component

* reload

* :wq

g:ç

q
fgsodgks with '#' will be ignored, and an empty message aborts the commit.
ranch is up to date with 'fork/linkedin-pipe2'.

* restoring dylib

* new pipe

* big flow

* gitignore

* congig

* stop tracking storage json files

* updated harvesting logic, fixed ux, fixed intro requester workflow

* update

* removing state and profiles
  • Loading branch information
m13v authored Dec 10, 2024
1 parent 966bea2 commit d3f3a42
Show file tree
Hide file tree
Showing 15 changed files with 1,040 additions and 18,329 deletions.
3 changes: 3 additions & 0 deletions pipes/linkedin_ai_assistant/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ IndexedDB/
WebStorage/
Cookies*
History*

# storage files
lib/storage/*.json
21 changes: 15 additions & 6 deletions pipes/linkedin_ai_assistant/app/api/harvest/start/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,32 @@ export async function POST(req: NextRequest) {
}

// Return detailed status messages based on the harvesting result
const message = result.weeklyLimitReached
? `weekly linkedin invitation limit reached, retrying at ${new Date(result.nextHarvestTime!).toLocaleString()}`
: result.connectionsSent === 0
? "no new connections found to harvest"
: `sent ${result.connectionsSent} connections.`;
let message = '';
if (result.weeklyLimitReached) {
message = `weekly limit reached, retrying at ${new Date(result.nextHarvestTime!).toLocaleString()}`;
} else if (result.dailyLimitReached) {
message = `daily limit of ${result.connectionsSent} connections reached, next harvest at ${new Date(result.nextHarvestTime!).toLocaleString()}`;
} else if (result.connectionsSent === 0) {
message = "no new connections found to harvest";
} else {
message = `sent ${result.connectionsSent} connections.`;
}

return NextResponse.json(
{
message,
weeklyLimitReached: result.weeklyLimitReached,
dailyLimitReached: result.dailyLimitReached,
connectionsSent: result.connectionsSent,
nextHarvestTime: result.nextHarvestTime,
},
{ status: 200 }
);
} catch (error: any) {
console.error('error starting harvesting:', error);
return NextResponse.json({ message: error.message.toLowerCase() }, { status: 500 });
return NextResponse.json(
{ message: error.message.toLowerCase() },
{ status: 500 }
);
}
}
34 changes: 18 additions & 16 deletions pipes/linkedin_ai_assistant/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,37 @@

import { useState } from "react";
import { LaunchLinkedInChromeSession } from "@/components/launch_linkedin_chrome_session";
import TemplateEditor from "@/components/settings_editor";
import template from "@/lib/storage/templates.json";
import { StartWorkflow } from "@/components/start_workflow";
import StateViewer from "@/components/state_viewer";
import { IntroRequester } from "@/components/intro-requester";
import { ReloadButton } from "@/components/reload_button";
import { HarvestClosestConnections } from "@/components/harvest";

export default function Home() {
const [loginStatus, setLoginStatus] = useState<'checking' | 'logged_in' | 'logged_out' | null>(null);

return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen w-full p-4 pb-20 gap-16 sm:p-8">
<main className="w-full max-w-[95vw] flex flex-col gap-8 row-start-2 items-center sm:items-start justify-center">
<div className="w-full flex justify-end">
</div>
<div className="min-h-screen w-full p-4 pb-20 sm:p-8">
<div className="space-y-1.5 mb-8">
<h1 className="text-2xl font-semibold tracking-tight">linkedin ai assistant</h1>
<p className="text-sm text-muted-foreground">automate your linkedin interactions with ai</p>
</div>

<div className="flex flex-col gap-2 mb-8">
<ReloadButton />
<LaunchLinkedInChromeSession
loginStatus={loginStatus}
setLoginStatus={setLoginStatus}
/>
{loginStatus === 'logged_in' && (
<>
</div>

{loginStatus === 'logged_in' && (
<div className="w-full space-y-6">
<h2 className="text-2xl font-semibold mb-6">workflows</h2>
<div className="space-y-6 text-lg">
<HarvestClosestConnections />
{/* <TemplateEditor initialTemplate={template} />
<StartWorkflow />
<StateViewer /> */}
</>
)}
</main>
<IntroRequester />
</div>
</div>
)}
</div>
);
}
86 changes: 57 additions & 29 deletions pipes/linkedin_ai_assistant/components/harvest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ export function HarvestClosestConnections() {
const [status, setStatus] = useState("");
const [nextHarvestTime, setNextHarvestTime] = useState<string | null>(null);
const [connectionsSent, setConnectionsSent] = useState(0);
const [dailyLimitReached, setDailyLimitReached] = useState(false);
const [weeklyLimitReached, setWeeklyLimitReached] = useState(false);

useEffect(() => {
// Update initial state
fetch("/api/harvest/status")
.then(res => res.json())
.then(data => {
setConnectionsSent(data.connectionsSent || 0);
setDailyLimitReached(data.dailyLimitReached || false);
setWeeklyLimitReached(data.weeklyLimitReached || false);
if (data.nextHarvestTime) {
setNextHarvestTime(data.nextHarvestTime);
if (new Date(data.nextHarvestTime) > new Date()) {
Expand All @@ -32,8 +36,16 @@ export function HarvestClosestConnections() {
.then(res => res.json())
.then(data => {
setConnectionsSent(data.connectionsSent || 0);
setIsRunning(data.isHarvesting || false);
if (!data.isHarvesting) {
setDailyLimitReached(data.dailyLimitReached || false);
setWeeklyLimitReached(data.weeklyLimitReached || false);
if (data.nextHarvestTime) {
setNextHarvestTime(data.nextHarvestTime);
if (new Date(data.nextHarvestTime) > new Date()) {
setStatus(`harvesting cooldown active until ${new Date(data.nextHarvestTime).toLocaleString()}`);
}
}
if (!data.isHarvesting && isRunning) {
setIsRunning(false);
setStatus("harvest process stopped");
}
})
Expand All @@ -45,30 +57,38 @@ export function HarvestClosestConnections() {
}, [isRunning]);

const startHarvesting = async () => {
setIsRunning(true);
setStatus("starting harvesting process...");

try {
setIsRunning(true);
setStatus("starting harvesting process...");

const response = await fetch("/api/harvest/start", {
method: "POST",
});

const data = await response.json();
console.log('response data:', data);
console.log('harvest start response:', data);

if (response.ok) {
setStatus(data.message?.toLowerCase() || 'unknown status');
setConnectionsSent(data.connectionsSent || 0);
setDailyLimitReached(data.dailyLimitReached || false);
setWeeklyLimitReached(data.weeklyLimitReached || false);
if (data.nextHarvestTime) {
setNextHarvestTime(data.nextHarvestTime);
}
setIsRunning(true);
} else {
setStatus(`${data.message?.toLowerCase() || 'unknown error'}`);
setIsRunning(false);
// Handle 429 without stopping the workflow
if (response.status === 429) {
setNextHarvestTime(data.nextHarvestTime);
setStatus(data.message?.toLowerCase() || 'rate limit reached');
} else {
setStatus(`error: ${data.message?.toLowerCase() || 'unknown error'}`);
setIsRunning(false);
}
}
} catch (error: any) {
console.error("failed to start harvesting:", error);
setStatus(`${error.message?.toLowerCase() || error.toString().toLowerCase()}`);
setStatus(`error: ${error.message?.toLowerCase() || error.toString().toLowerCase()}`);
setIsRunning(false);
}
};
Expand All @@ -93,32 +113,40 @@ export function HarvestClosestConnections() {

return (
<div className="flex flex-row items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">
<div className="flex items-center gap-4">
<span className="text-lg font-medium">
harvest connections {connectionsSent > 0 && `(${connectionsSent})`}
</span>
{isRunning || (nextHarvestTime && new Date(nextHarvestTime) > new Date()) ? (
<button
onClick={stopHarvesting}
className="bg-red-600 text-white px-3 py-1.5 rounded-md text-sm hover:bg-red-700"
>
stop
</button>
) : (
<button
onClick={startHarvesting}
className="bg-black text-white px-3 py-1.5 rounded-md text-sm"
disabled={nextHarvestTime && new Date(nextHarvestTime) > new Date()}
>
start
</button>
)}
<div className="flex gap-2">
{!isRunning && (
<button
onClick={startHarvesting}
className="bg-black text-white px-4 py-2 rounded-md text-base"
>
start
</button>
)}
{isRunning && (
<button
onClick={stopHarvesting}
className="bg-red-600 text-white px-4 py-2 rounded-md text-base hover:bg-red-700"
>
stop
</button>
)}
</div>
</div>
{status && (
{isRunning && status && (
<span className="text-sm text-gray-500">
{status}
</span>
)}
{(dailyLimitReached || weeklyLimitReached) && nextHarvestTime && (
<span className="text-sm text-gray-500">
{dailyLimitReached && `daily limit reached, next harvest at ${new Date(nextHarvestTime).toLocaleString()}`}
{weeklyLimitReached && `weekly limit reached, next harvest at ${new Date(nextHarvestTime).toLocaleString()}`}
</span>
)}
</div>
);
}
Loading

0 comments on commit d3f3a42

Please sign in to comment.