From 77cccba8a1e1cd87645c59953237ef8880899d68 Mon Sep 17 00:00:00 2001 From: caitlin Date: Wed, 1 Jul 2026 11:43:21 +0200 Subject: [PATCH 01/13] Fixed Dark Mode --- src/app/globals.css | 71 +++++++++++++-------- src/components/layout/sidebar-name-logo.tsx | 2 +- src/components/layout/topbar.tsx | 4 +- src/components/shared/page-header.tsx | 2 +- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index f4a849e..097c7ca 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -119,34 +119,47 @@ } .dark { - --background: 0 0% 10%; - --foreground: 210 40% 98%; - --card: 0 0% 9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 11%; - --popover-foreground: 210 40% 98%; - --primary: 0 100% 53%; + /* ── Slate palette tokens ── */ + --bg-page: 225 18% 9%; + --bg-sidebar: 227 25% 7%; + --bg-card: 224 24% 11%; + --bg-card-inset: 224 24% 13%; + --bg-nav-active: 221 43% 20%; + --border: 228 21% 21%; + --border-strong: 220 69% 59%; + --text-primary: 226 30% 95%; + --text-secondary: 228 13% 59%; + --text-active: 221 100% 87%; + --accent-hover: 220 70% 66%; + + /* ── Existing CSS variable overrides ── */ + --background: 225 18% 9%; + --foreground: 226 30% 95%; + --card: 224 24% 11%; + --card-foreground: 226 30% 95%; + --popover: 224 24% 13%; + --popover-foreground: 226 30% 95%; + --primary: 220 69% 59%; --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; + --secondary: 224 24% 13%; + --secondary-foreground: 226 30% 95%; + --muted: 224 24% 13%; + --muted-foreground: 228 13% 59%; + --accent: 224 24% 13%; + --accent-foreground: 226 30% 95%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 0 100% 53%; - --sidebar: 0 0% 11%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 145 65% 50%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 0 100% 53%; - + --border: 228 21% 21%; + --input: 228 21% 21%; + --ring: 220 69% 59%; + --sidebar: 227 25% 7%; + --sidebar-foreground: 226 30% 95%; + --sidebar-primary: 221 43% 20%; + --sidebar-primary-foreground: 221 100% 87%; + --sidebar-accent: 224 24% 13%; + --sidebar-accent-foreground: 226 30% 95%; + --sidebar-border: 228 21% 21%; + --sidebar-ring: 220 69% 59%; } .ocean { @@ -457,6 +470,14 @@ 100% { width: 0; height: 0; } } +.dark .bc-logo .accent { + color: hsl(220 69% 59%); +} +.dark .bc-logo::before, +.dark .bc-logo::after { + border-color: hsl(220 69% 59%); +} + @layer base { * { @apply border-border; diff --git a/src/components/layout/sidebar-name-logo.tsx b/src/components/layout/sidebar-name-logo.tsx index 088f884..a10c82e 100644 --- a/src/components/layout/sidebar-name-logo.tsx +++ b/src/components/layout/sidebar-name-logo.tsx @@ -4,7 +4,7 @@ export function SidebarNameLogo() { return (
- +
diff --git a/src/components/shared/page-header.tsx b/src/components/shared/page-header.tsx index ba62ac6..cd6be66 100644 --- a/src/components/shared/page-header.tsx +++ b/src/components/shared/page-header.tsx @@ -19,7 +19,7 @@ export function PageHeader({ title, description, children, className }: PageHead className={cn("flex items-center justify-between pb-6", className)} >
-

{title}

+

{title}

{description &&

{description}

}
{children &&
{children}
} From e8d80c3a164994960e7da63b10663b5af8c81650 Mon Sep 17 00:00:00 2001 From: caitlin Date: Wed, 1 Jul 2026 12:04:05 +0200 Subject: [PATCH 02/13] login logo fix that i broke --- src/app/globals.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 097c7ca..7ccdf14 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -470,14 +470,6 @@ 100% { width: 0; height: 0; } } -.dark .bc-logo .accent { - color: hsl(220 69% 59%); -} -.dark .bc-logo::before, -.dark .bc-logo::after { - border-color: hsl(220 69% 59%); -} - @layer base { * { @apply border-border; From e920d4925a051d548c9786a7a95463cf75e5e353 Mon Sep 17 00:00:00 2001 From: Ace Date: Wed, 1 Jul 2026 12:15:37 +0200 Subject: [PATCH 03/13] Updated the to use targeted jobs but its weird --- browser-use-service/main.py | 408 +++++++++++++++++----- data/ai/jobs.jsonl | 12 +- src/app/(dashboard)/ai-assistant/page.tsx | 46 ++- src/components/ai/ai-chat.tsx | 18 +- src/components/ai/job-selector.tsx | 84 +++-- 5 files changed, 434 insertions(+), 134 deletions(-) diff --git a/browser-use-service/main.py b/browser-use-service/main.py index b24f6b9..78367a2 100644 --- a/browser-use-service/main.py +++ b/browser-use-service/main.py @@ -341,6 +341,7 @@ OFFER_PATTERNS = [ r"\bfree\s+domain\b", r"\bweb\s+hosting\b", r"\bfree\s+website\b", + r"\bfree\s+\w+\s+website\b", r"\bprofessional\s+website", r"\baffordable\s+web\b", r"\bget\s+a\s+free\b", @@ -365,6 +366,47 @@ OFFER_PATTERNS = [ r"\bsponsored\b", r"\bpromoted\b", r"\badvertisement\b", + r"\bdo\s+you\s+(need|want)\b", + r"\bwe\s+(can|will)\s+(build|design|create|develop|do)\b", + r"\bi\s+(can|will)\s+(build|design|create|develop|do)\b", + r"\bmy\s+portfolio\b", + r"\breasonable\s+(price|pricing|rate)\b", + r"\bwhatsapp\s+(me|us|at)\b", + r"\blooking\s+for\s+(a\s+)?(business|client|customer)\b", + r"\bhelp\s+your\s+business\b", + r"\bi\s+am\s+a\s+(web|freelance|designer|developer)\b", + r"\bcontact\s+me\b", + r"\bwe\s+(are|offer|provide)\s+(web|website|design|service)\b", + r"\btake\s+(the\s+|this\s+)?quiz\b", + r"\bhomeschool\b", + r"\byour\s+(home\s+)?tutor\b", + r"\blink\s+(in\s+)?(bio|comment|below)\b", + r"\bapply\s+now\b", + r"\bget\s+started\b", + r"\bfor\s+only\b", + r"\blow\s+(price|cost|rate)\b", + r"\bhit\s+me\s+up\b", + r"\bsend\s+me\s+(a\s+)?(message|dm|pm)\b", + r"\bi\s+do\s+(web|website|design|development)\b", + r"\bwe\s+do\s+(web|website|design|development)\b", + r"\bi'm\s+offering\b", + r"\bwe're\s+offering\b", + r"\bstarting\s+a\s+\w+\s+(service|business)\b", + r"\blooking\s+for\s+a\s+few\s+businesses?\b", + r"\blet('?)s\s+connect\b", + r"\bi\s+have\s+projects?\b", + r"\byour\s+business\b", + r"\bwordpress\s+development\b", + r"\be.?commerce\s+website\b", + r"\bwebsite\s+builder\b", + r"\bai\s+studio\b", + r"\bpage\s+speed\b", + r"\bguest\s+post", + r"\bguest\s+blog", + r"\bfor\s+sale\b", + r"\bselling\s+(my|a|the|this)\b", + r"\bpremium\b", + r"\bi'm\s+selling\b", ] REQUEST_PATTERNS = [ @@ -549,6 +591,48 @@ FB_SEARCHES = [ "want to build a website for my", ] +TUTORING_SEARCHES = [ + "need a tutor", + "looking for a tutor", + "tutor for my child", + "need help with homework", + "private tutor needed", + "looking for tutoring", + "need a math tutor", + "english tutor needed", + "online tutor for", + "lessons for my child", + "help my child with", + "need someone to tutor", + "looking for someone to teach", + "tutoring for my", + "need help learning", + "recommend a tutor", + "anyone know a tutor", + "need a private tutor", + "tutoring services for my child", + "need academic help", + "need a reading tutor", + "looking for piano teacher", + "need help with maths", + "need a science tutor", + "looking for language tutor", + "need programming tutor", + "coding tutor for my", + "sat tutor needed", + "exam preparation tutor", + "need a homeschool tutor", + "tutor near me for", +] + +def _search_list_for_query(query: str) -> list[str]: + """Pick the appropriate search query pool based on the search term.""" + tl = query.lower() + tutoring_terms = ["tutor", "tutoring", "lessons", "homework", "teach", "learning", "child", "math", "english", "science", "exam", "homeschool", "coding", "programming", "piano", "reading"] + if any(t in tl for t in tutoring_terms): + return TUTORING_SEARCHES + return FB_SEARCHES + VIEWPORTS = [ {'width': 1280, 'height': 800}, {'width': 1366, 'height': 768}, @@ -663,6 +747,9 @@ def _clean_fb_text(text: str) -> str: cleaned_lines.append(stripped) return '\n'.join(cleaned_lines) +# Words that should never be treated as person names +_NON_NAMES = {"online","tutor","tutoring","school","academy","learning","urgently","looking","south","africa","philippines","australia","creative","professional","digital","services","solutions","statistics","actuarial","homeschooling","homeschool","reading","math","english","science","grade","group","page","website","design","development","agency","company","studio","graphic","technical","support","customer","guest","post","fashion","wordpress","shopify","ecommerce","business","marketing","social","media","content","strategy","seo","free","domain","hosting","sponsored","promoted","advertisement","need","want","help","please","contact","send","message","whatsapp","hire","freelance","writer","blogger","expert","specialist","consultant","freelancer","software","creators","village","town","city","university","college","institute","evolve","aesthetics","sale","selling","premium","builder","pro","people","ecommerce"} + # ── Post Extraction ────────────────────────────────────────────────── # Two strategies for extracting posts from page content: # 1. _extract_posts_from_elements — uses structured DOM data from _get_article_elements() @@ -720,6 +807,25 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: posts = [] seen_texts = set() cur = [] + + def _find_author(candidate_lines: list[str]) -> str: + for line in reversed(candidate_lines): + ln = line.strip() + if not ln or len(ln) > 60 or len(ln) < 3: + continue + words = ln.split() + if not (1 < len(words) < 8): + continue + if not all(w[0].isalpha() and w[0].isupper() for w in words if w): + continue + lower_ln = ln.lower() + if any(kw in lower_ln for kw in BROAD_KEYWORDS) or is_offer(ln): + continue + if 'http' in ln or '/' in ln or '@' in ln: + continue + return ln + return '' + for l in lines: words = re.findall(r'[A-Za-z]{2,}', l) if len(words) < 2: @@ -735,7 +841,7 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: posts.append({ "title": snippet[:300], "content": snippet[:1000], - "author": '', + "author": _find_author(cur[-3:]), "url": url, "source": "facebook", "date": _parse_fb_date(cur + [l]), @@ -759,7 +865,40 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: cur.append(l) if len(cur) > 10: cur.pop(0) - return posts + # Post-process: scan each post's text for inline names + def _scan_inline_name(text: str) -> str: + for m in re.finditer(r'([A-Z][a-z]{2,}(?:\s+[A-Z][a-z]{2,}){1,3})[\s,;:]', text): + cand = m.group(1).strip() + if cand and 3 <= len(cand) <= 60: + lower = cand.lower() + words = lower.split() + if not any(kw in lower for kw in BROAD_KEYWORDS) and not is_offer(cand): + if not any(w in _NON_NAMES for w in words): + return cand + return '' + for p in posts: + if not p.get('author'): + txt = p.get('content') or p.get('title') or '' + name = _scan_inline_name(txt) + if name: + p['author'] = name + # Remove group posts: any post whose text looks like it came from a group + _group_suffixes = [" - South Africa", " - Australia", " PH", "Group", "help group", "info group"] + filtered = [] + for p in posts: + t = (p.get('title') or p.get('content') or '') + if ' / ' in t: + continue + lower = t.lower() + skip = False + for suff in _group_suffixes: + if suff.lower() in lower: + skip = True + break + if skip: + continue + filtered.append(p) + return filtered # ── Human-like Behavior Simulation ────────────────────────────────── # These functions add random delays, mouse movements, and scroll patterns @@ -811,58 +950,31 @@ async def _get_article_elements(page) -> list[dict]: return await page.evaluate('''() => { const results = []; const seenTexts = new Set(); - const selectors = [ - 'div[role="article"]', - 'div[role="feed"] > div', - 'div.x1yztbdb', - 'div[data-pagelet]', - ]; - for (const sel of selectors) { - const els = document.querySelectorAll(sel); - for (const el of els) { - const text = (el.innerText || '').trim(); - if (text.length < 40) continue; - const key = text.substring(0, 80); - if (seenTexts.has(key)) continue; - seenTexts.add(key); - - // --- Publisher name --- - let author = ''; - const nameEl = el.querySelector('h4, strong, a[role="link"]'); - if (nameEl) { - author = (nameEl.innerText || '').trim(); - } - if (!author) { - const links = el.querySelectorAll('a'); - for (const a of links) { - const t = (a.innerText || '').trim(); - if (t && t.length > 1 && t.length < 60 && /^[A-Za-zÀ-ÿ]/.test(t) && !t.includes('facebook') && !t.includes('/')) { - author = t; - break; - } - } - } - - // --- Post URL --- - let postUrl = ''; - for (const a of el.querySelectorAll('a')) { - const h = (a.getAttribute('href') || ''); - if (h.includes('/posts/') || h.includes('/photo/') || h.includes('/video/') || h.includes('/groups/') || h.includes('/permalink/')) { - postUrl = h.startsWith('http') ? h : 'https://www.facebook.com' + h; - break; - } - } - - // --- Date from