Skip to content
Open
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
19 changes: 11 additions & 8 deletions backend/linkedin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
async def scrape_user(username : str):
# Initialize browser
async with BrowserManager(headless=False) as browser:
# Load authenticated session
await browser.load_session("playwright/session.json")
# Load authenticated session if it exists
import os
session_path = os.path.join(os.path.dirname(__file__), "playwright", "session.json")
if os.path.exists(session_path):
await browser.load_session(session_path)

# Create scraper
scraper = PersonScraper(browser.page)
Expand Down Expand Up @@ -58,7 +61,10 @@ async def scrape_user(username : str):

async def scrape_company(company : str):
async with BrowserManager(headless=False) as browser:
await browser.load_session("playwright/session.json")
import os
session_path = os.path.join(os.path.dirname(__file__), "playwright", "session.json")
if os.path.exists(session_path):
await browser.load_session(session_path)

scraper = CompanyScraper(browser.page)
company = await scraper.scrape(f"https://linkedin.com/company/{company}/")
Expand Down Expand Up @@ -88,9 +94,6 @@ async def scrape_company(company : str):
return json.dumps(returned_data)



#response = asyncio.run(scrape_company("rogers-communications"))
#print(response)
#response = asyncio.run(scrape_user("dayuhechen"))
#response = asyncio.run(scrape_user("aathithy-aananth"))
#print(response)
#asyncio.run(scrape_user("dayuhechen"))
#asyncio.run(scrape_user("aathithya-ananth"))
42 changes: 0 additions & 42 deletions backend/yoshixi.txt

This file was deleted.

94 changes: 94 additions & 0 deletions src/components/DetectiveBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ function AssetTracker() {
export function DetectiveBoard() {
const [editor, setEditor] = useState<Editor | null>(null)
const [isSearching, setIsSearching] = useState(false)
const [isLinkedInSearching, setIsLinkedInSearching] = useState(false)
const [isSherlockSearching, setIsSherlockSearching] = useState(false)
const [leadBranchingFactor, setLeadBranchingFactor] = useState(2)
const [isPanelExpanded, setIsPanelExpanded] = useState(true)
Expand Down Expand Up @@ -640,6 +641,12 @@ export function DetectiveBoard() {

const mainNotePosition = {x:0, y:0}
function handleTextUpload(inputstr : string) {
const noteCardWidth = 180
const noteCardHeight = 180
const viewport = editor.getViewportPageBounds()
const centerX = viewport.center.x - noteCardWidth / 2
const centerY = viewport.center.y - noteCardHeight / 2

if (!editor) return
const original_id = createShapeId()
editor.createShape({
Expand Down Expand Up @@ -860,6 +867,91 @@ export function DetectiveBoard() {
resetTimeout()

}

const handleLinkedInSearch = useCallback(async (username: string) => {
if (!editor) return

setIsLinkedInSearching(true)

try {
// Call the LinkedIn API endpoint
const response = await fetch(`http://localhost:5000/api/linkedin_photo_pins/${username}`)
const data = await response.json()

if (data.error) {
console.error('LinkedIn search error:', data.error)
alert(`Error: ${data.error}`)
return
}

// Calculate base position for photo pins
const allShapes = editor.getCurrentPageShapes()
const existingPhotoPins = allShapes.filter((shape: any) => shape.type === 'photo-pin')

let baseX = 300
let baseY = 300

if (existingPhotoPins.length > 0) {
// Offset from the last photo pin
const lastPin = existingPhotoPins[existingPhotoPins.length - 1]
baseX = lastPin.x + 250
baseY = lastPin.y
}

const pinWidth = 200
const pinHeight = 200
const horizontalSpacing = 250

// Create photo pins for each logo
data.photo_pins.forEach((pin: any, index: number) => {
const pinX = baseX + (index * horizontalSpacing)
const pinY = baseY

// Find non-colliding position
const position = findNonCollidingPosition(
editor,
pinX,
pinY,
pinWidth,
pinHeight,
80
)

editor.createShape({
id: createShapeId(),
type: 'photo-pin',
x: position.x,
y: position.y,
props: {
w: pinWidth,
h: pinHeight,
imageUrl: pin.imageUrl,
caption: pin.caption,
},
})
})

// Optionally, create a note with profile info
if (data.profile && data.profile.name) {
editor.createShape({
id: createShapeId(),
type: 'note-card',
x: baseX,
y: baseY - 150,
props: {
text: `${data.profile.name}\n${data.profile.location || ''}\n${data.profile.linkedin_url || ''}`,
},
})
}

} catch (error) {
console.error('Error fetching LinkedIn data:', error)
alert('Failed to fetch LinkedIn data. Make sure the backend server is running.')
} finally {
setIsLinkedInSearching(false)
}
}, [editor])

const handleImageUpload = useCallback(async (file: File | string) => {
if (!editor) return

Expand Down Expand Up @@ -1187,7 +1279,9 @@ export function DetectiveBoard() {
<SearchPanel
onImageUpload={handleImageUpload}
onTextSearch={handleTextUpload}
onLinkedInSearch={handleLinkedInSearch}
isSearching={isSearching}
isLinkedInSearching={isLinkedInSearching}
onExpandChange={setIsPanelExpanded}
/>
{/* Lead Branching Factor Slider */}
Expand Down
64 changes: 64 additions & 0 deletions src/components/SearchPanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,67 @@
.search-tips li {
margin-bottom: 6px;
}

.text-search-form {
display: flex;
gap: 8px;
margin-bottom: 20px;
}

.text-search-input {
flex: 1;
padding: 10px 12px;
border: 2px solid rgba(139, 69, 19, 0.3);
border-radius: 6px;
font-size: 14px;
background: white;
color: #333;
transition: border-color 0.2s;
}

.text-search-input:focus {
outline: none;
border-color: #8b4513;
}

.text-search-input:disabled {
background: #f5f5f5;
cursor: not-allowed;
}

.text-search-input::placeholder {
color: #999;
}

.text-search-button {
padding: 10px 20px;
background: #8b4513;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 14px;
transition: background 0.2s;
white-space: nowrap;
}

.text-search-button:hover:not(:disabled) {
background: #654321;
}

.text-search-button:disabled {
background: #ccc;
cursor: not-allowed;
}

.search-panel h3 {
margin: 20px 0 12px 0;
font-size: 16px;
color: #8b4513;
font-weight: 600;
}

.search-panel h3:first-of-type {
margin-top: 0;
}
37 changes: 36 additions & 1 deletion src/components/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ interface SearchPanelProps {
isSearching: boolean
onTextSearch: (searchTerm: string) => void
onExpandChange?: (isExpanded: boolean) => void
onLinkedInSearch?: (isExpanded: boolean) => void
isLinkedInSearching: boolean
}


export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpandChange }: SearchPanelProps) {
export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpandChange, onLinkedInSearch, isLinkedInSearching }: SearchPanelProps) {
const [dragActive, setDragActive] = useState(false)
const [isExpanded, setIsExpanded] = useState(true)

Expand Down Expand Up @@ -89,6 +91,39 @@ export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpand
</form>
</div>

<h3>
LinkedIn Profile Search
</h3>
<div>
<form
onSubmit={e => {
e.preventDefault();
const input = e.currentTarget.querySelector('input[name="linkedin-username"]') as HTMLInputElement;
if (input && input.value.trim()) {
onLinkedInSearch(input.value.trim())
input.value = ''
}
}}
className="text-search-form"
>
<input
type="text"
name="linkedin-username"
placeholder="Enter LinkedIn Username"
className="text-search-input"
autoComplete="off"
disabled={isLinkedInSearching}
/>
<button
type="submit"
className="text-search-button"
disabled={isLinkedInSearching}
>
{isLinkedInSearching ? 'Searching...' : 'Search'}
</button>
</form>
</div>

<div
className={`upload-area ${dragActive ? 'drag-active' : ''} ${isSearching ? 'searching' : ''}`}
onDragEnter={handleDrag}
Expand Down