├── public ├── video_demo.mp4 └── grid.svg ├── postcss.config.mjs ├── src ├── lib │ ├── logger.ts │ ├── parse-exa-profile.test.ts │ ├── utils.ts │ ├── redis.ts │ ├── fetch-twitter-profile.ts │ ├── parse-exa-profile.ts │ ├── load-avatar.ts │ ├── test_data │ │ ├── results-Inveeest.json │ │ ├── results-mazeincoding.json │ │ ├── results-vishyfishy2.json │ │ └── results-rauchg.json │ └── indexed-db.ts ├── components │ ├── theme-provider.tsx │ └── ui │ │ ├── use-mobile.tsx │ │ ├── input.tsx │ │ ├── separator.tsx │ │ ├── sonner.tsx │ │ ├── tooltip.tsx │ │ ├── scroll-area.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ └── alert-dialog.tsx ├── app │ ├── providers.tsx │ ├── layout.tsx │ ├── globals.css │ ├── actions │ │ └── analyze-tweets.ts │ ├── components │ │ └── analysis-panel.tsx │ └── page.tsx └── hooks │ ├── use-mobile.tsx │ └── use-debounce.ts ├── .env.example ├── .gitignore ├── components.json ├── tsconfig.json ├── next.config.mjs ├── README.md ├── package.json ├── tailwind.config.ts └── profile-debug.json /public/video_demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1shy-dev/x-alignment-chart/HEAD/public/video_demo.mp4 -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | export const logger = pino({ 4 | level: process.env.LOG_LEVEL || process.env.NODE_ENV === "development" ? "debug" : "info", 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # https://exa.ai for Twitter data 2 | EXA_API_KEY= 3 | 4 | # Upstash Redis 5 | KV_REST_API_READ_ONLY_TOKEN= 6 | KV_REST_API_TOKEN= 7 | KV_REST_API_URL= 8 | KV_URL= 9 | 10 | # OpenAI for LLMs 11 | OPENAI_API_KEY= 12 | 13 | -------------------------------------------------------------------------------- /src/lib/parse-exa-profile.test.ts: -------------------------------------------------------------------------------- 1 | import { parseExaUserString } from "./parse-exa-profile"; 2 | import rauchg from "./test_data/results-rauchg.json" 3 | 4 | const rauchg_parsed = parseExaUserString(rauchg.data.results[0].text) 5 | 6 | const tweets = rauchg_parsed.data?.tweets ?? [] 7 | 8 | for (const tweet of tweets) { 9 | console.log(tweet) 10 | } -------------------------------------------------------------------------------- /src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { 5 | ThemeProvider as NextThemesProvider, 6 | type ThemeProviderProps, 7 | } from 'next-themes' 8 | 9 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export const getRandomPosition = () => { 9 | const padding = 10; 10 | const x = Math.random() * (100 - 2 * padding) + padding; 11 | const y = Math.random() * (100 - 2 * padding) + padding; 12 | 13 | return { x, y }; 14 | }; 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # next.js 7 | /.next/ 8 | /out/ 9 | 10 | # production 11 | /build 12 | 13 | # debug 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | .pnpm-debug.log* 18 | 19 | # env files 20 | .env* 21 | 22 | # vercel 23 | .vercel 24 | 25 | # typescript 26 | *.tsbuildinfo 27 | next-env.d.ts 28 | .env*.local 29 | !.env.example -------------------------------------------------------------------------------- /src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type React from "react"; 4 | 5 | import { TooltipProvider } from "@/components/ui/tooltip"; 6 | import { Analytics } from "@vercel/analytics/react"; 7 | import { Toaster } from "@/components/ui/sonner"; 8 | 9 | export function Providers({ children }: { children: React.ReactNode }) { 10 | return ( 11 | 12 | 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | import { Providers } from "./providers"; 3 | import "./globals.css"; 4 | import { Geist } from "next/font/google"; 5 | 6 | const font = Geist({ 7 | subsets: ["latin"], 8 | display: "swap", 9 | }); 10 | 11 | export const metadata = { 12 | generator: "v0.dev", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState( 7 | undefined 8 | ); 9 | 10 | React.useEffect(() => { 11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 12 | const onChange = () => { 13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 14 | }; 15 | mql.addEventListener("change", onChange); 16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 17 | return () => mql.removeEventListener("change", onChange); 18 | }, []); 19 | 20 | return !!isMobile; 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "target": "ES6", 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner } from "sonner" 5 | 6 | type ToasterProps = React.ComponentProps 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme() 10 | 11 | return ( 12 | 28 | ) 29 | } 30 | 31 | export { Toaster } 32 | -------------------------------------------------------------------------------- /src/hooks/use-debounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | type AnyFunction = (...args: any[]) => any; 4 | 5 | export function useDebounce( 6 | callback: T, 7 | delay: number 8 | ): (...args: Parameters) => void { 9 | const callbackRef = useRef(callback); 10 | const timeoutRef = useRef(null); 11 | 12 | // Update the callback ref when callback changes 13 | useEffect(() => { 14 | callbackRef.current = callback; 15 | }, [callback]); 16 | 17 | // Clear timeout on unmount 18 | useEffect(() => { 19 | return () => { 20 | if (timeoutRef.current) { 21 | clearTimeout(timeoutRef.current); 22 | } 23 | }; 24 | }, []); 25 | 26 | return (...args: Parameters) => { 27 | if (timeoutRef.current) { 28 | clearTimeout(timeoutRef.current); 29 | } 30 | 31 | timeoutRef.current = setTimeout(() => { 32 | callbackRef.current(...args); 33 | }, delay); 34 | }; 35 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | let userConfig = undefined 2 | try { 3 | userConfig = await import('./v0-user-next.config') 4 | } catch (e) { 5 | // ignore error 6 | } 7 | 8 | /** @type {import('next').NextConfig} */ 9 | const nextConfig = { 10 | eslint: { 11 | ignoreDuringBuilds: true, 12 | }, 13 | typescript: { 14 | ignoreBuildErrors: true, 15 | }, 16 | images: { 17 | unoptimized: true, 18 | }, 19 | experimental: { 20 | webpackBuildWorker: true, 21 | parallelServerBuildTraces: true, 22 | parallelServerCompiles: true, 23 | }, 24 | } 25 | 26 | mergeConfig(nextConfig, userConfig) 27 | 28 | function mergeConfig(nextConfig, userConfig) { 29 | if (!userConfig) { 30 | return 31 | } 32 | 33 | for (const key in userConfig) { 34 | if ( 35 | typeof nextConfig[key] === 'object' && 36 | !Array.isArray(nextConfig[key]) 37 | ) { 38 | nextConfig[key] = { 39 | ...nextConfig[key], 40 | ...userConfig[key], 41 | } 42 | } else { 43 | nextConfig[key] = userConfig[key] 44 | } 45 | } 46 | } 47 | 48 | export default nextConfig 49 | -------------------------------------------------------------------------------- /src/lib/redis.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis" 2 | import { logger } from "./logger" 3 | 4 | let redisClient: Redis | null = null 5 | 6 | export function getRedisClient() { 7 | if (!redisClient) { 8 | redisClient = new Redis({ 9 | url: process.env.KV_REST_API_URL || "", 10 | token: process.env.KV_REST_API_TOKEN || "", 11 | }) 12 | } 13 | 14 | return redisClient 15 | } 16 | 17 | export async function getCachedData(key: string): Promise { 18 | try { 19 | const client = getRedisClient() 20 | const data = await client.get(key) 21 | 22 | if (data) { 23 | return data as T 24 | } 25 | 26 | return null 27 | } catch (error) { 28 | logger.warn("[upstash] Redis cache get error:", error) 29 | return null 30 | } 31 | } 32 | 33 | export async function setCachedData(key: string, data: any, ttlSeconds = 3600): Promise { 34 | try { 35 | const client = getRedisClient() 36 | await client.set(key, data, { ex: ttlSeconds }) 37 | } catch (error) { 38 | logger.warn("[upstash] Redis cache set error:", error) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )) 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 31 | -------------------------------------------------------------------------------- /public/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef & { ref?: React.Ref } 11 | >(({ className, children, ...props }, ref) => ( 12 | 13 | }> 14 | {children} 15 | 16 | 17 | 18 | 19 | )) 20 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 21 | 22 | const ScrollBar = React.forwardRef< 23 | React.ElementRef, 24 | React.ComponentPropsWithoutRef 25 | >(({ className, orientation = "vertical", ...props }, ref) => ( 26 | 37 | 38 | 39 | )) 40 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 41 | 42 | export { ScrollArea, ScrollBar } 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # X Alignment Chart 2 | 3 | Create D&D-style alignment charts for X (formerly Twitter) users. Place users on a Lawful-Chaotic and Good-Evil grid based on an AI analysis of their tweets or manually position them. Powered by [Exa](https://exa.ai/) and [Vercel AI SDK](https://sdk.vercel.ai). 4 | 5 | Try it here! → [magic-x-alignment-chart.vercel.app](https://dub.sh/magic-x-alignment-chart/) 6 | 7 | https://github.com/user-attachments/assets/2e5e2587-468e-4a77-ac59-742c92f8d58a 8 | 9 | ## Tutorial 10 | 11 | 1. Enter an X username in the input field 12 | 2. Choose between: 13 | - **AI Analysis** (purple button): Analyzes the user's tweets and places them on the chart 14 | - **Random Placement** (black button): Places the user randomly on the chart for manual positioning 15 | 3. View the alignment chart with positioned users 16 | 4. Drag unlocked users to reposition them (AI-placed users are locked) 17 | 5. Click on chart axis labels to learn more about each alignment 18 | 19 | 20 | ## Development Setup 21 | 22 | 1. Sign up for accounts with the AI provider you want to use (e.g., OpenAI (default), Anthropic), and obtain an API key. 23 | 2. Setup a Redis DB on [Upstash Redis](https://upstash.com/) 24 | 3. Create a `.env.local` file based on the `.env.example` template 25 | 4. `bun install` to install dependencies 26 | 5. `bun dev` to run the development server 27 | 28 | 29 | ## Deploy your own 30 | 31 | [![Deploy with Vercel](https://vercel.com/button)]([https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ff1shy-dev%2Fx-alignment-chart](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ff1shy-dev%2Fx-alignment-chart%2F&env=OPENAI_API_KEY,EXA_API_KEY&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17)) 32 | 33 | ## Credits 34 | 35 | - Original concept by [mdmatthewdc](https://x.com/mdmathewdc/status/1899767815344722325) 36 | - Draggable v0 by [rauchg](https://x.com/rauchg/status/1899895262023467035) 37 | - AI version (this one) by [f1shy-dev](https://x.com/vishyfishy2/status/1899929030620598508) 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-v0-project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev | pino-pretty", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "bun test", 11 | "deploy": "bun vercel --prod" 12 | }, 13 | "dependencies": { 14 | "@ai-sdk/openai": "latest", 15 | "@emotion/is-prop-valid": "latest", 16 | "@hookform/resolvers": "^3.9.1", 17 | "@radix-ui/react-alert-dialog": "^1.1.6", 18 | "@radix-ui/react-dialog": "^1.1.6", 19 | "@radix-ui/react-scroll-area": "latest", 20 | "@radix-ui/react-separator": "^1.1.1", 21 | "@radix-ui/react-slot": "^1.1.2", 22 | "@radix-ui/react-toast": "^1.2.4", 23 | "@radix-ui/react-tooltip": "^1.1.6", 24 | "@remix-run/react": "latest", 25 | "@sveltejs/kit": "latest", 26 | "@upstash/redis": "latest", 27 | "@vercel/analytics": "latest", 28 | "@vercel/functions": "^2.0.0", 29 | "ai": "latest", 30 | "autoprefixer": "^10.4.20", 31 | "class-variance-authority": "^0.7.1", 32 | "clsx": "^2.1.1", 33 | "cmdk": "1.0.4", 34 | "date-fns": "4.1.0", 35 | "framer-motion": "latest", 36 | "lucide-react": "^0.454.0", 37 | "next": "15.1.0", 38 | "next-themes": "^0.4.4", 39 | "pino": "^9.6.0", 40 | "react": "^19", 41 | "react-dom": "^19", 42 | "react-hook-form": "^7.54.1", 43 | "server-only": "^0.0.1", 44 | "sonner": "^1.7.1", 45 | "svelte": "latest", 46 | "tailwind-merge": "^2.5.5", 47 | "tailwindcss-animate": "^1.0.7", 48 | "ts-dedent": "^2.2.0", 49 | "vercel": "^41.4.1", 50 | "vue": "latest", 51 | "vue-router": "latest", 52 | "zod": "latest" 53 | }, 54 | "devDependencies": { 55 | "@types/chai": "^5.2.0", 56 | "@types/mocha": "^10.0.10", 57 | "@types/node": "^22", 58 | "@types/react": "^19", 59 | "@types/react-dom": "^19", 60 | "chai": "^5.2.0", 61 | "mocha": "^11.1.0", 62 | "postcss": "^8", 63 | "tailwindcss": "^3.4.17", 64 | "typescript": "^5" 65 | } 66 | } -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 259 83% 62%; 17 | --primary-foreground: 259 83% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | 78 | @keyframes spin { 79 | from { 80 | transform: rotate(0deg); 81 | } 82 | to { 83 | transform: rotate(360deg); 84 | } 85 | } 86 | 87 | .animate-spin { 88 | animation: spin 1s linear infinite; 89 | } 90 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLDivElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |
64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /src/lib/fetch-twitter-profile.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { z } from "zod" 4 | import { parseExaUserString, XProfile } from "@/lib/parse-exa-profile" 5 | import { logger } from "@/lib/logger" 6 | 7 | const ExaResponseSchema = z.object({ 8 | results: z.array( 9 | z.object({ 10 | id: z.string(), 11 | title: z.string(), 12 | url: z.string(), 13 | publishedDate: z.string().optional(), 14 | author: z.string(), 15 | text: z.string(), 16 | }), 17 | ), 18 | requestId: z.string(), 19 | costDollars: z.object({ 20 | total: z.number(), 21 | contents: z.object({ 22 | text: z.number(), 23 | }), 24 | }), 25 | }) 26 | 27 | export async function fetchTwitterProfile(username: string): Promise { 28 | if (!username) return null 29 | const cleanUsername = username.trim().replace(/^@/, "") 30 | 31 | try { 32 | logger.info(`Fetching tweets for @${cleanUsername} from EXA API`) 33 | 34 | const response = await fetch("https://api.exa.ai/contents", { 35 | method: "POST", 36 | headers: { 37 | "Content-Type": "application/json", 38 | Authorization: `Bearer ${process.env.EXA_API_KEY || ""}`, 39 | }, 40 | body: JSON.stringify({ 41 | ids: [`https://x.com/${cleanUsername}`], 42 | text: true, 43 | livecrawl: "always", 44 | }), 45 | }) 46 | 47 | if (!response.ok) { 48 | const errorText = await response.text() 49 | throw new Error(`EXA API error: ${response.status} ${errorText}`) 50 | } 51 | 52 | const rawData = await response.json() 53 | 54 | const data = rawData.data ? rawData.data : rawData 55 | const validatedData = ExaResponseSchema.parse(data) 56 | if (validatedData.results.length === 0) { 57 | return null 58 | } 59 | const parsedData = parseExaUserString(validatedData.results[0].text) 60 | if (!parsedData.success || !parsedData.data) { 61 | logger.error("Error parsing tweets:", parsedData.error) 62 | return null 63 | } 64 | 65 | const tweets = parsedData.data.tweets 66 | 67 | logger.info(`Found ${tweets.length} tweets for @${cleanUsername}`) 68 | logger.debug(tweets.slice(0, 20).map((tweet) => 69 | `${tweet.text} 70 | 71 | ❤️ ${tweet.favorite_count}, 💬 ${tweet.reply_count}, 🔄 ${tweet.retweet_count}, 🔗 ${tweet.quote_count} 72 | --------------------------------` 73 | ).join("\n")) 74 | 75 | return parsedData.data 76 | } catch (error) { 77 | logger.error(`Error fetching tweets for @${cleanUsername} from Exa:`, error) 78 | return null 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./src/**/*.{js,ts,jsx,tsx,mdx}", 7 | ], 8 | theme: { 9 | extend: { 10 | screens: { 11 | xs: "480px", 12 | }, 13 | colors: { 14 | background: 'hsl(var(--background))', 15 | foreground: 'hsl(var(--foreground))', 16 | card: { 17 | DEFAULT: 'hsl(var(--card))', 18 | foreground: 'hsl(var(--card-foreground))' 19 | }, 20 | popover: { 21 | DEFAULT: 'hsl(var(--popover))', 22 | foreground: 'hsl(var(--popover-foreground))' 23 | }, 24 | primary: { 25 | DEFAULT: 'hsl(var(--primary))', 26 | foreground: 'hsl(var(--primary-foreground))' 27 | }, 28 | secondary: { 29 | DEFAULT: 'hsl(var(--secondary))', 30 | foreground: 'hsl(var(--secondary-foreground))' 31 | }, 32 | muted: { 33 | DEFAULT: 'hsl(var(--muted))', 34 | foreground: 'hsl(var(--muted-foreground))' 35 | }, 36 | accent: { 37 | DEFAULT: 'hsl(var(--accent))', 38 | foreground: 'hsl(var(--accent-foreground))' 39 | }, 40 | destructive: { 41 | DEFAULT: 'hsl(var(--destructive))', 42 | foreground: 'hsl(var(--destructive-foreground))' 43 | }, 44 | border: 'hsl(var(--border))', 45 | input: 'hsl(var(--input))', 46 | ring: 'hsl(var(--ring))', 47 | chart: { 48 | '1': 'hsl(var(--chart-1))', 49 | '2': 'hsl(var(--chart-2))', 50 | '3': 'hsl(var(--chart-3))', 51 | '4': 'hsl(var(--chart-4))', 52 | '5': 'hsl(var(--chart-5))' 53 | }, 54 | sidebar: { 55 | DEFAULT: 'hsl(var(--sidebar-background))', 56 | foreground: 'hsl(var(--sidebar-foreground))', 57 | primary: 'hsl(var(--sidebar-primary))', 58 | 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', 59 | accent: 'hsl(var(--sidebar-accent))', 60 | 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', 61 | border: 'hsl(var(--sidebar-border))', 62 | ring: 'hsl(var(--sidebar-ring))' 63 | } 64 | }, 65 | borderRadius: { 66 | lg: 'var(--radius)', 67 | md: 'calc(var(--radius) - 2px)', 68 | sm: 'calc(var(--radius) - 4px)' 69 | }, 70 | keyframes: { 71 | 'accordion-down': { 72 | from: { 73 | height: '0' 74 | }, 75 | to: { 76 | height: 'var(--radix-accordion-content-height)' 77 | } 78 | }, 79 | 'accordion-up': { 80 | from: { 81 | height: 'var(--radix-accordion-content-height)' 82 | }, 83 | to: { 84 | height: '0' 85 | } 86 | } 87 | }, 88 | animation: { 89 | 'accordion-down': 'accordion-down 0.2s ease-out', 90 | 'accordion-up': 'accordion-up 0.2s ease-out' 91 | } 92 | } 93 | }, 94 | plugins: [require("tailwindcss-animate")], 95 | }; 96 | export default config; 97 | -------------------------------------------------------------------------------- /src/lib/parse-exa-profile.ts: -------------------------------------------------------------------------------- 1 | export interface ExaApiResponse { 2 | data: { 3 | results: ExaResult[]; 4 | requestId: string; 5 | costDollars: { 6 | total: number; 7 | contents: { 8 | text: number; 9 | }; 10 | }; 11 | }; 12 | } 13 | 14 | export interface ExaResult { 15 | id: string; 16 | title: string; 17 | url: string; 18 | publishedDate: string; 19 | author: string; 20 | text: string; 21 | } 22 | 23 | 24 | export type Tweet = { 25 | text: string, 26 | created_at: string, 27 | favorite_count: number, 28 | quote_count: number, 29 | reply_count: number, 30 | retweet_count: number, 31 | is_quote_status?: boolean, 32 | 33 | } 34 | 35 | export type XProfile = { 36 | tweets: Tweet[], 37 | bio?: string, 38 | profile_url?: string, 39 | name?: string, 40 | created_at?: string, 41 | followers_count?: number, 42 | statuses_count?: number, 43 | location?: string, 44 | } 45 | 46 | export const parseExaUserString = (raw_string: string) => { 47 | try { 48 | let composed_object: XProfile = { 49 | tweets: [], 50 | } 51 | const base_yml = raw_string; 52 | const profile_yml = base_yml.match(/^.*?statuses_count:\s*\d+/)?.[0]; 53 | const tweets_yml = base_yml.replace(profile_yml!, '').replace('| location:', '').trim(); 54 | 55 | const PROFILE_PATTERNS = { 56 | bio: /^(.*?)(?=\| (?:profile_url:|name:|created_at:|followers_count:|favourites_count:|friends_count:|media_count:|statuses_count:|location:))/, 57 | profile_url: /\| profile_url:\s*([^\s|]+)/, 58 | name: /\| name:\s*([^|]+)/, 59 | created_at: /\| created_at:\s*([^|]+)/, 60 | followers_count: /\| followers_count:\s*([^|]+)/, 61 | statuses_count: /\| statuses_count:\s*([^|]+)/, 62 | location: /\| location:\s*([^|]+)/, 63 | } as const; 64 | 65 | const num_keys = ['followers_count', 'favourites_count', 'friends_count', 'media_count', 'statuses_count', 'favorite_count', 'quote_count', 'reply_count', 'retweet_count']; 66 | for (const [key, pattern] of Object.entries(PROFILE_PATTERNS)) { 67 | const match = profile_yml?.match(pattern); 68 | if (match) { 69 | Object.assign(composed_object, { [key]: num_keys.includes(key) ? parseInt(match[1].trim()) : match[1].trim() }); 70 | } 71 | } 72 | 73 | const each_tweet_yml = tweets_yml.split(/\| lang: [a-z]{2,3}(?:\s|$)/); 74 | 75 | const TWEET_PATTERNS = { 76 | created_at: /\| created_at:\s*([^|]+)/, 77 | favorite_count: /\| favorite_count:\s*([^|]+)/, 78 | quote_count: /\| quote_count:\s*([^|]+)/, 79 | reply_count: /\| reply_count:\s*([^|]+)/, 80 | retweet_count: /\| retweet_count:\s*([^|]+)/, 81 | is_quote_status: /\| is_quote_status:\s*([^|]+)/, 82 | } as const; 83 | 84 | 85 | for (const tweet of each_tweet_yml) { 86 | const tweet_object = {}; 87 | for (const [key, pattern] of Object.entries(TWEET_PATTERNS)) { 88 | const match = tweet.match(pattern); 89 | if (match) { 90 | if (key === "is_quote_status") { 91 | Object.assign(tweet_object, { [key]: match[1].trim() === "True" }); 92 | } else { 93 | Object.assign(tweet_object, { [key]: num_keys.includes(key) ? parseInt(match[1].trim()) : match[1].trim() }); 94 | } 95 | } 96 | } 97 | 98 | const tweet_text = tweet.replace(TWEET_PATTERNS.created_at, '').replace(TWEET_PATTERNS.favorite_count, '').replace(TWEET_PATTERNS.quote_count, '').replace(TWEET_PATTERNS.reply_count, '').replace(TWEET_PATTERNS.retweet_count, '').replace(TWEET_PATTERNS.is_quote_status, '').trim(); 99 | Object.assign(tweet_object, { text: tweet_text }); 100 | 101 | composed_object.tweets.push(tweet_object as Tweet); 102 | } 103 | 104 | composed_object.tweets = composed_object.tweets.filter((tweet) => tweet.text.length > 0); 105 | 106 | return { success: true, data: composed_object }; 107 | } catch (error) { 108 | console.error(error); 109 | return { success: false, error: error }; 110 | } 111 | } -------------------------------------------------------------------------------- /src/lib/load-avatar.ts: -------------------------------------------------------------------------------- 1 | export const loadImage = (url: string): Promise => { 2 | return new Promise((resolve, reject) => { 3 | const img = new Image(); 4 | img.crossOrigin = "anonymous"; 5 | 6 | const timeout = setTimeout(() => { 7 | reject(new Error("Image load timeout")); 8 | }, 5000); 9 | 10 | img.onload = () => { 11 | clearTimeout(timeout); 12 | resolve(img); 13 | }; 14 | 15 | img.onerror = () => { 16 | clearTimeout(timeout); 17 | reject(new Error("Image load error")); 18 | }; 19 | 20 | img.src = url; 21 | }); 22 | }; 23 | 24 | export const analyzeColorfulness = ( 25 | img: HTMLImageElement, 26 | canvas: HTMLCanvasElement 27 | ): number => { 28 | try { 29 | if (!img.complete || img.naturalWidth === 0) { 30 | return 0; 31 | } 32 | 33 | const ctx = canvas.getContext("2d"); 34 | if (!ctx) return 0; 35 | 36 | canvas.width = img.naturalWidth; 37 | canvas.height = img.naturalHeight; 38 | 39 | ctx.drawImage(img, 0, 0); 40 | 41 | const sampleSize = 20; 42 | const uniqueColors = new Set(); 43 | 44 | for (let y = 0; y < sampleSize; y++) { 45 | for (let x = 0; x < sampleSize; x++) { 46 | const sampleX = Math.floor((x / sampleSize) * canvas.width); 47 | const sampleY = Math.floor((y / sampleSize) * canvas.height); 48 | 49 | try { 50 | const data = ctx.getImageData(sampleX, sampleY, 1, 1).data; 51 | const r = data[0]; 52 | const g = data[1]; 53 | const b = data[2]; 54 | 55 | const colorKey = `${r},${g},${b}`; 56 | uniqueColors.add(colorKey); 57 | } catch (error) { 58 | // Ignore errors for individual pixels 59 | } 60 | } 61 | } 62 | 63 | return uniqueColors.size; 64 | } catch (error) { 65 | // If any error occurs, return 0 66 | return 0; 67 | } 68 | }; 69 | 70 | /** 71 | * Compares two possible avatar URLs for an X username (with and without @ prefix) 72 | * and returns the URL that produces the more colorful/detailed image 73 | */ 74 | export const getBestAvatarUrl = async (username: string): Promise => { 75 | const cleanUsername = username.trim().replace(/^@/, ""); 76 | const withAtUrl = `https://unavatar.io/x/@${cleanUsername}`; 77 | const withoutAtUrl = `https://unavatar.io/x/${cleanUsername}`; 78 | 79 | let finalUrl = withoutAtUrl; // Default fallback 80 | 81 | try { 82 | const withAtCanvas = document.createElement("canvas"); 83 | const withoutAtCanvas = document.createElement("canvas"); 84 | 85 | const [withAtImg, withoutAtImg] = await Promise.allSettled([ 86 | loadImage(withAtUrl), 87 | loadImage(withoutAtUrl) 88 | ]); 89 | 90 | // Compare colorfulness if both images loaded successfully 91 | if (withAtImg.status === "fulfilled" && withoutAtImg.status === "fulfilled") { 92 | const withAtColorfulness = analyzeColorfulness(withAtImg.value, withAtCanvas); 93 | const withoutAtColorfulness = analyzeColorfulness(withoutAtImg.value, withoutAtCanvas); 94 | 95 | // Choose the image with more unique colors 96 | if (withAtColorfulness > withoutAtColorfulness) { 97 | finalUrl = withAtUrl; 98 | } else { 99 | finalUrl = withoutAtUrl; 100 | } 101 | } 102 | // If only one loaded successfully, use that one 103 | else if (withAtImg.status === "fulfilled") { 104 | finalUrl = withAtUrl; 105 | } else if (withoutAtImg.status === "fulfilled") { 106 | finalUrl = withoutAtUrl; 107 | } 108 | } catch (error) { 109 | console.error("Error comparing avatar images:", error); 110 | } 111 | 112 | return finalUrl; 113 | }; 114 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { buttonVariants } from "@/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )) 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ) 60 | AlertDialogHeader.displayName = "AlertDialogHeader" 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ) 74 | AlertDialogFooter.displayName = "AlertDialogFooter" 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )) 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )) 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )) 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | } 142 | -------------------------------------------------------------------------------- /src/app/actions/analyze-tweets.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | import "server-only" 3 | import { dedent } from "ts-dedent" 4 | import { openai } from "@ai-sdk/openai" 5 | import { CoreMessage, generateObject } from "ai" 6 | import { z } from "zod" 7 | import { getCachedData, setCachedData } from "../../lib/redis" 8 | import { fetchTwitterProfile } from "../../lib/fetch-twitter-profile" 9 | import { logger } from "@/lib/logger" 10 | import { track } from '@vercel/analytics/server'; 11 | import { waitUntil } from "@vercel/functions"; 12 | const AlignmentSchema = z.object({ 13 | explanation: z.string().describe("Your brief-ish explanation/reasoning for the given alignment assessment"), 14 | lawfulChaotic: z.number().min(-100).max(100).describe("A score from -100 (lawful) to 100 (chaotic)"), 15 | goodEvil: z.number().min(-100).max(100).describe("A score from -100 (good) to 100 (evil)"), 16 | }); 17 | 18 | export type AlignmentAnalysis = z.infer 19 | 20 | export async function analyseUser(username: string): Promise { 21 | const cleanUsername = username.trim().replace(/^@/, "") 22 | const cacheKey = `analysis-v2:${cleanUsername}` 23 | 24 | try { 25 | const cachedAnalysis = await getCachedData(cacheKey) 26 | 27 | if (cachedAnalysis) { 28 | logger.info(`Using cached analysis for @${cleanUsername}`) 29 | logger.info(cachedAnalysis) 30 | 31 | waitUntil(track("analysis_cached", { 32 | username: cleanUsername, 33 | lawful_chaotic: cachedAnalysis.lawfulChaotic, 34 | good_evil: cachedAnalysis.goodEvil, 35 | })) 36 | return { ...cachedAnalysis, cached: true, isError: false } 37 | } 38 | 39 | logger.info(`Analyzing tweets for @${cleanUsername}`) 40 | 41 | const profile = await fetchTwitterProfile(username) 42 | if (!profile) { 43 | throw new Error(`No profile found for @${cleanUsername}`) 44 | } 45 | 46 | const profile_str = JSON.stringify({ ...profile, tweets: undefined }, null, 2) 47 | 48 | const tweetTexts = profile.tweets.map((tweet) => 49 | ` 50 | ${tweet.text} 51 | ${tweet.favorite_count} likes, ${tweet.reply_count} replies, ${tweet.retweet_count} retweets, ${tweet.quote_count} quotes 52 | ` 53 | ).join("\n\n") 54 | 55 | const messages = [ 56 | { 57 | role: "system", 58 | content: dedent` 59 | Analyze the following tweets the given from Twitter user and determine their alignment on a D&D-style alignment chart. 60 | 61 | For lawful-chaotic axis: 62 | - Lawful (-100): Follows rules, traditions, and social norms. They value tradition, loyalty, and order. 63 | - Neutral (0): Balanced approach to rules and freedom 64 | - Chaotic (100): Rebels against convention, valuing personal freedom - follows their own moral compass regardless of rules or traditions 65 | 66 | For good-evil axis: 67 | - Good (-100): Altruistic, compassionate, puts others first 68 | - Neutral (0): Balanced self-interest and concern for others 69 | - Evil (100): Selfish, manipulative, or harmful to others. Some are motivated by greed, hatred, or lust for power. 70 | 71 | Based only on these tweets, provide a numerical assessment of this user's alignment. Be willing to move to any side/extreme! 72 | 73 | Since this is a bit of fun, be willing to overly exaggerate if the user has a specific trait expressed barely - e.g. if they are evil at some point then make sure to express it! - I don't just want everyone to end up as chaotic-neutral in the end... However don't always exaggerate a user's chaotic characteristic, you can also try to exaggerate their lawful or good/evil traits if they are more pronounced. Just be fun with it. 74 | 75 | For the explaination, try to avoid overly waffling - but show your reasoning behind your judgement. You can mention specific things about their user like mentioned traits/remarks or projects/etc - the more personalised the better. 76 | `.trim() 77 | }, 78 | { 79 | role: "user", 80 | content: 81 | dedent`Username: @${username} 82 | 83 | 84 | ${profile_str} 85 | 86 | 87 | 88 | ${tweetTexts} 89 | `.trim() 90 | } 91 | ] satisfies CoreMessage[] 92 | 93 | 94 | const { object } = await generateObject({ 95 | model: openai("gpt-4o-mini"), 96 | temperature: 0.8, 97 | schema: AlignmentSchema, 98 | messages 99 | }) 100 | 101 | // Cache for 2 weeks - maybe the users will have more tweets by then... 102 | await setCachedData(cacheKey, object, 604_800) 103 | 104 | waitUntil(track("analysis_complete", { 105 | username: cleanUsername, 106 | lawful_chaotic: object.lawfulChaotic, 107 | good_evil: object.goodEvil, 108 | })) 109 | 110 | return { ...object, cached: false, isError: false } 111 | } catch (error) { 112 | logger.error(`Error analyzing tweets for @${cleanUsername}:`, error) 113 | return { 114 | lawfulChaotic: 0, 115 | goodEvil: 0, 116 | explanation: `Error analyzing tweets for @${cleanUsername}... Check you used a valid username and try again later.`, 117 | cached: false, 118 | isError: true, 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /src/lib/test_data/results-Inveeest.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "results": [ 4 | { 5 | "id": "x.com/Inveeest", 6 | "title": "@Inveeest on X: @sa1k0s @benhylak Yes but way too costly", 7 | "url": "http://x.com/Inveeest", 8 | "publishedDate": "2024-05-04T22:24:09.000Z", 9 | "author": "Inveeest", 10 | "text": "| name: Jame | created_at: Sat May 04 22:24:09 +0000 2024 | favourites_count: 871 | followers_count: 12 | friends_count: 211 | media_count: 59 | statuses_count: 265 | location: 🇨🇦 @sa1k0s @benhylak Yes but way too costly| created_at: Mon Mar 03 21:02:20 +0000 2025 | favorite_count: 6 | reply_count: 1 | lang: en @AlertesInfos Pas confirmé du tout si jamais, les sources n'ont rien pour assuré cela il communiquera dans les 72h\nhttps://t.co/4SEtlXZPzL?| created_at: Mon Jan 06 07:12:31 +0000 2025 | favorite_count: 5 | retweet_count: 1 | lang: fr @StockMKTNewz Although it started well https://t.co/RSdj9SIfqm| created_at: Wed Dec 18 21:27:29 +0000 2024 | favorite_count: 4 | lang: en @elpistollero_ @RevatiScorpion @t0rpack Yes bro mais devenir millionaire en étant salarié uniquement grâce à la bourse c'est pas impossible mais bon 😅 faut être entrepreneur ou être capable de répondre et de scale des opportunités comme tu dis| created_at: Sat Jul 06 18:33:29 +0000 2024 | favorite_count: 3 | reply_count: 1 | lang: fr @KrabsDividende @NotionRaphaxelo 27% 😬| created_at: Wed Jul 03 02:15:49 +0000 2024 | favorite_count: 2 | reply_count: 2 | lang: und @levelsio @boltdotnew @SynthflowAI @coderabbitai @scaledropship Are you gonna scale this game in something bigger with a team or something else ? With you're visibility rn u have the opportunity to create something huge but u look limited by only using Cursor and your knowledge| created_at: Thu Mar 06 23:07:23 +0000 2025 | favorite_count: 5 | reply_count: 1 | lang: en Love grok so much for debugging, he's not like Claude 3.7\nPrecision > sloppy guesses| created_at: Fri Mar 07 06:40:09 +0000 2025 | favorite_count: 1 | is_quote_status: True | lang: en @mayfer @yacineMTB It keep sending the whole code even after the setup nearly at every prompt (only sending less in Haiku request), which become way too expensive over time. I was working on a small project and it costed like 30$ in 2 days, switched to Cursor https://t.co/mfKxKML7Hr| created_at: Sun Mar 02 21:09:29 +0000 2025 | favorite_count: 2 | lang: en @levelsio Gonna become a real competitor to microsoft flight simulator 🤣| created_at: Thu Mar 06 23:17:12 +0000 2025 | favorite_count: 4 | reply_count: 1 | lang: en @ns123abc @grok Ain't getting any response anymore from him sadly 🥹| created_at: Fri Mar 07 09:16:34 +0000 2025 | favorite_count: 2 | lang: en @grok @ns123abc what do you think of @AskPerplexity| created_at: Fri Mar 07 06:27:04 +0000 2025 | favorite_count: 2 | lang: en @donvito https://t.co/Vx7j2ZMiYu transform your map into this haha| created_at: Fri Mar 07 09:15:47 +0000 2025 | favorite_count: 3 | is_quote_status: True | lang: en @matteoinvest @Barth36512091 Il faut un sale capital ou plus d'une cinquantaine d'années d'intéret composé 💀| created_at: Sat Jun 29 04:07:05 +0000 2024 | favorite_count: 1 | reply_count: 1 | lang: fr @KrabsDividende Mini perf sur NVDA, 5.44% S&P 500 et 4% NASDAQ 100 en 3 mois https://t.co/MmTS92WTqe| created_at: Wed Jul 03 02:14:11 +0000 2024 | favorite_count: 1 | reply_count: 1 | lang: nl @laktozmentes5 @yacineMTB Claude| created_at: Sun Mar 02 21:26:30 +0000 2025 | favorite_count: 1 | reply_count: 1 | lang: en @donvito Everything else than the plane look so similar to the pieter one. Crazy that u got nearly the same result| created_at: Fri Mar 07 07:00:52 +0000 2025 | favorite_count: 1 | lang: en @yacineMTB A lot of people don't even know what's chatgpt😅| created_at: Thu Mar 06 18:35:35 +0000 2025 | favorite_count: 1 | lang: en @yacineMTB bro is a meme generator| created_at: Fri Mar 07 04:07:54 +0000 2025 | favorite_count: 1 | lang: en @ns123abc @grok Replied to everyone at the same time https://t.co/Awim4QyshG| created_at: Fri Mar 07 06:26:20 +0000 2025 | favorite_count: 1 | reply_count: 1 | lang: en @maybeblackboy @Arkunir @MrBeast 🤓| created_at: Thu Jan 16 23:38:09 +0000 2025 | favorite_count: 1 | lang: qme @DataDeLaurier @grok @ns123abc https://t.co/kvdBSgdxgW| created_at: Fri Mar 07 06:32:57 +0000 2025 | favorite_count: 1 | reply_count: 1 | lang: qme @IceSolst I'll try this ! It was with two iPhone speakers at max nearby, they may not produce enough sounds| created_at: Mon Mar 03 19:43:39 +0000 2025 | favorite_count: 1 | lang: en @grok @ns123abc no waaaay| created_at: Fri Mar 07 06:27:48 +0000 2025 | favorite_count: 1 | lang: en @CryptoEtFi @NewsDividendes C'est interactive broker ?| created_at: Thu Jul 18 19:00:41 +0000 2024 | reply_count: 1 | lang: fr @MMMTwealth I dont understand why does they have that much free cash and they dont clear the debt ? If its long term interest are prolly high ?| created_at: Thu Aug 01 18:22:20 +0000 2024 | favorite_count: 1 | reply_count: 1 | lang: en @laktozmentes5 @yacineMTB Yeah men with only one /compact command| created_at: Sun Mar 02 21:39:05 +0000 2025 | favorite_count: 1 | lang: en @levelsio @boltdotnew @SynthflowAI @coderabbitai @scaledropship I completly agree ! You could make something like https://t.co/fWK6oVTuuF, a whole game, browser-based and kept as simple and authentic as it used to be.| created_at: Thu Mar 06 23:14:09 +0000 2025 | favorite_count: 1 | lang: en @ns123abc @grok slow atm, prolly due to the number of request 😅| created_at: Fri Mar 07 06:24:55 +0000 2025 | favorite_count: 1 | reply_count: 1 | lang: en @levelsio @boltdotnew @SynthflowAI @coderabbitai @scaledropship 100k soon🤩| created_at: Thu Mar 06 23:03:39 +0000 2025 | favorite_count: 1 | lang: en @IceSolst Its not 100% accurate| created_at: Mon Mar 03 19:09:35 +0000 2025 | favorite_count: 1 | reply_count: 1 | lang: en @ns123abc @elonmusk @grok write us another reminder in this genre| created_at: Fri Mar 07 06:36:26 +0000 2025 | favorite_count: 1 | lang: en" 11 | } 12 | ], 13 | "requestId": "45d0d8e16c75a8243c1c8a2b3db244c2", 14 | "costDollars": { 15 | "total": 0.001, 16 | "contents": { 17 | "text": 0.001 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/lib/indexed-db.ts: -------------------------------------------------------------------------------- 1 | import { AlignmentAnalysis } from "@/app/actions/analyze-tweets"; 2 | import { Placement } from "@/app/page"; 3 | import { logger } from "./logger"; 4 | 5 | export interface StoredPlacement { 6 | id: string; 7 | src: string; 8 | position: { 9 | x: number; 10 | y: number; 11 | }; 12 | username?: string; 13 | analysis?: AlignmentAnalysis; 14 | isAiPlaced?: boolean; 15 | timestamp?: string; // ISO string 16 | } 17 | 18 | const DB_NAME = 'alignment-chart-db'; 19 | const DB_VERSION = 1; 20 | const USERS_STORE = 'users'; 21 | 22 | interface IndexedDBInstance { 23 | db: IDBDatabase | null; 24 | isInitializing: boolean; 25 | onInitialize: Array<() => void>; 26 | } 27 | 28 | const indexedDB: IndexedDBInstance = { 29 | db: null, 30 | isInitializing: false, 31 | onInitialize: [], 32 | }; 33 | 34 | export function initIndexedDB(): Promise { 35 | return new Promise((resolve, reject) => { 36 | if (indexedDB.db) { 37 | resolve(); 38 | return; 39 | } 40 | 41 | if (indexedDB.isInitializing) { 42 | indexedDB.onInitialize.push(() => resolve()); 43 | return; 44 | } 45 | 46 | indexedDB.isInitializing = true; 47 | 48 | if (!window.indexedDB) { 49 | console.error("Your browser doesn't support IndexedDB"); 50 | indexedDB.isInitializing = false; 51 | reject(new Error("IndexedDB not supported")); 52 | return; 53 | } 54 | 55 | const request = window.indexedDB.open(DB_NAME, DB_VERSION); 56 | 57 | request.onerror = (event) => { 58 | console.error("IndexedDB error:", event); 59 | indexedDB.isInitializing = false; 60 | reject(new Error("Failed to open IndexedDB")); 61 | }; 62 | 63 | request.onsuccess = (event) => { 64 | indexedDB.db = (event.target as IDBOpenDBRequest).result; 65 | indexedDB.isInitializing = false; 66 | 67 | indexedDB.onInitialize.forEach(callback => callback()); 68 | indexedDB.onInitialize = []; 69 | 70 | resolve(); 71 | }; 72 | 73 | request.onupgradeneeded = (event) => { 74 | const db = (event.target as IDBOpenDBRequest).result; 75 | 76 | if (!db.objectStoreNames.contains(USERS_STORE)) { 77 | db.createObjectStore(USERS_STORE, { keyPath: 'id' }); 78 | } 79 | }; 80 | }); 81 | } 82 | 83 | export async function cachePlacementsLocally(placements: Placement[]): Promise { 84 | const stored_placements: StoredPlacement[] = placements.map((item) => ({ 85 | id: item.id, 86 | src: item.src, 87 | position: item.position, 88 | username: item.username, 89 | analysis: item.analysis, 90 | isAiPlaced: item.isAiPlaced, 91 | timestamp: item.timestamp?.toISOString(), 92 | })); 93 | 94 | logger.debug("Saving users to IndexedDB", stored_placements); 95 | await initIndexedDB(); 96 | 97 | if (!indexedDB.db) { 98 | throw new Error("IndexedDB not initialized"); 99 | } 100 | 101 | return new Promise((resolve, reject) => { 102 | const transaction = indexedDB.db!.transaction([USERS_STORE], 'readwrite'); 103 | const store = transaction.objectStore(USERS_STORE); 104 | 105 | const clearRequest = store.clear(); 106 | 107 | clearRequest.onsuccess = () => { 108 | let remaining = placements.length; 109 | 110 | if (placements.length === 0) { 111 | resolve(); 112 | return; 113 | } 114 | 115 | stored_placements.forEach(placement => { 116 | const stored_placement: StoredPlacement = { 117 | ...placement, 118 | timestamp: placement.timestamp || new Date().toISOString() 119 | }; 120 | 121 | const request = store.add(stored_placement); 122 | 123 | request.onsuccess = () => { 124 | remaining--; 125 | if (remaining === 0) { 126 | resolve(); 127 | } 128 | }; 129 | 130 | request.onerror = (event) => { 131 | logger.error("Error adding placement to IndexedDB:", event); 132 | reject(new Error("Failed to add placement to IndexedDB")); 133 | }; 134 | }); 135 | }; 136 | 137 | clearRequest.onerror = (event) => { 138 | logger.error("Error clearing IndexedDB store:", event); 139 | reject(new Error("Failed to clear IndexedDB store")); 140 | }; 141 | 142 | transaction.onerror = (event) => { 143 | logger.error("Transaction error:", event); 144 | reject(new Error("IndexedDB transaction failed")); 145 | }; 146 | }); 147 | } 148 | 149 | export async function removeCachedPlacement(id: string): Promise { 150 | await initIndexedDB(); 151 | if (!indexedDB.db) throw new Error("IndexedDB not initialized"); 152 | 153 | return new Promise((resolve, reject) => { 154 | const transaction = indexedDB.db!.transaction([USERS_STORE], 'readwrite'); 155 | const store = transaction.objectStore(USERS_STORE); 156 | 157 | const request = store.delete(id); 158 | 159 | request.onsuccess = () => { 160 | resolve(); 161 | }; 162 | 163 | request.onerror = (event) => { 164 | console.error("Error deleting user from IndexedDB:", event); 165 | reject(new Error("Failed to delete user from IndexedDB")); 166 | }; 167 | }); 168 | } 169 | 170 | export async function loadCachedPlacements(): Promise { 171 | await initIndexedDB(); 172 | if (!indexedDB.db) return []; 173 | 174 | return new Promise((resolve, reject) => { 175 | const transaction = indexedDB.db!.transaction([USERS_STORE], 'readonly'); 176 | const store = transaction.objectStore(USERS_STORE); 177 | const request = store.getAll(); 178 | 179 | request.onsuccess = () => { 180 | const placements = request.result as StoredPlacement[]; 181 | resolve(placements); 182 | }; 183 | 184 | request.onerror = (event) => { 185 | console.error("Error loading users from IndexedDB:", event); 186 | reject(new Error("Failed to load users from IndexedDB")); 187 | }; 188 | }); 189 | } 190 | 191 | export async function clearLocalCache(): Promise { 192 | await initIndexedDB(); 193 | if (!indexedDB.db) return; 194 | 195 | 196 | return new Promise((resolve, reject) => { 197 | const transaction = indexedDB.db!.transaction([USERS_STORE], 'readwrite'); 198 | const store = transaction.objectStore(USERS_STORE); 199 | const request = store.clear(); 200 | 201 | request.onsuccess = () => { 202 | resolve(); 203 | }; 204 | 205 | request.onerror = (event) => { 206 | console.error("Error clearing IndexedDB:", event); 207 | reject(new Error("Failed to clear IndexedDB")); 208 | }; 209 | }); 210 | } 211 | -------------------------------------------------------------------------------- /src/app/components/analysis-panel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect, useRef } from "react"; 4 | import Image from "next/image"; 5 | import { motion, AnimatePresence } from "framer-motion"; 6 | import { 7 | MessageSquare, 8 | X, 9 | Sparkles, 10 | SquareDashedMousePointer, 11 | } from "lucide-react"; 12 | import { Button } from "@/components/ui/button"; 13 | import { Card } from "@/components/ui/card"; 14 | import type { AlignmentAnalysis } from "../actions/analyze-tweets"; 15 | import { cn } from "@/lib/utils"; 16 | import { ScrollArea } from "@/components/ui/scroll-area"; 17 | import { Separator } from "@/components/ui/separator"; 18 | 19 | export function AnalysisPanel({ 20 | analyses, 21 | newAnalysisId, 22 | children, 23 | }: { 24 | analyses: Array<{ 25 | id: string; 26 | username: string; 27 | imageSrc: string; 28 | analysis: AlignmentAnalysis; 29 | timestamp: Date; 30 | }>; 31 | newAnalysisId: string | null; 32 | children?: React.ReactNode; 33 | }) { 34 | const [isOpen, setIsOpen] = useState(false); 35 | const [hasNewAnalysis, setHasNewAnalysis] = useState(false); 36 | const scrollAreaRef = useRef(null); 37 | 38 | // Scroll to bottom when new analyses are added 39 | useEffect(() => { 40 | if (isOpen && scrollAreaRef.current) { 41 | scrollAreaRef.current.scrollTop = scrollAreaRef.current.scrollHeight; 42 | } 43 | }, [analyses, isOpen]); 44 | 45 | // Handle new analysis notification 46 | useEffect(() => { 47 | if (newAnalysisId && !isOpen) { 48 | setHasNewAnalysis(true); 49 | } 50 | }, [newAnalysisId, isOpen]); 51 | 52 | // Reset notification when panel is opened 53 | useEffect(() => { 54 | if (isOpen) { 55 | setHasNewAnalysis(false); 56 | } 57 | }, [isOpen]); 58 | 59 | // Get alignment name based on scores 60 | const getAlignmentName = (lawfulChaotic: number, goodEvil: number) => { 61 | const lawfulAxis = 62 | lawfulChaotic < -33 63 | ? "Lawful" 64 | : lawfulChaotic > 33 65 | ? "Chaotic" 66 | : "Neutral"; 67 | const goodAxis = 68 | goodEvil < -33 ? "Good" : goodEvil > 33 ? "Evil" : "Neutral"; 69 | 70 | // Special case for true neutral 71 | if (lawfulAxis === "Neutral" && goodAxis === "Neutral") { 72 | return "True Neutral"; 73 | } 74 | 75 | return `${lawfulAxis} ${goodAxis}`; 76 | }; 77 | 78 | return ( 79 |
80 | 81 | {isOpen && ( 82 | 89 | 90 |
91 |
92 |
93 |
94 | 95 |

AI X alignment analysis

96 |
97 | 105 |
106 |
107 | 111 | {analyses.length === 0 ? ( 112 |
113 | 114 |

115 | You haven't analysed any X users yet. Use the purple 116 | button with the sparkles to analyze a user's vibe using 117 | AI. 118 |

119 |
120 | ) : ( 121 |
122 |
123 | {analyses.map((item) => ( 124 |
130 |
131 |
132 | {`@${item.username}`} 139 |
140 |
141 |
142 |
143 | 144 | @{item.username} 145 | 146 | 147 | {new Date(item.timestamp).toLocaleTimeString( 148 | [], 149 | { hour: "2-digit", minute: "2-digit" } 150 | )} 151 | 152 |
153 |
154 |
155 | 156 | 157 | {getAlignmentName( 158 | item.analysis.lawfulChaotic, 159 | item.analysis.goodEvil 160 | )} 161 | 162 |
163 |
164 | 165 | 166 | Lawful-Chaotic: 167 | {" "} 168 | {item.analysis.lawfulChaotic} 169 | 170 | 171 | 172 | Good-Evil: 173 | {" "} 174 | {item.analysis.goodEvil} 175 | 176 |

177 | {item.analysis.explanation} 178 |

179 |
180 |
181 |
182 |
183 | ))} 184 |
185 |
186 | )} 187 |
188 |
189 |
190 |
191 | )} 192 |
193 | 194 |
195 | 198 | 206 | 207 | {hasNewAnalysis && ( 208 | 213 | ! 214 | 215 | )} 216 | 217 | 218 | {children} 219 |
220 |
221 | ); 222 | } 223 | -------------------------------------------------------------------------------- /src/lib/test_data/results-mazeincoding.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "results": [ 4 | { 5 | "id": "https://x.com/mazeincoding", 6 | "title": "@mazeincoding on X: Please fix your Next.js app https://t.co/hEJd4b786K", 7 | "url": "https://x.com/mazeincoding", 8 | "publishedDate": "2024-01-16T16:44:41.000Z", 9 | "author": "mazeincoding", 10 | "text": "Making complete auth a default for your next project with Mazeway. Never spend time on implementing auth again| profile_url: http://mazeway.dev | name: Maze Winther | created_at: Tue Jan 16 16:44:41 +0000 2024 | favourites_count: 6598 | followers_count: 515 | friends_count: 465 | media_count: 354 | statuses_count: 4699 Please fix your Next.js app https://t.co/hEJd4b786K| created_at: Sat Mar 01 23:39:36 +0000 2025 | favorite_count: 2043 | quote_count: 13 | reply_count: 37 | retweet_count: 115 | lang: en How is Claude 3.5 Sonnet so good at using @shadcn components? It knows every single one.\nDo you still use GPT for coding?| created_at: Tue Jul 09 18:27:28 +0000 2024 | favorite_count: 331 | quote_count: 3 | reply_count: 39 | retweet_count: 4 | lang: en @theo I feel like it’s far worse here.\nAll people do is troll for engagement.| created_at: Sat Nov 30 10:28:18 +0000 2024 | favorite_count: 92 | reply_count: 8 | lang: en @frederic_ooo Neat way to exclude errors\nhttps://t.co/oJeArTqT63| created_at: Sun Mar 02 12:16:23 +0000 2025 | favorite_count: 84 | reply_count: 1 | retweet_count: 2 | is_quote_status: True | lang: en @sama Running code runs in an isolated environment, right?\nThere's only one way to find out https://t.co/zkOm3QuQns| created_at: Tue Dec 10 18:59:09 +0000 2024 | favorite_count: 75 | quote_count: 1 | reply_count: 3 | lang: en @theo Java understood the assignment with static typing. JavaScript comes along and ruins it. Someone makes typescript to solve a problem Java already solved.\nJavaScript should’ve never been run in the browser.| created_at: Thu Nov 28 22:06:03 +0000 2024 | favorite_count: 63 | reply_count: 10 | lang: en @theo Do they not realize everything's verified on the server, especially when you send a message?| created_at: Sun Jan 12 11:41:56 +0000 2025 | favorite_count: 64 | reply_count: 2 | lang: en @drarzorol @Nathi_Shaun @Storm_The_Yard @Melusi20547230 @ujessiica Yes, you can do very simple things like creating a useless login app.\nBut truthfully, to create any real app that will scale, you need to have the basic understanding of programming and how it works.\nUnderstanding what you’re doing is essential to build anything with AI.| created_at: Thu Jun 27 22:24:48 +0000 2024 | favorite_count: 58 | lang: en @despxwned @netflix That’s one way to get likes| created_at: Sat Nov 16 00:26:46 +0000 2024 | favorite_count: 48 | reply_count: 3 | retweet_count: 1 | lang: en I have no idea why but @justusecobalt feels 10x faster after their update.\nHuge W's https://t.co/j1nHUAOfXP| created_at: Wed Oct 30 21:12:44 +0000 2024 | favorite_count: 49 | reply_count: 2 | lang: en Fixed it https://t.co/rv9TcWqTke| created_at: Sun Mar 02 12:15:55 +0000 2025 | favorite_count: 48 | quote_count: 1 | reply_count: 2 | retweet_count: 2 | is_quote_status: True | lang: en @jackfriks @postbridge_ Living in a small country means yours posts mostly reach nearby people, making it hard to get more than a few thousand views. So this only works really well if:\n1. You use a VPN and remove your sim-card from your phone. (TikTok uses your sim-card and IP to detect location)\n2.| created_at: Mon Nov 11 18:24:59 +0000 2024 | favorite_count: 45 | reply_count: 7 | retweet_count: 2 | lang: en Okay... here me out.\nWindsurf, that editor everyone talks about?\nYeah, it's kind of good?\nThere's something that makes me say this. It's their \"memories\" feature.\nI've been talking about how much this is needed in AI coding, and they did it.\nIt's SO NEEDED. This is good.| created_at: Sun Feb 23 15:05:34 +0000 2025 | favorite_count: 39 | quote_count: 1 | reply_count: 6 | lang: en @bruvimtired Same! It's so nice actually. I've been deleting and adding back console.error statements (maybe skill issue) for prod and dev\nThis does it by itself| created_at: Sun Mar 02 00:46:28 +0000 2025 | favorite_count: 38 | reply_count: 1 | lang: en @momiyss @OpenAI This is actually funny af, you earned today's comedy gold award| created_at: Thu Dec 12 00:41:29 +0000 2024 | favorite_count: 29 | lang: en @v0 still waiting for v0 to be able to update the global.css file. Any plans for it?| created_at: Thu Nov 21 03:55:53 +0000 2024 | favorite_count: 28 | reply_count: 2 | lang: en Switching my project from Firebase to Supabase.\nWish me luck 😢 https://t.co/zxDB7CZewH| created_at: Mon Jul 08 10:33:36 +0000 2024 | favorite_count: 28 | reply_count: 13 | lang: en I never thought we'd get this far, but here we are!\nAppreciate y'all https://t.co/s2fBu2uR7W| created_at: Mon Nov 11 15:42:24 +0000 2024 | favorite_count: 23 | reply_count: 11 | lang: en I love Cobalt @justusecobalt https://t.co/8nJQ7hQYBz| created_at: Tue Nov 26 15:35:27 +0000 2024 | favorite_count: 21 | reply_count: 2 | lang: en @destroynectar https://t.co/05vHa7T4pL| created_at: Thu Nov 21 04:01:50 +0000 2024 | favorite_count: 20 | lang: qme @gnukeith The process:\n- installs it (let’s see)\n- cool home page.\n- can I change it?\n- clicks \"customize\"\n- damm, my browser now\n- \"where’s the extensions icon\"\n- oh I found it\n- installs extension\n- \"why are the tabs horizontal\"\n- \"goes to settings\"\n- damm you can change everything here| created_at: Wed Nov 20 20:42:35 +0000 2024 | favorite_count: 19 | quote_count: 2 | reply_count: 2 | lang: en @spattanaik01 @GithubProjects The UI first of all.\nSecondly, open-source always wins because it allows the community to make it better.\nAs a dev, I’d love to fix a lot of shit for the closed source apps, but I can’t.\nWith open source, anyone can contribute and make it better for everyone.\nHuge win| created_at: Tue Feb 18 17:18:18 +0000 2025 | favorite_count: 20 | reply_count: 1 | lang: en @OpenAI So now we know it's about canvas before even watching it?\nThat's like telling someone how a movie ends before they watch it.| created_at: Tue Dec 10 17:51:20 +0000 2024 | favorite_count: 19 | reply_count: 4 | lang: en @shadcn I always wonder why why Shadcn's code blocks are light theme (I love it)| created_at: Mon Dec 16 11:56:45 +0000 2024 | favorite_count: 18 | reply_count: 1 | lang: en @ItsBoTime88 @mariaxxnn Man woke up and decided speaking facts 🔥🔥| created_at: Thu Oct 24 17:41:32 +0000 2024 | favorite_count: 17 | quote_count: 1 | lang: en @sparbz @shadcn You might want to try @cursor_ai in that case. It doesn’t have cap limits the same way.\nYou get 400 fast-requests and unlimited slow-requests (you have to wait in a queue. For me, the queue is between 1 - 5 people usually but it can sometimes be 80 people.)| created_at: Tue Jul 09 19:23:51 +0000 2024 | favorite_count: 17 | reply_count: 3 | lang: en This is insane!\nWatch Cursor agent spent 7 minutes on reviewing all my API routes and fixing the code on the fly! https://t.co/hWWoMYvFGw| created_at: Wed Feb 19 15:07:30 +0000 2025 | favorite_count: 17 | quote_count: 1 | reply_count: 5 | lang: en I might be the only one left by now to have auto-complete disabled.\nAnyone else?| created_at: Wed Nov 13 22:59:07 +0000 2024 | favorite_count: 17 | reply_count: 10 | lang: en @shadcn Authentication will be easier than ever. It'll hopefully be secure by default for new projects.\nBuilding right now actually , open source: https://t.co/qYeTxiirBP| created_at: Fri Dec 27 16:13:28 +0000 2024 | favorite_count: 17 | reply_count: 1 | lang: en @yared_ow Yeah, totally a thing. Docs:\nhttps://t.co/s7LhcTxFja| created_at: Sun Mar 02 12:50:59 +0000 2025 | favorite_count: 15 | reply_count: 1 | lang: en @alexalbert__ @AnthropicAI Finally, I don’t need to copy all my tables from Supabase to a LLM?| created_at: Mon Nov 25 16:12:43 +0000 2024 | favorite_count: 15 | lang: en @peer_rich Seems fast to me, a few milliseconds for a redirect doesn't matter. And a few seconds for loading? Cache it.\nIf it matters that much, it's not a performance issue. It's a problem with your app. Users will wait a little extra for something that solves a problem.| created_at: Fri Jan 24 12:37:58 +0000 2025 | favorite_count: 15 | reply_count: 2 | lang: en I’ve used Cursor for 1+ year. Here’s everything I learned in 2 minutes. https://t.co/tt4mt5nBJ9| created_at: Wed Nov 13 17:41:21 +0000 2024 | favorite_count: 15 | quote_count: 2 | reply_count: 2 | lang: en Fun fact: you learn a lot by creating a course. Doesn’t matter if you know nothing, this will help you so much.\nDon’t be afraid to do this early.| created_at: Tue Nov 12 21:33:07 +0000 2024 | favorite_count: 14 | reply_count: 6 | lang: en @flaxety @Kja1mani https://t.co/1LmoKsfOud| created_at: Mon Oct 28 00:29:59 +0000 2024 | favorite_count: 14 | lang: qme Still stuck with Firebase in 2024?\nSupabase exists. It's better + faster.\nWhat's your excuse for not switching?| created_at: Fri Jul 12 09:11:25 +0000 2024 | favorite_count: 13 | reply_count: 4 | retweet_count: 1 | lang: en @kiwicopple @supabase A \"fork\" feature. So you can fork somebody else's project. Use case:\nhttps://t.co/BPRYgyrOPO\nIt's an auth starter that would really benefit from this. It's focused on \"just do this and enjoy complete auth\". The less setup = the better| created_at: Thu Jan 02 09:04:34 +0000 2025 | favorite_count: 14 | reply_count: 3 | lang: en @KevinNaughtonJr What does it stand for? Generational Procrastination Tool?| created_at: Mon Feb 24 20:56:45 +0000 2025 | favorite_count: 13 | lang: en @theo $1.66 per day or in total?| created_at: Sun Feb 02 23:26:47 +0000 2025 | favorite_count: 13 | reply_count: 4 | lang: en @Prathkum That's normal. Learning to code is not about remembering everything. It's about understand concepts and knowing how to apply them| created_at: Sun Nov 24 00:27:21 +0000 2024 | favorite_count: 13 | retweet_count: 2 | lang: en @Socialtrainers @ujessiica You started somewhere, didn’t you?\nDid you start off knowing everything? No. You likely started with notepad.| created_at: Thu Jun 27 22:27:48 +0000 2024 | favorite_count: 12 | reply_count: 1 | lang: en Charts being added by @shadcn is awesome.\nNow we just need a sidebar| created_at: Sat Jul 06 15:30:49 +0000 2024 | favorite_count: 13 | quote_count: 1 | reply_count: 3 | lang: en @theo People on the other app are mostly retards, same with Reddit. I'm seeing more hate than tech talk on those platforms in TECH communites.\nPeople just hate to hate, smh| created_at: Sun Feb 23 21:40:31 +0000 2025 | favorite_count: 12 | lang: en You know it's bad when even Claude takes a deep breath https://t.co/tjPpUUgRDV| created_at: Thu Feb 20 19:48:23 +0000 2025 | favorite_count: 12 | reply_count: 4 | retweet_count: 1 | lang: en @KevinNaughtonJr There’s no way this is real. But why were you even looking at the console log?| created_at: Wed Jan 22 17:13:47 +0000 2025 | favorite_count: 11 | reply_count: 1 | lang: en Shadcn never misses| created_at: Sat Aug 31 07:31:42 +0000 2024 | favorite_count: 11 | reply_count: 1 | is_quote_status: True | lang: en @OfficialLoganK Gemini is definitely going places. You can’t hate on it. Google’s the only one pushing out new models this fast and leveling up consistently| created_at: Thu Nov 21 18:38:59 +0000 2024 | favorite_count: 11 | reply_count: 1 | lang: en Lovable is one of the best ads created in human time.\nWhoever did the editing needs a raise| created_at: Thu Nov 21 10:23:56 +0000 2024 | favorite_count: 11 | quote_count: 1 | retweet_count: 1 | is_quote_status: True | lang: en @jackfriks @postbridge_ Yes, however Instagram doesn’t use your sim-card for location so you just have to use a VPN, which is why I prefer that| created_at: Mon Nov 11 20:32:33 +0000 2024 | favorite_count: 11 | reply_count: 3 | lang: en @supabase @boltdotnew Insane seeing a company nearly shutting down and now succeeding this hard.\nI wonder what turned that around.| created_at: Fri Feb 21 18:35:58 +0000 2025 | favorite_count: 11 | reply_count: 2 | lang: en @antho1404 They're useful during development| created_at: Sun Mar 02 17:30:12 +0000 2025 | favorite_count: 11 | lang: en verified = true; https://t.co/qLaDMuEqmV| created_at: Wed Nov 13 09:27:59 +0000 2024 | favorite_count: 11 | reply_count: 5 | lang: en @desert__runner @sama It would delete everything from \"/\" which deletes the entire system.| created_at: Tue Dec 10 22:55:21 +0000 2024 | favorite_count: 11 | lang: en @Socialtrainers @ujessiica Fair point. Learning a new skill and seeing progress often brings joy. Some feel like they need to share it, others keep it private. She's probably doing what feels right for her.| created_at: Fri Jun 28 07:35:35 +0000 2024 | favorite_count: 9 | reply_count: 1 | lang: en @theo https://t.co/M1wMfluNoC must’ve been really expensive| created_at: Sun Nov 17 00:19:15 +0000 2024 | favorite_count: 10 | reply_count: 1 | lang: en For every time I go on X, I love @supabase more.\n90 days + a way to backup on the FREE tier? Fucking amazing| created_at: Fri Nov 29 02:20:51 +0000 2024 | favorite_count: 10 | reply_count: 1 | is_quote_status: True | lang: en This is the most useful thing about about Grok.\nBeing able to look up posts. You could probably ask it \"look up 5 problems with [niche]\" and that's your product idea.\nJust saved 5 minutes by simply asking Grok to find an old post of mine https://t.co/vcPQhZxMkh| created_at: Sun Feb 23 16:03:35 +0000 2025 | favorite_count: 9 | reply_count: 2 | lang: en @theo \"my audience is so excited to learn about new tech that they often like the ads more than the videos themselves\"\nokay, I thought I was the only one. this is so acccurate| created_at: Mon Feb 24 00:20:21 +0000 2025 | favorite_count: 9 | reply_count: 1 | lang: en @ai_for_success That domain once redirected to Google's AI studio and Grok. I wouldn't trust it until Open AI confirms they bought it.| created_at: Thu Nov 07 15:22:45 +0000 2024 | favorite_count: 9 | reply_count: 1 | lang: en @TheRealRPuri @OpenAI @CVPR @9AM Too late to release it now. I already canceled my subscription. Cursor is better IMO.| created_at: Tue Jun 18 21:39:37 +0000 2024 | favorite_count: 9 | lang: en @Shefali__J 3 all the way!\nThe amount of times I try to copy something and it didn’t actually copy is too frustrating. Copying 5 times can’t hurt, can it? 🤨| created_at: Sun Jul 07 00:59:38 +0000 2024 | favorite_count: 8 | reply_count: 2 | lang: en @theo @rauchg https://t.co/rfWpTookqd| created_at: Tue Nov 19 23:41:54 +0000 2024 | favorite_count: 9 | reply_count: 1 | lang: qme @leore245 @theo Lmao, Claude got detected as human while GPT 4.5 was detected as human\n0% VS 63% is a huge difference. Just shows Claude is better at writing https://t.co/kbKrrHILuB| created_at: Thu Feb 27 21:27:53 +0000 2025 | favorite_count: 10 | reply_count: 3 | retweet_count: 1 | lang: en @danizord @shadcn Interesting. I’m using @cursor_ai with Claude and it’s still able to use the Shadcn components correctly.\nI wonder if Cursor instructed it too, or if the shadcn components are also in its training data.| created_at: Tue Jul 09 23:20:32 +0000 2024 | favorite_count: 9 | reply_count: 1 | lang: en @dannypostmaa Passwords are far worse than magic links| created_at: Sun Nov 17 11:24:34 +0000 2024 | favorite_count: 9 | reply_count: 2 | lang: en @OpenAI GPT 4o voice anytime soon though?\nIt’s been a month officially today.| created_at: Thu Jun 13 21:22:01 +0000 2024 | favorite_count: 6 | lang: en Thank y'all.\nCan we make it to 200 before the week starts? https://t.co/p7jqOOm7er| created_at: Sun Nov 10 20:48:58 +0000 2024 | favorite_count: 8 | reply_count: 4 | lang: en @_devJNS At first, it was distracting. I never did it just until one day. You get used to working with it.| created_at: Mon Nov 25 16:06:30 +0000 2024 | favorite_count: 8 | reply_count: 2 | lang: en I keep switching between Chrome, Arc, and Zen. Here are my honest takes:\nChrome:\n❌ Horizontal tabs suck\n❌ Eats up ram\n❌ No \"spaces\" or \"workflows\"\n✅ Familiar & easy to use\n✅ Extension development = super simple\nArc:\n❌ Opening new tabs = annoying (no empty tabs 😭)\n❌| created_at: Wed Nov 20 18:57:37 +0000 2024 | favorite_count: 8 | quote_count: 1 | reply_count: 2 | lang: en @justusecobalt Google is always the worst. The Google cloud platform for example, fucking nightmare| created_at: Tue Nov 26 15:41:58 +0000 2024 | favorite_count: 8 | lang: en Btw, not all Shadcn sidebars have to look the same https://t.co/39ewKpzOcJ| created_at: Sat Nov 16 21:03:59 +0000 2024 | favorite_count: 8 | reply_count: 1 | lang: en @Alessio_Gh @shadcn Absolutely! At least, in the programming languages I have tried so far:\nJavaScript\nTypeScript\nPython\nPython is probably top 1. It’s mind-blowing how good it is at Python.| created_at: Wed Jul 10 08:20:57 +0000 2024 | favorite_count: 8 | reply_count: 2 | lang: en @theo No fucking shot. I don't even need to optimize my apps anymore (this is a joke)\nBut wow. Huge| created_at: Tue Feb 18 20:29:46 +0000 2025 | favorite_count: 8 | reply_count: 1 | lang: en Time to change a profile on launch day? https://t.co/BLN4Pxij3V| created_at: Sun Mar 02 23:58:09 +0000 2025 | favorite_count: 8 | lang: en Apple, how did it take so lot to add this?? https://t.co/QfOnJed5Mj| created_at: Thu Nov 14 17:58:01 +0000 2024 | favorite_count: 8 | reply_count: 3 | lang: en So apparently, this video was fully generated with Wan 2.1 14b?\nThis looks insanely good, I’m convinced it’s real.\nThis is literally 10x better than Sora https://t.co/VekpAqclSI| created_at: Fri Feb 28 02:21:04 +0000 2025 | favorite_count: 8 | reply_count: 1 | retweet_count: 2 | lang: en @lovable_dev This launch is hot, and the video made me want to buy a subscription| created_at: Thu Nov 21 10:26:00 +0000 2024 | favorite_count: 8 | lang: en My therapist said I should stop buying domains so I bought https://t.co/u02Hv33LAL\nhttps://t.co/u02Hv33LAL| created_at: Sun Nov 10 23:43:02 +0000 2024 | favorite_count: 5 | quote_count: 1 | reply_count: 6 | lang: en Did you know X is a progressive web app ??| created_at: Sun Nov 17 21:37:36 +0000 2024 | favorite_count: 7 | reply_count: 4 | lang: en @theo Someone should make a wrapper that can work with both Claude/GPT and Ollama.| created_at: Sun Dec 08 21:30:17 +0000 2024 | favorite_count: 7 | reply_count: 5 | lang: en @DebadreeC @that_anokha_boy Nah, it should be created_by, even if it’s JS.| created_at: Sun Nov 03 08:38:58 +0000 2024 | favorite_count: 6 | lang: en This is so fucking nice| created_at: Sun Mar 02 14:38:43 +0000 2025 | favorite_count: 7 | reply_count: 2 | retweet_count: 2 | is_quote_status: True | lang: en So, never skate near cars| created_at: Sun Nov 17 14:23:16 +0000 2024 | favorite_count: 7 | reply_count: 5 | lang: en Is this too many imports? https://t.co/tlU3opm9VB| created_at: Mon Feb 24 19:52:39 +0000 2025 | favorite_count: 7 | reply_count: 5 | lang: en @Schisci9 Just imagine the comments if you said women instead.| created_at: Tue Nov 12 19:05:45 +0000 2024 | favorite_count: 7 | lang: en @justusecobalt This is what makes Cobalt so reliable. Thank you for making downloading videos great again| created_at: Tue Nov 26 15:40:34 +0000 2024 | favorite_count: 7 | lang: en @KevinNaughtonJr @bryan_johnson I don't know why people drink coffee, it doesn't even taste good| created_at: Sun Feb 23 21:49:21 +0000 2025 | favorite_count: 7 | reply_count: 2 | lang: en @destroynectar The new one looks more modern. I don’t like the company but cool logo change| created_at: Thu Nov 21 03:58:41 +0000 2024 | favorite_count: 7 | reply_count: 1 | lang: en I thought @justusecobalt had a public API?| created_at: Sun Dec 29 15:01:41 +0000 2024 | favorite_count: 7 | reply_count: 2 | lang: en @theo @kuba_oof @samwhoo W Theo| created_at: Fri Nov 29 00:01:54 +0000 2024 | favorite_count: 6 | lang: pl This is what I mean when I say @supabase is limited.\nWhat if you wanted:\n- Only uppercase + symbols\n- Symbols but no digits\n- Just uppercase letters\n- Custom combinations\nYou simply can't do that with these presets. Supabase is great, but re-consider a config and more options https://t.co/lZb6Qbw1Be| created_at: Tue Jan 28 13:46:42 +0000 2025 | favorite_count: 7 | reply_count: 4 | lang: en @BennyKokMusic @comfydeploy That's not an issue with Next.js, that's something to do with the code.\nIt's not normal for Next.js to take 5 seconds, just for hot reload.| created_at: Tue Dec 31 11:45:17 +0000 2024 | favorite_count: 7 | reply_count: 1 | lang: en @BennyKokMusic @comfydeploy I wouldn’t wanna miss out on:\n- tailwind setup by default\n- Shadcn\n- next.js router\n- image optimizations\n- and more\nFor that| created_at: Tue Dec 31 10:10:37 +0000 2024 | favorite_count: 7 | reply_count: 2 | lang: en @theo \"Just one more IDE bro, please\"| created_at: Fri Nov 15 13:13:36 +0000 2024 | favorite_count: 7 | lang: en Just got an email for paying an additional $20 to Cursor.\nI love this editor but my bank account doesn’t like it.\nLet me make my own editor real quick with open-source models.| created_at: Fri Nov 15 14:00:53 +0000 2024 | favorite_count: 7 | quote_count: 1 | reply_count: 4 | lang: en @dshukertjr @supabase What do you use to edit your screen recordings?| created_at: Sat Feb 01 13:04:24 +0000 2025 | favorite_count: 7 | reply_count: 2 | lang: en @CodeByPoonam And this is the most engagement hungry post on X I've ever seen| created_at: Tue Dec 10 22:48:36 +0000 2024 | favorite_count: 7 | lang: en @trikcode My commit history right now https://t.co/TO6x0TsaDr| created_at: Mon Nov 25 16:05:38 +0000 2024 | favorite_count: 7 | reply_count: 1 | lang: en for everyday that goes by, I hate the internet more.\nI'm not anti-internet, I'm anti-BS.| created_at: Fri Nov 29 13:29:47 +0000 2024 | favorite_count: 6 | reply_count: 2 | lang: en 🎉 We did it!\n1000+ people have a domain buying addiction. Are you gonna be next? https://t.co/Hk7MY73Ccx| created_at: Tue Nov 19 00:55:45 +0000 2024 | favorite_count: 6 | reply_count: 2 | lang: en" 11 | } 12 | ], 13 | "requestId": "0c126ce401085c617696def7072744e1", 14 | "costDollars": { 15 | "total": 0.001, 16 | "contents": { 17 | "text": 0.001 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /profile-debug.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "role": "system", 4 | "content": "Analyze the following tweets the given from Twitter user and determine their alignment on a D&D-style alignment chart.\n\nFor lawful-chaotic axis:\n- Lawful (-100): Follows rules, traditions, and social norms\n- Neutral (0): Balanced approach to rules and freedom\n- Chaotic (100): Loves and values freedom, flexibility, and individualism over rules\n\nFor good-evil axis:\n- Good (-100): Altruistic, compassionate, puts others first\n- Neutral (0): Balanced self-interest and concern for others\n- Evil (100): Selfish, manipulative, or harmful to others\n\nBased only on these tweets, provide a numerical assessment of this user's alignment. Be willing to move to any side/extreme!\n\nSince this is a bit of fun, be willing to overly exaggerate if the user has a specific trait expressed barely - e.g. if they are evil at some point then make sure to express it! - I don't just want everyone to end up as chaotic-neutral in the end... However don't always exaggerate a user's chaotic characteristic, you can also try to exaggerate their lawful or good/evil traits if they are more pronounced. Just be fun with it.\n\nFor the explaination, try to avoid overly waffling - but show your reasoning behind your judgement. You can mention specific things about their user like mentioned traits/remarks or projects/etc - the more personalised the better." 5 | }, 6 | { 7 | "role": "user", 8 | "content": "Username: @vishyfishy2\n\n\n{\n \"bio\": \"solo dev | building everyday ai → http://uncovr.app | likes vectors, japan and cats\",\n \"profile_url\": \"https://uncovr.app\",\n \"name\": \"f1shy-dev\",\n \"created_at\": \"Tue Feb 25 16:15:56 +0000 2020\",\n \"followers_count\": 2580,\n \"statuses_count\": 2624\n}\n\n\n\n\nshipping new features - literally every ai model ever\n- built in web+video+image+academic search\n- customise and save prompts\n- code execution\n- image uploads\nfree 7 day trial\nwhat are you waiting for https://t.co/94wEPgwnCt\n34 likes, 5 replies, undefined retweets, 1 quotes\n\n\n\n@p1nkempress_gd if you do a data export you also get to see what age they think you are from your messages/something\nit isn't just this message, i'd bet its a mix of ai-based signals...\n3647 likes, 10 replies, 8 retweets, 1 quotes\n\n\n\napple intelligence on iphone 14 pro\nat least the UI… https://t.co/Pm4kKbFqSp\n2300 likes, 46 replies, 57 retweets, 6 quotes\n\n\n\nit's time to leave every other ai wrapper behind. https://t.co/iBGiAgWjb3\n2245 likes, 101 replies, 102 retweets, 11 quotes\n\n\n\n@RhysSullivan sign in with lego https://t.co/Mn0n9wC8wP\n701 likes, 1 replies, 2 retweets, undefined quotes\n\n\n\napple intelligence (and call recording) - not just UI, actual model\non iphone 14 pro, ios 18.1db4\nsiri works, but writing tools and others don’t\nthanks to me and @XeZrunner https://t.co/A1hhqKrx9R\n477 likes, 20 replies, 31 retweets, 8 quotes\n\n\n\n@AnxiousHolly who on earth uses this bruh\n332 likes, 29 replies, undefined retweets, undefined quotes\n\n\n\nnow live. the best looking ai wrapper. free to use. https://t.co/Re6OyEgS08\n235 likes, 13 replies, 16 retweets, 2 quotes\n\n\n\n@xyz3va i mean you can get £300+£300 of free gmaps api credit with just a payment method and a google account but\n220 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@MaxWinebach here in the uk, literally everyone accepts apple pay. if they don't accept apple pay, that's probably because they don't take card at all\nevery other shop has a square or sumup reader, or something\ni even went to a corner shop and they were using tap-to-pay on iphone\n171 likes, 6 replies, undefined retweets, undefined quotes\n\n\n\nControlConfig Update (iOS 16/15 MDC) - moving modules working perfectly and hidden modules enabled. Going to do some more testing and fix small issues before release. https://t.co/H1BN195H3b\n154 likes, 9 replies, 30 retweets, 3 quotes\n\n\n\n@p1nkempress_gd if you can even get through support (cooked) then i guess it would come down to id verification.\nbut yeah given their horrible support...looking like little hope\n149 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@ExaAILabs this is very cool\nbut... this isn't even possible with Exa\n- can't get profile data with exa API\n- can barely get tweets for smaller profiles\nso you guys just have to have access to either X api or some really good scraping system with tons of bot accounts\n137 likes, 8 replies, 2 retweets, undefined quotes\n\n\n\niOS/iPadOS 18.1 Beta 5 has patched SparseRestore - the backup-based exploit used in Nugget (pc, mobile), MisakaX, MitokoX, PureKFD/PureiOS and AIEnabler.\nno more Apple Intelligence, gestalt editing (no dynamic island, etc), or bypassing EU restrictions... 😥\n133 likes, 5 replies, 12 retweets, 2 quotes\n\n\n\n@JPT_Struggles unserious https://t.co/18XoCVRjUR\n120 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\nControlConfig v0.1.0 alpha released!!\n🎨 Rebrand with new icon and new first-launch screen\n✨ Move all modules around in settings!\n✅ Add hidden modules on iOS 15/16 (NFC, Stage Manager, DND, and so on...)\nhttps://t.co/ZYDkYKC29M\n108 likes, 24 replies, 33 retweets, 3 quotes\n\n\n\n@saltyAom to be fair, what on earth is this 😭 https://t.co/rIixUwoK4t\n114 likes, 13 replies, 2 retweets, 3 quotes\n\n\n\nControlConfig icons in the works! Sorry it took me so long, but i’ve got the hang of it now. There’ll be a full icon editor and you can make icon packs or use ones made by me and the community https://t.co/XZ8drM6hhI\n105 likes, 10 replies, 11 retweets, 3 quotes\n\n\n\nfull guide, by me is here\nhttps://t.co/AGe19ieAcF\n⚠️complicated, not really worth it - use at own risk\n96 likes, 3 replies, 8 retweets, 4 quotes\n\n\n\n@RKBDI what i dont get is why they cant just combine google assistant intents and gemini\napple literally did it with the on device index and lora adapters, why cant google the massive company do it... even if it was pixel only feature or like NPU needed cpu on phone, still...\n88 likes, 2 replies, 1 retweets, undefined quotes\n\n\n\n@MaxWinebach 1 rating\n1 star\n😭\n92 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\nhmm…\n@Little_34306 btw https://t.co/1BCh07casW\n86 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\nApple Intelligence in iOS 18.1 Public Beta / DB4 is only a fraction of what it will be/was promised to be.\nI wouldn't even call this Siri 2.0 yet - it just seems like a slightly smarter old Siri (user guide, few more intents), which still does not use an LLM. As of current, Siri https://t.co/uhmMvZbcpL\n85 likes, 4 replies, 1 retweets, 2 quotes\n\n\n\nfinally live! the best looking ai wrapper. free to use! https://t.co/F7H3LJZyFQ\n85 likes, 18 replies, 3 retweets, 2 quotes\n\n\n\n@SnazzyLabs PayPal, the wrapper company.\nEverything is a wrapper...\n75 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@KrystalWolfyAlt you can use it ONCE and ONCE only\n68 likes, undefined replies, 1 retweets, undefined quotes\n\n\n\n@justusecobalt @legojjk no way half of cobalt is javascript and not typescript. what a crime!\n65 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@MohapatraHemant i mean https://t.co/s0LfhakUps\n66 likes, 5 replies, undefined retweets, 1 quotes\n\n\n\nFirst payout of the year. So grateful for everyone who made this possible.\nHere's to a successful 2025 🙏 https://t.co/AIbGXt4F0f\n61 likes, 10 replies, 1 retweets, 1 quotes\n\n\n\n@Little_34306 seems like it doesn’t do much\nsomebody in server has changed producttype to iphone 16 on a 14 pro and it “seems” to be downloading but ?\nmine might be downloading on my 14 pro but no indicator except slightly rising system data\ngonna give it a few hours and see what happens\n61 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@colinarms Also the job description:\n*30 years of Web development experience*\n53 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@LinkofSunshine - openai has won consumer\n- surprised it isn’t a samsung\n- font size on max\n55 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@iconredesign it depends on your region ? but also works once in a blue moon for me\n50 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\nHoping to release ControlConfig v1 (with kfd?) sometime soon (1-2 weeks?) - lots of new features (new list view, better application, explore page, and more) since the last public release, and lots more planned too 🙌 https://t.co/cf9BkyaB4a\n51 likes, 13 replies, 7 retweets, undefined quotes\n\n\n\nfor reference, it takes up around 3GB here (might be missing some models)\nalso, got it to work while keeping FaceID https://t.co/AQt2S4NpYy\n50 likes, 6 replies, undefined retweets, undefined quotes\n\n\n\n@ammaar @Replit Where's my $2B in funding!\nI've been building my own AI search. Latest in-progress version demoed below. Works with lots of stuff (e.g. video), and is super fast. https://t.co/v4KL7vkkcW\n49 likes, 6 replies, 2 retweets, undefined quotes\n\n\n\nGPT 4.5:\nInput: $75.00 / 1M tokens\nOutput: $150.00 / 1M tokens\nWHAT\n49 likes, 11 replies, undefined retweets, 3 quotes\n\n\n\n@t3dotgg @0xJoker33 bruh it’s so easy to figure out i’ve had literally no problems on this $5 plan\nlike for anything just read the docs simple as that they explain it well if not then idfk ask chatgpt if you can’t comprehend units\n47 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@colin_fraser hmm… https://t.co/gBNFF2CQOe\n48 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\nnewly launched: @_uncovrapp tool - youtube copilot\ndemo: (in real time) getting the transcript+summary for a ~2hour video, and asking the questions about the video\nuncovr [dot] app! https://t.co/NHUPHKAcx3\n45 likes, 5 replies, 5 retweets, 2 quotes\n\n\n\n@Flighty Does this fix tail numbers not available in flights around asia until literally the minute before takeoff?\n38 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@miiura They really focused on just coding for this model iteration honestly\nIt’s great\n43 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@AetherAurelia you cooked\n40 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@theo $75/mtok input and $150/mtok output is outrageous\n41 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nMaking progress - this is with kfd + Cowabunga Lite (no top gaps) - though it’s quite buggy - no settings names and it took a while to apply. Testing in progress for other devices… https://t.co/fRJKVtFxHF\n35 likes, 3 replies, 4 retweets, undefined quotes\n\n\n\n@hot_girl_spring works on my machine™\n36 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@saltyAom @Aizkmusic the more you know?!\n34 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nPrivate ControlConfig testers got it working on various iOS versions/devices - now going to start work on icon packs and other features in-app 😊🙌 https://t.co/U0XKlwALqc\n31 likes, 2 replies, 1 retweets, undefined quotes\n\n\n\n@stalkermustang @OfficialLoganK doesn't seem to be using the actual model just yet! https://t.co/OpdzEuyClN\n32 likes, undefined replies, undefined retweets, 1 quotes\n\n\n\n@yacineMTB nah stickies is the goat https://t.co/uOrwv9FZcQ\n32 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@BlushOf2021 @p1nkempress_gd human (or probably, ai) has to be here because what about the million edge cases?\nwhat if it’s a paste from somewhere else with the text inside?\n31 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@yacineMTB - yapping\n- spamming mute key every 2 seconds\n29 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nControlConfig Updates (1/2)\nNew in-app tutorial for both KFD and MDC (KFD shown) with the KFD one also showing steps needed on Cowabunga Lite https://t.co/66lGQSn61w\n30 likes, 4 replies, 2 retweets, undefined quotes\n\n\n\n@AviSchiffmann Overall isn’t this negative? More 0-value AI content, filling the internet…\nIf they wanted to artificially boost likes they could have done that ages ago, without AI.\n30 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@ctjlewis @jarredsumner server side jquery? we’ve come full circle…\n28 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@yacineMTB am i the prime example\nbuilt ui + first ver in like a weekend, now aiming it to become the best ai search https://t.co/3ZOJQacnG6\n28 likes, 5 replies, undefined retweets, undefined quotes\n\n\n\n@theo how/what did you even do this because how does nothing from this whole UI rerender while typing\nprofessional react skill issue from me 😭 https://t.co/r3MccrfMdP\n28 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@theo @rauchg why not tauri? i’d imagine there isn’t much native logic to do, and it would make the app much smaller\nunless you want to make a global chat bar ui or something (like a raycast bar but t3 chat)\nhotkeys shouldn’t be too hard in rust really\n28 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@NizzyABI @ryandavogel not sure about this one. you don't want to put overly distracting animations behing/on the main content and or something you might use often.\ni would go for something simple, possibly a split layout. here's my 5min v0 on it (obviously you would perfect the right layouts, dark https://t.co/HsEd4J342M\n27 likes, 7 replies, undefined retweets, undefined quotes\n\n\n\nmini-update!\nfree users now get 10 image upload messages a day!\n(and you can now send a message with just an image) https://t.co/25Xd8BSlgd\n28 likes, 4 replies, 3 retweets, undefined quotes\n\n\n\n@sabziz feature requests\n- remove the grok button in the bottom right\n- hide subscribe, only if you're already followed\n- put the menu items you hide into the \"more\"\n- option to make the post button smaller/more like a part of the list than a huge distracting button\n- zen mode which lets\n25 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@aidenybai skill issue type cpu? (lol) https://t.co/QyKbRLVRBL\n25 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@eepyeri lmao what\n\"oh hey there! before you dropship this from aliexpress, just to let you know it miiiight be illegal. just letting you know! <3\"\n25 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@AetherAurelia Oh no\n24 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@albrorithm yeah! one man team.\nthank you 😁\n23 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\nControlConfig - backup/revert system, nearly done.\n(swiftui looks so good, and why is Filza so buggy??) https://t.co/MNXub6lNdC\n21 likes, undefined replies, 2 retweets, undefined quotes\n\n\n\n@SullyOmarr grounding is very expensive on gemini's API though... $0.04 per query?\nbeen building my own ai search. works very well https://t.co/JodZXxqk5q\n22 likes, 3 replies, undefined retweets, undefined quotes\n\n\n\n@itsandrewgao @LangChainAI @browserbasehq @ExaAILabs @firecrawl_dev @OpenAI don’t even need browsers (slow) or firecrawl (also slow), in theory\nshould I build this in 1-2days and make it oss\n“Open Research”\n21 likes, 1 replies, 1 retweets, undefined quotes\n\n\n\n@SnazzyLabs Then your daughter can watch it back on her vision pro with custom facial interface specifically for her face!\nOr maybe her vision lite, in 2027\n21 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n@aidenybai no it just calls o1 or whatever, it just doesnt change the model name at the top left\nclick on rewrite to see actual model used\n21 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\n⚠️ Respring bug fix\nIf your device is respringing on rotate to landscape after having used ControlConfig, please ensure the Stage Manager module is added like this (both toggles must either be on or off to prevent the issue) https://t.co/1h9UmQfpCt\n19 likes, 3 replies, 1 retweets, 1 quotes\n\n\n\n@aidenybai Maybe it’s also related to them being emojis?\nHow does twitter render emojis internally? Since e.g. twemoji has some library to turn any emojis on the page into twemoji SVGs\nI wonder if something similiar is happening\n18 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\ngenerative ui is quite fun. adding lots of these mini cards into search. https://t.co/hGzo3IlSrX\n19 likes, 2 replies, 2 retweets, undefined quotes\n\n\n\n@yacineMTB Forget google pixels it’s gonna be every new android phone eventually\nPlus they’re going to start enforcing gemini as assistant default to OEMs with the rise of competitors like perplexity assistant\n17 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@XxsempaiixX @AnxiousHolly isn’t it just a local player or something\n18 likes, 3 replies, undefined retweets, undefined quotes\n\n\n\ntoday's shipments:\nmaking landing feel... beautiful. https://t.co/jgZ2bwH8bU\n18 likes, 7 replies, undefined retweets, undefined quotes\n\n\n\nvibe coded away the whole repo: https://t.co/abvgdVijk0\n19 likes, 2 replies, undefined retweets, 1 quotes\n\n\n\n@LeakerApple tiktok is such a joke\nthat iphone 11 doesnt even.. have OLED\n18 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\ngot a bit bored on the flight… @alistaiir @m1guelpf\nfound/documented apis for oneworld flights past the one we saw - there’s another two and also even one for weather https://t.co/1kgoUFMeIT\n18 likes, 2 replies, undefined retweets, 1 quotes\n\n\n\nTIL... you can resize arc at same time on X and Y using shift (and or) option https://t.co/llmGIfDZ6k\n17 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\njavascript is easy!\nalso javascript: https://t.co/aFJVhlMfBy\n17 likes, 1 replies, 1 retweets, undefined quotes\n\n\n\nnew: youtube summary/transcript/chat\nshipped in 3 hours today (@cursor_ai helped!)\npowered by @aisdk ✨ https://t.co/vMHNuh7ksn\n17 likes, 5 replies, 3 retweets, undefined quotes\n\n\n\n@rjonesy @nikitabier Aliexpress does similiar stuff https://t.co/Bqu32PPlKN\n17 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\ni am sad to announce that I have made hot chocolate with skimmed/no-fat milk on accident.\nit tastes absolutely horrible.\non behalf of big cocoa, i am truly sorry for my actions.\n17 likes, 7 replies, 1 retweets, undefined quotes\n\n\n\n@aidenybai I mean I still feel they are the most comprehensive set of docs i’ve ever seen\nBut maybe to get there they’ve forgotten their roots in making payements extremely simple?\n16 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@MMatt14 @Jiankui_He those who know\n16 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nsearch answer quality is 🆙🔥\nsoon gonna get to the point where i can replace chatgpt and perplexity and gemini with this 😇 https://t.co/22NcFmVq0I\n16 likes, 4 replies, undefined retweets, undefined quotes\n\n\n\n@aidan_mclau generally why does it score so on aidanbench?\n16 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@theo would be fun to see some open data on financing this whole thing\naverage token usage for free and paid users, how much do you pay for PTU to providers, how much paid serverless, usage per model\nand most interesting: are you making a profit or on trajectory to do so ?\n16 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@seatedro they also offer... ai safety\ncan't wait for hybrid claude reasoning though\n15 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@tom_doerr @perplexity_ai personally, https://t.co/3ZOJQabPQy is better though fr\n15 likes, undefined replies, 1 retweets, undefined quotes\n\n\n\nMore details:\nWriting Tools and Notification Summaries are based using LORA adapters on top of the on-device 3B LLM, however the device can choose to also do Writing Tools on the server (Cloud Compute) - seen more often on Mac, and in the mail app.\nThere is seemingly also a\n15 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@adonis_singh @mark_k They fine tune a model to get enough usage data\nThen maybe use that usage data to fine tune back the base one (which itself is probably a fine tune over the API models)…\n15 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nIn the works… all (normal) modules movable, sizing and hidden modules next. https://t.co/NcqDWiOOD8\n14 likes, 1 replies, 2 retweets, 1 quotes\n\n\n\n@n0w00j get your head in the game https://t.co/Dk1DqNUYqe\n13 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\n@snowmaker well i didnt apply to yc but building that at → https://t.co/3ZOJQabPQy\n14 likes, 1 replies, undefined retweets, 1 quotes\n\n\n\n@jaredpalmer never deleting this app https://t.co/OmXICN9X8T\n14 likes, undefined replies, undefined retweets, undefined quotes\n\n\n\nupdate #4 for ➡️ https://t.co/PBPdAULECs\nsadly, i also do not have $1.8m in funding for a domain.\nhowever, i did ship so many things that it was too much to fit in one tweet.\n⤵️ changelog thread: 🧵 (1/8)\n14 likes, 2 replies, undefined retweets, undefined quotes\n\n\n\niOS 16 all CC modules with MDC, coming soon to controlconfig. https://t.co/sqahBOuXER\n12 likes, 1 replies, 1 retweets, undefined quotes\n\n\n\n@ImSh4yy what do you have to say to this. I hear VC is furious https://t.co/3gYhWgK8um\n14 likes, 1 replies, undefined retweets, undefined quotes\n\n\n\n@seatedro should i\n13 likes, 3 replies, undefined retweets, undefined quotes\n\n" 9 | } 10 | ] -------------------------------------------------------------------------------- /src/lib/test_data/results-vishyfishy2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "results": [ 4 | { 5 | "id": "x.com/vishyfishy2", 6 | "title": "@vishyfishy2 on X: - literally every ai model ever\n- built in web+video+image+academic search\n- c...", 7 | "url": "http://x.com/vishyfishy2", 8 | "publishedDate": "2020-02-25T16:15:56.000Z", 9 | "author": "vishyfishy2", 10 | "text": "solo dev | building everyday ai → http://uncovr.app | likes vectors, japan and cats| profile_url: https://uncovr.app | name: f1shy-dev | created_at: Tue Feb 25 16:15:56 +0000 2020 | favourites_count: 9783 | followers_count: 2580 | friends_count: 589 | media_count: 311 | statuses_count: 2624 | location: shipping new features - literally every ai model ever\n- built in web+video+image+academic search\n- customise and save prompts\n- code execution\n- image uploads\nfree 7 day trial\nwhat are you waiting for https://t.co/94wEPgwnCt| created_at: Mon Mar 10 01:31:44 +0000 2025 | favorite_count: 34 | quote_count: 1 | reply_count: 5 | lang: en @p1nkempress_gd if you do a data export you also get to see what age they think you are from your messages/something\nit isn't just this message, i'd bet its a mix of ai-based signals...| created_at: Sun Dec 22 20:13:15 +0000 2024 | favorite_count: 3647 | quote_count: 1 | reply_count: 10 | retweet_count: 8 | lang: en apple intelligence on iphone 14 pro\nat least the UI… https://t.co/Pm4kKbFqSp| created_at: Tue Sep 17 21:54:13 +0000 2024 | favorite_count: 2300 | quote_count: 6 | reply_count: 46 | retweet_count: 57 | lang: en it's time to leave every other ai wrapper behind. https://t.co/iBGiAgWjb3| created_at: Sun Feb 09 20:05:16 +0000 2025 | favorite_count: 2245 | quote_count: 11 | reply_count: 101 | retweet_count: 102 | is_quote_status: True | lang: en @RhysSullivan sign in with lego https://t.co/Mn0n9wC8wP| created_at: Sat Jan 18 11:23:13 +0000 2025 | favorite_count: 701 | reply_count: 1 | retweet_count: 2 | lang: en apple intelligence (and call recording) - not just UI, actual model\non iphone 14 pro, ios 18.1db4\nsiri works, but writing tools and others don’t\nthanks to me and @XeZrunner https://t.co/A1hhqKrx9R| created_at: Wed Sep 18 20:43:35 +0000 2024 | favorite_count: 477 | quote_count: 8 | reply_count: 20 | retweet_count: 31 | lang: en @AnxiousHolly who on earth uses this bruh| created_at: Mon Aug 05 14:38:48 +0000 2024 | favorite_count: 332 | reply_count: 29 | lang: en now live. the best looking ai wrapper. free to use. https://t.co/Re6OyEgS08| created_at: Mon Feb 24 22:29:41 +0000 2025 | favorite_count: 235 | quote_count: 2 | reply_count: 13 | retweet_count: 16 | lang: en @xyz3va i mean you can get £300+£300 of free gmaps api credit with just a payment method and a google account but| created_at: Mon Aug 05 15:52:13 +0000 2024 | favorite_count: 220 | reply_count: 1 | lang: en @MaxWinebach here in the uk, literally everyone accepts apple pay. if they don't accept apple pay, that's probably because they don't take card at all\nevery other shop has a square or sumup reader, or something\ni even went to a corner shop and they were using tap-to-pay on iphone| created_at: Sat Feb 22 23:19:34 +0000 2025 | favorite_count: 171 | reply_count: 6 | lang: en ControlConfig Update (iOS 16/15 MDC) - moving modules working perfectly and hidden modules enabled. Going to do some more testing and fix small issues before release. https://t.co/H1BN195H3b| created_at: Sat Mar 11 14:06:14 +0000 2023 | favorite_count: 154 | quote_count: 3 | reply_count: 9 | retweet_count: 30 | lang: en @p1nkempress_gd if you can even get through support (cooked) then i guess it would come down to id verification.\nbut yeah given their horrible support...looking like little hope| created_at: Sun Dec 22 20:15:25 +0000 2024 | favorite_count: 149 | reply_count: 1 | lang: en @ExaAILabs this is very cool\nbut... this isn't even possible with Exa\n- can't get profile data with exa API\n- can barely get tweets for smaller profiles\nso you guys just have to have access to either X api or some really good scraping system with tons of bot accounts| created_at: Thu Dec 26 20:55:48 +0000 2024 | favorite_count: 137 | reply_count: 8 | retweet_count: 2 | lang: en iOS/iPadOS 18.1 Beta 5 has patched SparseRestore - the backup-based exploit used in Nugget (pc, mobile), MisakaX, MitokoX, PureKFD/PureiOS and AIEnabler.\nno more Apple Intelligence, gestalt editing (no dynamic island, etc), or bypassing EU restrictions... 😥| created_at: Mon Sep 23 19:31:04 +0000 2024 | favorite_count: 133 | quote_count: 2 | reply_count: 5 | retweet_count: 12 | lang: en @JPT_Struggles unserious https://t.co/18XoCVRjUR| created_at: Sun Aug 04 18:33:50 +0000 2024 | favorite_count: 120 | reply_count: 4 | lang: en ControlConfig v0.1.0 alpha released!!\n🎨 Rebrand with new icon and new first-launch screen\n✨ Move all modules around in settings!\n✅ Add hidden modules on iOS 15/16 (NFC, Stage Manager, DND, and so on...)\nhttps://t.co/ZYDkYKC29M| created_at: Mon Mar 13 23:22:33 +0000 2023 | favorite_count: 108 | quote_count: 3 | reply_count: 24 | retweet_count: 33 | lang: en @saltyAom to be fair, what on earth is this 😭 https://t.co/rIixUwoK4t| created_at: Sun Mar 02 12:13:56 +0000 2025 | favorite_count: 114 | quote_count: 3 | reply_count: 13 | retweet_count: 2 | lang: en ControlConfig icons in the works! Sorry it took me so long, but i’ve got the hang of it now. There’ll be a full icon editor and you can make icon packs or use ones made by me and the community https://t.co/XZ8drM6hhI| created_at: Fri Apr 14 09:49:34 +0000 2023 | favorite_count: 105 | quote_count: 3 | reply_count: 10 | retweet_count: 11 | lang: en full guide, by me is here\nhttps://t.co/AGe19ieAcF\n⚠️complicated, not really worth it - use at own risk| created_at: Wed Sep 18 20:43:38 +0000 2024 | favorite_count: 96 | quote_count: 4 | reply_count: 3 | retweet_count: 8 | lang: en @RKBDI what i dont get is why they cant just combine google assistant intents and gemini\napple literally did it with the on device index and lora adapters, why cant google the massive company do it... even if it was pixel only feature or like NPU needed cpu on phone, still...| created_at: Wed Aug 07 14:13:03 +0000 2024 | favorite_count: 88 | reply_count: 2 | retweet_count: 1 | lang: en @MaxWinebach 1 rating\n1 star\n😭| created_at: Sun Mar 02 16:47:27 +0000 2025 | favorite_count: 92 | reply_count: 4 | lang: en hmm…\n@Little_34306 btw https://t.co/1BCh07casW| created_at: Tue Sep 17 21:57:34 +0000 2024 | favorite_count: 86 | reply_count: 1 | lang: pl Apple Intelligence in iOS 18.1 Public Beta / DB4 is only a fraction of what it will be/was promised to be.\nI wouldn't even call this Siri 2.0 yet - it just seems like a slightly smarter old Siri (user guide, few more intents), which still does not use an LLM. As of current, Siri https://t.co/uhmMvZbcpL| created_at: Thu Sep 19 22:08:36 +0000 2024 | favorite_count: 85 | quote_count: 2 | reply_count: 4 | retweet_count: 1 | lang: en finally live! the best looking ai wrapper. free to use! https://t.co/F7H3LJZyFQ| created_at: Mon Feb 24 22:34:32 +0000 2025 | favorite_count: 85 | quote_count: 2 | reply_count: 18 | retweet_count: 3 | lang: en @SnazzyLabs PayPal, the wrapper company.\nEverything is a wrapper...| created_at: Fri Jan 03 18:59:02 +0000 2025 | favorite_count: 75 | reply_count: 1 | lang: en @KrystalWolfyAlt you can use it ONCE and ONCE only| created_at: Mon Aug 05 14:41:10 +0000 2024 | favorite_count: 68 | retweet_count: 1 | lang: en @justusecobalt @legojjk no way half of cobalt is javascript and not typescript. what a crime!| created_at: Sat Jan 11 18:42:28 +0000 2025 | favorite_count: 65 | reply_count: 1 | lang: en @MohapatraHemant i mean https://t.co/s0LfhakUps| created_at: Mon Jan 27 15:13:39 +0000 2025 | favorite_count: 66 | quote_count: 1 | reply_count: 5 | lang: en First payout of the year. So grateful for everyone who made this possible.\nHere's to a successful 2025 🙏 https://t.co/AIbGXt4F0f| created_at: Sat Jan 04 00:08:10 +0000 2025 | favorite_count: 61 | quote_count: 1 | reply_count: 10 | retweet_count: 1 | lang: en @Little_34306 seems like it doesn’t do much\nsomebody in server has changed producttype to iphone 16 on a 14 pro and it “seems” to be downloading but ?\nmine might be downloading on my 14 pro but no indicator except slightly rising system data\ngonna give it a few hours and see what happens| created_at: Tue Sep 17 23:12:03 +0000 2024 | favorite_count: 61 | reply_count: 1 | lang: en @colinarms Also the job description:\n*30 years of Web development experience*| created_at: Sat Aug 14 20:19:58 +0000 2021 | favorite_count: 53 | reply_count: 2 | lang: en @LinkofSunshine - openai has won consumer\n- surprised it isn’t a samsung\n- font size on max| created_at: Sat Dec 28 00:37:22 +0000 2024 | favorite_count: 55 | lang: en @iconredesign it depends on your region ? but also works once in a blue moon for me| created_at: Mon Aug 05 01:26:43 +0000 2024 | favorite_count: 50 | reply_count: 2 | lang: en Hoping to release ControlConfig v1 (with kfd?) sometime soon (1-2 weeks?) - lots of new features (new list view, better application, explore page, and more) since the last public release, and lots more planned too 🙌 https://t.co/cf9BkyaB4a| created_at: Fri Aug 04 00:22:19 +0000 2023 | favorite_count: 51 | reply_count: 13 | retweet_count: 7 | lang: en for reference, it takes up around 3GB here (might be missing some models)\nalso, got it to work while keeping FaceID https://t.co/AQt2S4NpYy| created_at: Wed Sep 18 20:43:37 +0000 2024 | favorite_count: 50 | reply_count: 6 | lang: en @ammaar @Replit Where's my $2B in funding!\nI've been building my own AI search. Latest in-progress version demoed below. Works with lots of stuff (e.g. video), and is super fast. https://t.co/v4KL7vkkcW| created_at: Fri Jan 03 21:29:26 +0000 2025 | favorite_count: 49 | reply_count: 6 | retweet_count: 2 | lang: en GPT 4.5:\nInput: $75.00 / 1M tokens\nOutput: $150.00 / 1M tokens\nWHAT| created_at: Thu Feb 27 20:14:14 +0000 2025 | favorite_count: 49 | quote_count: 3 | reply_count: 11 | lang: en @t3dotgg @0xJoker33 bruh it’s so easy to figure out i’ve had literally no problems on this $5 plan\nlike for anything just read the docs simple as that they explain it well if not then idfk ask chatgpt if you can’t comprehend units| created_at: Mon Aug 05 01:28:14 +0000 2024 | favorite_count: 47 | reply_count: 2 | lang: en @colin_fraser hmm… https://t.co/gBNFF2CQOe| created_at: Sun Feb 23 23:52:15 +0000 2025 | favorite_count: 48 | reply_count: 4 | lang: und newly launched: @_uncovrapp tool - youtube copilot\ndemo: (in real time) getting the transcript+summary for a ~2hour video, and asking the questions about the video\nuncovr [dot] app! https://t.co/NHUPHKAcx3| created_at: Mon Dec 23 13:58:54 +0000 2024 | favorite_count: 45 | quote_count: 2 | reply_count: 5 | retweet_count: 5 | lang: en @Flighty Does this fix tail numbers not available in flights around asia until literally the minute before takeoff?| created_at: Tue Aug 06 21:16:50 +0000 2024 | favorite_count: 38 | lang: en @miiura They really focused on just coding for this model iteration honestly\nIt’s great| created_at: Tue Feb 25 00:45:35 +0000 2025 | favorite_count: 43 | reply_count: 1 | lang: en @AetherAurelia you cooked| created_at: Thu Jan 09 15:33:53 +0000 2025 | favorite_count: 40 | lang: en @theo $75/mtok input and $150/mtok output is outrageous| created_at: Thu Feb 27 20:15:14 +0000 2025 | favorite_count: 41 | lang: en Making progress - this is with kfd + Cowabunga Lite (no top gaps) - though it’s quite buggy - no settings names and it took a while to apply. Testing in progress for other devices… https://t.co/fRJKVtFxHF| created_at: Mon Aug 07 07:53:55 +0000 2023 | favorite_count: 35 | reply_count: 3 | retweet_count: 4 | lang: en @hot_girl_spring works on my machine™| created_at: Mon Aug 05 11:18:58 +0000 2024 | favorite_count: 36 | lang: en @saltyAom @Aizkmusic the more you know?!| created_at: Thu Jun 27 00:59:24 +0000 2024 | favorite_count: 34 | lang: en Private ControlConfig testers got it working on various iOS versions/devices - now going to start work on icon packs and other features in-app 😊🙌 https://t.co/U0XKlwALqc| created_at: Tue Aug 08 08:33:32 +0000 2023 | favorite_count: 31 | reply_count: 2 | retweet_count: 1 | lang: en @stalkermustang @OfficialLoganK doesn't seem to be using the actual model just yet! https://t.co/OpdzEuyClN| created_at: Tue Jan 28 20:10:29 +0000 2025 | favorite_count: 32 | quote_count: 1 | lang: en @yacineMTB nah stickies is the goat https://t.co/uOrwv9FZcQ| created_at: Mon Dec 23 14:33:52 +0000 2024 | favorite_count: 32 | lang: en @BlushOf2021 @p1nkempress_gd human (or probably, ai) has to be here because what about the million edge cases?\nwhat if it’s a paste from somewhere else with the text inside?| created_at: Sun Dec 22 21:35:48 +0000 2024 | favorite_count: 31 | reply_count: 2 | lang: en @yacineMTB - yapping\n- spamming mute key every 2 seconds| created_at: Thu Dec 26 17:33:39 +0000 2024 | favorite_count: 29 | lang: en ControlConfig Updates (1/2)\nNew in-app tutorial for both KFD and MDC (KFD shown) with the KFD one also showing steps needed on Cowabunga Lite https://t.co/66lGQSn61w| created_at: Tue Aug 08 17:40:32 +0000 2023 | favorite_count: 30 | reply_count: 4 | retweet_count: 2 | lang: en @AviSchiffmann Overall isn’t this negative? More 0-value AI content, filling the internet…\nIf they wanted to artificially boost likes they could have done that ages ago, without AI.| created_at: Sat Jan 04 02:24:10 +0000 2025 | favorite_count: 30 | reply_count: 2 | lang: en @ctjlewis @jarredsumner server side jquery? we’ve come full circle…| created_at: Fri Nov 24 18:53:55 +0000 2023 | favorite_count: 28 | reply_count: 2 | lang: en @yacineMTB am i the prime example\nbuilt ui + first ver in like a weekend, now aiming it to become the best ai search https://t.co/3ZOJQacnG6| created_at: Sat Aug 10 22:33:35 +0000 2024 | favorite_count: 28 | reply_count: 5 | lang: en @theo how/what did you even do this because how does nothing from this whole UI rerender while typing\nprofessional react skill issue from me 😭 https://t.co/r3MccrfMdP| created_at: Thu Jan 16 22:56:27 +0000 2025 | favorite_count: 28 | reply_count: 2 | lang: en @theo @rauchg why not tauri? i’d imagine there isn’t much native logic to do, and it would make the app much smaller\nunless you want to make a global chat bar ui or something (like a raycast bar but t3 chat)\nhotkeys shouldn’t be too hard in rust really| created_at: Sun Jan 12 03:01:28 +0000 2025 | favorite_count: 28 | reply_count: 2 | lang: en @NizzyABI @ryandavogel not sure about this one. you don't want to put overly distracting animations behing/on the main content and or something you might use often.\ni would go for something simple, possibly a split layout. here's my 5min v0 on it (obviously you would perfect the right layouts, dark https://t.co/HsEd4J342M| created_at: Sat Feb 15 16:41:34 +0000 2025 | favorite_count: 27 | reply_count: 7 | lang: en mini-update!\nfree users now get 10 image upload messages a day!\n(and you can now send a message with just an image) https://t.co/25Xd8BSlgd| created_at: Tue Feb 25 23:47:07 +0000 2025 | favorite_count: 28 | reply_count: 4 | retweet_count: 3 | lang: en @sabziz feature requests\n- remove the grok button in the bottom right\n- hide subscribe, only if you're already followed\n- put the menu items you hide into the \"more\"\n- option to make the post button smaller/more like a part of the list than a huge distracting button\n- zen mode which lets| created_at: Sat Jan 18 11:26:51 +0000 2025 | favorite_count: 25 | reply_count: 1 | lang: en @aidenybai skill issue type cpu? (lol) https://t.co/QyKbRLVRBL| created_at: Thu Jan 09 18:32:08 +0000 2025 | favorite_count: 25 | lang: en @eepyeri lmao what\n\"oh hey there! before you dropship this from aliexpress, just to let you know it miiiight be illegal. just letting you know! <3\"| created_at: Sat Jan 11 22:41:59 +0000 2025 | favorite_count: 25 | reply_count: 2 | lang: en @AetherAurelia Oh no| created_at: Tue Dec 31 15:40:45 +0000 2024 | favorite_count: 24 | lang: en @albrorithm yeah! one man team.\nthank you 😁| created_at: Mon Feb 10 00:12:16 +0000 2025 | favorite_count: 23 | reply_count: 1 | lang: en ControlConfig - backup/revert system, nearly done.\n(swiftui looks so good, and why is Filza so buggy??) https://t.co/MNXub6lNdC| created_at: Wed Mar 08 23:01:42 +0000 2023 | favorite_count: 21 | retweet_count: 2 | lang: en @SullyOmarr grounding is very expensive on gemini's API though... $0.04 per query?\nbeen building my own ai search. works very well https://t.co/JodZXxqk5q| created_at: Fri Jan 03 23:50:47 +0000 2025 | favorite_count: 22 | reply_count: 3 | lang: en @itsandrewgao @LangChainAI @browserbasehq @ExaAILabs @firecrawl_dev @OpenAI don’t even need browsers (slow) or firecrawl (also slow), in theory\nshould I build this in 1-2days and make it oss\n“Open Research”| created_at: Mon Feb 03 00:31:09 +0000 2025 | favorite_count: 21 | reply_count: 1 | retweet_count: 1 | lang: en @SnazzyLabs Then your daughter can watch it back on her vision pro with custom facial interface specifically for her face!\nOr maybe her vision lite, in 2027| created_at: Sun Feb 02 18:33:42 +0000 2025 | favorite_count: 21 | lang: en @aidenybai no it just calls o1 or whatever, it just doesnt change the model name at the top left\nclick on rewrite to see actual model used| created_at: Thu Jan 30 17:01:00 +0000 2025 | favorite_count: 21 | lang: en ⚠️ Respring bug fix\nIf your device is respringing on rotate to landscape after having used ControlConfig, please ensure the Stage Manager module is added like this (both toggles must either be on or off to prevent the issue) https://t.co/1h9UmQfpCt| created_at: Fri Mar 17 07:53:41 +0000 2023 | favorite_count: 19 | quote_count: 1 | reply_count: 3 | retweet_count: 1 | lang: en @aidenybai Maybe it’s also related to them being emojis?\nHow does twitter render emojis internally? Since e.g. twemoji has some library to turn any emojis on the page into twemoji SVGs\nI wonder if something similiar is happening| created_at: Thu Jan 09 17:19:38 +0000 2025 | favorite_count: 18 | reply_count: 4 | lang: en generative ui is quite fun. adding lots of these mini cards into search. https://t.co/hGzo3IlSrX| created_at: Fri Jan 03 15:15:00 +0000 2025 | favorite_count: 19 | reply_count: 2 | retweet_count: 2 | lang: en @yacineMTB Forget google pixels it’s gonna be every new android phone eventually\nPlus they’re going to start enforcing gemini as assistant default to OEMs with the rise of competitors like perplexity assistant| created_at: Tue Jan 28 00:22:26 +0000 2025 | favorite_count: 17 | reply_count: 2 | lang: en @XxsempaiixX @AnxiousHolly isn’t it just a local player or something| created_at: Mon Aug 05 14:42:35 +0000 2024 | favorite_count: 18 | reply_count: 3 | lang: en today's shipments:\nmaking landing feel... beautiful. https://t.co/jgZ2bwH8bU| created_at: Mon Jan 13 20:42:51 +0000 2025 | favorite_count: 18 | reply_count: 7 | lang: en vibe coded away the whole repo: https://t.co/abvgdVijk0| created_at: Sat Mar 01 22:16:45 +0000 2025 | favorite_count: 19 | quote_count: 1 | reply_count: 2 | lang: en @LeakerApple tiktok is such a joke\nthat iphone 11 doesnt even.. have OLED| created_at: Sun Aug 04 14:25:21 +0000 2024 | favorite_count: 18 | lang: en got a bit bored on the flight… @alistaiir @m1guelpf\nfound/documented apis for oneworld flights past the one we saw - there’s another two and also even one for weather https://t.co/1kgoUFMeIT| created_at: Thu Dec 07 13:38:39 +0000 2023 | favorite_count: 18 | quote_count: 1 | reply_count: 2 | lang: en TIL... you can resize arc at same time on X and Y using shift (and or) option https://t.co/llmGIfDZ6k| created_at: Sat Feb 08 21:07:16 +0000 2025 | favorite_count: 17 | reply_count: 4 | lang: en javascript is easy!\nalso javascript: https://t.co/aFJVhlMfBy| created_at: Sun Jan 05 18:44:18 +0000 2025 | favorite_count: 17 | reply_count: 1 | retweet_count: 1 | lang: en new: youtube summary/transcript/chat\nshipped in 3 hours today (@cursor_ai helped!)\npowered by @aisdk ✨ https://t.co/vMHNuh7ksn| created_at: Sun Dec 22 19:31:02 +0000 2024 | favorite_count: 17 | reply_count: 5 | retweet_count: 3 | lang: en @rjonesy @nikitabier Aliexpress does similiar stuff https://t.co/Bqu32PPlKN| created_at: Wed Jan 15 07:39:49 +0000 2025 | favorite_count: 17 | reply_count: 1 | lang: en i am sad to announce that I have made hot chocolate with skimmed/no-fat milk on accident.\nit tastes absolutely horrible.\non behalf of big cocoa, i am truly sorry for my actions.| created_at: Sun Jan 05 00:29:14 +0000 2025 | favorite_count: 17 | reply_count: 7 | retweet_count: 1 | lang: en @aidenybai I mean I still feel they are the most comprehensive set of docs i’ve ever seen\nBut maybe to get there they’ve forgotten their roots in making payements extremely simple?| created_at: Sun Jan 12 00:09:34 +0000 2025 | favorite_count: 16 | reply_count: 1 | lang: en @MMatt14 @Jiankui_He those who know| created_at: Mon Dec 23 14:13:34 +0000 2024 | favorite_count: 16 | lang: en search answer quality is 🆙🔥\nsoon gonna get to the point where i can replace chatgpt and perplexity and gemini with this 😇 https://t.co/22NcFmVq0I| created_at: Mon Jan 13 01:05:10 +0000 2025 | favorite_count: 16 | reply_count: 4 | lang: en @aidan_mclau generally why does it score so on aidanbench?| created_at: Fri Dec 27 21:06:41 +0000 2024 | favorite_count: 16 | reply_count: 2 | lang: en @theo would be fun to see some open data on financing this whole thing\naverage token usage for free and paid users, how much do you pay for PTU to providers, how much paid serverless, usage per model\nand most interesting: are you making a profit or on trajectory to do so ?| created_at: Fri Jan 24 22:02:40 +0000 2025 | favorite_count: 16 | reply_count: 2 | lang: en @seatedro they also offer... ai safety\ncan't wait for hybrid claude reasoning though| created_at: Sat Feb 22 22:29:23 +0000 2025 | favorite_count: 15 | reply_count: 1 | lang: en @tom_doerr @perplexity_ai personally, https://t.co/3ZOJQabPQy is better though fr| created_at: Fri Aug 09 09:34:30 +0000 2024 | favorite_count: 15 | retweet_count: 1 | lang: en More details:\nWriting Tools and Notification Summaries are based using LORA adapters on top of the on-device 3B LLM, however the device can choose to also do Writing Tools on the server (Cloud Compute) - seen more often on Mac, and in the mail app.\nThere is seemingly also a| created_at: Thu Sep 19 22:08:37 +0000 2024 | favorite_count: 15 | reply_count: 1 | lang: en @adonis_singh @mark_k They fine tune a model to get enough usage data\nThen maybe use that usage data to fine tune back the base one (which itself is probably a fine tune over the API models)…| created_at: Wed Jan 15 00:34:10 +0000 2025 | favorite_count: 15 | lang: en In the works… all (normal) modules movable, sizing and hidden modules next. https://t.co/NcqDWiOOD8| created_at: Tue Mar 07 23:33:51 +0000 2023 | favorite_count: 14 | quote_count: 1 | reply_count: 1 | retweet_count: 2 | lang: en @n0w00j get your head in the game https://t.co/Dk1DqNUYqe| created_at: Wed Dec 25 23:42:04 +0000 2024 | favorite_count: 13 | reply_count: 2 | lang: en @snowmaker well i didnt apply to yc but building that at → https://t.co/3ZOJQabPQy| created_at: Sat Aug 10 22:34:11 +0000 2024 | favorite_count: 14 | quote_count: 1 | reply_count: 1 | lang: en @jaredpalmer never deleting this app https://t.co/OmXICN9X8T| created_at: Tue Jan 14 18:11:29 +0000 2025 | favorite_count: 14 | lang: en update #4 for ➡️ https://t.co/PBPdAULECs\nsadly, i also do not have $1.8m in funding for a domain.\nhowever, i did ship so many things that it was too much to fit in one tweet.\n⤵️ changelog thread: 🧵 (1/8)| created_at: Thu Aug 01 12:57:25 +0000 2024 | favorite_count: 14 | reply_count: 2 | lang: en iOS 16 all CC modules with MDC, coming soon to controlconfig. https://t.co/sqahBOuXER| created_at: Mon Mar 06 08:03:00 +0000 2023 | favorite_count: 12 | reply_count: 1 | retweet_count: 1 | lang: en @ImSh4yy what do you have to say to this. I hear VC is furious https://t.co/3gYhWgK8um| created_at: Sat Jan 04 23:21:12 +0000 2025 | favorite_count: 14 | reply_count: 1 | lang: en @seatedro should i| created_at: Tue Jan 28 16:59:23 +0000 2025 | favorite_count: 13 | reply_count: 3 | lang: en" 11 | } 12 | ], 13 | "requestId": "cd2220ec3f00d145b2168e80820f2c1e", 14 | "costDollars": { 15 | "total": 0.001, 16 | "contents": { 17 | "text": 0.001 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/lib/test_data/results-rauchg.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "results": [ 4 | { 5 | "id": "x.com/rauchg", 6 | "title": "@rauchg on X: It's always day zero", 7 | "url": "http://x.com/rauchg", 8 | "publishedDate": "2008-07-22T22:54:37.000Z", 9 | "author": "rauchg", 10 | "text": "@vercel CEO| profile_url: http://rauchg.com | name: Guillermo Rauch | created_at: Tue Jul 22 22:54:37 +0000 2008 | favourites_count: 56132 | followers_count: 257478 | friends_count: 214 | media_count: 4111 | statuses_count: 45425 | location: San Francisco, CA It's always day zero| created_at: Wed Mar 12 23:43:15 +0000 2025 | favorite_count: 64 | reply_count: 7 | retweet_count: 1 | lang: en https://t.co/Ndyy2WVL2d| created_at: Mon Nov 25 13:51:17 +0000 2019 | favorite_count: 14305 | quote_count: 202 | reply_count: 74 | retweet_count: 3672 | lang: zxx The American dream fr https://t.co/hD1sYnBhW8| created_at: Sat Nov 23 01:57:58 +0000 2024 | favorite_count: 11365 | quote_count: 76 | reply_count: 118 | retweet_count: 442 | lang: en Zuck has so far open sourced and given the world:\n◆ React\n◆ React Native\n◆ PyTorch\n◆ Llama\n◆ GraphQL\n◆ Flow\n◆ Jest\n◆ Relay\n◆ HHVM / Hack\n◆ Yoga\n◆ Hermes\n◆ RocksDB\n◆ Zstandard\nGoated.| created_at: Tue Nov 14 18:16:57 +0000 2023 | favorite_count: 10378 | quote_count: 166 | reply_count: 345 | retweet_count: 835 | lang: en I built https://t.co/YtUfA7fIyM – verify you're human by playing DOOM and killing at least 3 enemies 😁 (in nightmare mode)\npowered by webassembly × libsdl, ui built in @v0 https://t.co/kv6TAbJglx| created_at: Tue Dec 31 16:26:52 +0000 2024 | favorite_count: 7908 | quote_count: 181 | reply_count: 274 | retweet_count: 1060 | lang: en Remote work is individual convenience at the expense of group effectiveness| created_at: Sun Jan 19 23:04:34 +0000 2025 | favorite_count: 6183 | quote_count: 313 | reply_count: 816 | retweet_count: 290 | lang: en LaTeX.css 😍\nhttps://t.co/n2ETCmle2k https://t.co/guHkIgW1oS| created_at: Sat May 23 20:43:38 +0000 2020 | favorite_count: 5556 | quote_count: 145 | reply_count: 57 | retweet_count: 1319 | lang: en real ones keep the default create-next-app favicon https://t.co/EIiPpB644Z| created_at: Fri Nov 22 18:43:12 +0000 2024 | favorite_count: 5657 | quote_count: 58 | reply_count: 85 | retweet_count: 380 | is_quote_status: True | lang: en Just ran software update. Got this.\nSteve would have had an aneurysm. https://t.co/TjXdrVYFQT| created_at: Wed Oct 16 16:34:50 +0000 2024 | favorite_count: 4810 | quote_count: 38 | reply_count: 134 | retweet_count: 132 | lang: en This is the only color palette you need to go from $0 to $1B ARR https://t.co/q3HtQh0AW7| created_at: Mon Feb 24 16:05:47 +0000 2025 | favorite_count: 4864 | quote_count: 30 | reply_count: 85 | retweet_count: 173 | lang: en Congrats President Trump 🇺🇸\nExcited about the impact 𝕏, prediction markets, citizen journalism, podcasts, and the open web played in fostering democracy and a free media. It's a new world.| created_at: Wed Nov 06 16:56:06 +0000 2024 | favorite_count: 4713 | quote_count: 329 | reply_count: 399 | retweet_count: 149 | lang: en We've acquired https://t.co/PwO0TEzOO6, the fastest code search engine on the planet (of over 500k+ Git repos).\nWe'll support it as a standalone tool, API, and integrate its search engine into @v0 & the @vercel platform. https://t.co/GNBUTfam60| created_at: Wed Nov 20 22:38:16 +0000 2024 | favorite_count: 4235 | quote_count: 45 | reply_count: 112 | retweet_count: 311 | is_quote_status: True | lang: en URL design matters. Ship beautiful URLs| created_at: Thu Dec 12 18:55:47 +0000 2024 | favorite_count: 4131 | quote_count: 60 | reply_count: 137 | retweet_count: 217 | lang: en money.css\na​n​a​t​o​l​y​z​e​n​k​o​v​.​c​o​m​/​r​e​s​i​z​a​b​i​l​l\nhttps://t.co/IVpkcMeLYM| created_at: Wed Jan 08 15:12:11 +0000 2025 | favorite_count: 4086 | quote_count: 30 | reply_count: 90 | retweet_count: 248 | lang: cs @getpeid @jack No app store, no developer licenses, hyperlinks for everything, review-less deployment model, dynamic edge rendering for instant cold boots, etc.| created_at: Thu Nov 24 20:04:30 +0000 2022 | favorite_count: 3792 | quote_count: 31 | reply_count: 134 | retweet_count: 188 | lang: en This `browsh` text-based browser is so. amazingly. good.\nThis is what the elders expected `lynx` to become\nhttps://t.co/nkTmcwk3c3 https://t.co/3BtkYfR91G| created_at: Mon Jul 09 07:08:53 +0000 2018 | favorite_count: 3745 | quote_count: 132 | reply_count: 40 | retweet_count: 1436 | lang: en People who are good at coming up with ideas will enjoy great leverage in the age of AI| created_at: Tue Feb 18 21:26:57 +0000 2025 | favorite_count: 3980 | quote_count: 117 | reply_count: 219 | retweet_count: 394 | lang: en This is a remarkable idea. Turns every website into an API. Think of it as automatic Structured Output for any website on the internet. Wild. https://t.co/SRXDuux1ne https://t.co/ubiCtgPiP4| created_at: Tue Sep 10 16:14:39 +0000 2024 | favorite_count: 3831 | quote_count: 51 | reply_count: 105 | retweet_count: 258 | lang: en AI vectorization works so freaking well\nleft: low quality jpg (input)\nright: high quality svg (output) https://t.co/EKRuYHHV5S| created_at: Wed Nov 27 17:10:04 +0000 2024 | favorite_count: 3772 | quote_count: 23 | reply_count: 72 | retweet_count: 102 | lang: en Well played\nhttps://t.co/RnlCKZe8dx\nAlso: modern web browsers are engineering marvels https://t.co/l4qPSCdnCU| created_at: Thu May 11 02:55:23 +0000 2023 | favorite_count: 3666 | quote_count: 92 | reply_count: 110 | retweet_count: 426 | lang: en <Mafs />: React components for Math 🧮 Delightful!\nhttps://t.co/y0aRMfKU9o https://t.co/2plZPhC37H| created_at: Mon Jan 02 18:55:25 +0000 2023 | favorite_count: 3594 | quote_count: 28 | reply_count: 30 | retweet_count: 454 | lang: en I want this thing to trigger immediately. No delays, no animations, no inline suggestions. Thanks! https://t.co/EE5Kn5I9Po| created_at: Thu Mar 28 02:49:07 +0000 2024 | favorite_count: 3537 | quote_count: 27 | reply_count: 166 | retweet_count: 82 | lang: en Hello World from Thomas, our 5th 🖤 https://t.co/lMTqG1VsZm| created_at: Sun Dec 10 23:29:36 +0000 2023 | favorite_count: 3536 | quote_count: 3 | reply_count: 265 | retweet_count: 14 | lang: en Guess what this PR did https://t.co/DQ068Y5QUW| created_at: Tue Sep 03 20:22:39 +0000 2024 | favorite_count: 3463 | quote_count: 112 | reply_count: 1153 | retweet_count: 57 | lang: en This is brilliant and will save devtools authors many a headache 😁\nhttps://t.co/eo1kNWc8mU https://t.co/rceFyffQOO| created_at: Mon Aug 07 00:24:09 +0000 2023 | favorite_count: 3477 | quote_count: 34 | reply_count: 42 | retweet_count: 484 | lang: en https://t.co/FYsg9qtlKp| created_at: Thu May 18 15:38:39 +0000 2023 | favorite_count: 3331 | quote_count: 39 | reply_count: 29 | retweet_count: 311 | lang: zxx Idea to production timelines are compressing https://t.co/6CPMsozJ9j| created_at: Tue Feb 04 16:04:07 +0000 2025 | favorite_count: 3450 | quote_count: 16 | reply_count: 31 | retweet_count: 137 | lang: en We @vercel just raised a $102M Series C @ 1.1B on our mission to help build a faster Web, made for everyone.\nOur goals:\n1️⃣ Build the SDK for Web\n2️⃣ Lower the barrier of entry\n3️⃣ Focus on the end-user\nhttps://t.co/lRhHrjNFev| created_at: Wed Jun 23 15:04:58 +0000 2021 | favorite_count: 3327 | quote_count: 121 | reply_count: 217 | retweet_count: 318 | lang: en https://t.co/ycjfDrWr0E| created_at: Mon Jul 24 14:56:02 +0000 2023 | favorite_count: 3257 | quote_count: 33 | reply_count: 66 | retweet_count: 161 | lang: zxx There's no place like localhost:3000| created_at: Mon Dec 09 04:18:55 +0000 2024 | favorite_count: 3300 | quote_count: 28 | reply_count: 143 | retweet_count: 287 | lang: en I have `python3`, `python3.8`, `python3.9`, `python3.10`, `python3.11`, `pip`, `pip3`, `pip3.8`, `pip3.9`, `pip3.10`, `pip3.11` in $​PATH. AMA.| created_at: Fri Jul 28 00:42:01 +0000 2023 | favorite_count: 3169 | quote_count: 67 | reply_count: 251 | retweet_count: 158 | lang: ht https://t.co/SJEgkSKUrt| created_at: Thu Jan 30 19:04:15 +0000 2025 | favorite_count: 3274 | quote_count: 12 | reply_count: 64 | retweet_count: 91 | lang: zxx DeepSeek just emboldened and inspired startups all over the world to take on the giants. A lot of imagined moats are vanishing. Exciting times.| created_at: Mon Jan 27 19:48:04 +0000 2025 | favorite_count: 3205 | quote_count: 30 | reply_count: 94 | retweet_count: 301 | lang: en I think about this a lot. Optimize your published packages people. https://t.co/ct95oQHv2k| created_at: Tue Apr 23 20:35:30 +0000 2024 | favorite_count: 3010 | quote_count: 23 | reply_count: 39 | retweet_count: 298 | lang: en TypeScript sucked out much of the joy I had reading property of undefined NaN [object Object] CrashLoopBackOff| created_at: Sun May 07 20:59:58 +0000 2023 | favorite_count: 2908 | quote_count: 23 | reply_count: 52 | retweet_count: 222 | lang: en Nice minimalist print-friendly bio site built with @shadcn & @nextjs.\n→ https://t.co/Y5AHTIPdGc https://t.co/OMJCHpMDji| created_at: Tue Dec 26 16:45:08 +0000 2023 | favorite_count: 2896 | quote_count: 54 | reply_count: 77 | retweet_count: 186 | lang: en We’re testing a @v0 extension for @code / @cursor_ai https://t.co/wE5JsQGeC6| created_at: Sat Jan 04 04:14:53 +0000 2025 | favorite_count: 2908 | quote_count: 76 | reply_count: 230 | retweet_count: 121 | lang: en No big deal it’s just Debian Linux running inside WebAssembly running inside the browser running on your phone\nhttps://t.co/D0C4fIFjFe https://t.co/JtbVKtqHJH| created_at: Sat Nov 16 18:49:10 +0000 2024 | favorite_count: 2758 | quote_count: 24 | reply_count: 87 | retweet_count: 221 | lang: en As of today, I'm officially an American citizen.\nMy gratitude goes out to everyone who's helped me and welcomed me here. It's been the journey and honor of a lifetime.\nI look forward to contributing to this Dream for years to come.| created_at: Tue Apr 05 17:42:22 +0000 2022 | favorite_count: 2681 | quote_count: 8 | reply_count: 203 | retweet_count: 29 | lang: en Some engineering principles I live by:\n✓ Make it work, make it right, make it fast\n✓ Progressive disclosure of complexity\n✓ Minimize the number of concepts & modes\n✓ Most 'flukes' aren't… your tech just sucks\n✓ Feedback must be given to users instantly\n✓ Maximize user| created_at: Sun Nov 03 18:25:29 +0000 2024 | favorite_count: 2747 | quote_count: 33 | reply_count: 58 | retweet_count: 306 | lang: en Flying to Argentina 🇦🇷 after a years-long hiatus. Lots has changed since my last visit. Argentina now has the world’s first Libertarian president in @JMilei. More optimism and startups being born. Messi confirmed 🐐 status. Looking fwd to evaluating the delta with my own eyes.| created_at: Fri Oct 25 21:21:57 +0000 2024 | favorite_count: 2711 | quote_count: 18 | reply_count: 148 | retweet_count: 154 | lang: en The USA Constitution, with each Amendment as a git commit.\nI can't imagine a better tool to understand the historical evolution of this document…\nhttps://t.co/SoptlooyNO https://t.co/62RRSwllnK| created_at: Wed Oct 23 19:08:39 +0000 2019 | favorite_count: 2454 | quote_count: 86 | reply_count: 34 | retweet_count: 654 | lang: en I disabled video autoplay and the internet is 3142% better https://t.co/VWybRdiacK| created_at: Wed Dec 13 21:52:45 +0000 2017 | favorite_count: 2444 | quote_count: 46 | reply_count: 39 | retweet_count: 703 | lang: en Imagine https://t.co/Xd3YQzOYpf| created_at: Wed Oct 30 23:31:58 +0000 2024 | favorite_count: 2544 | quote_count: 59 | reply_count: 216 | retweet_count: 91 | lang: en I’m very impressed with the integration of AI features on @x — first social network to be deeply LLM-enhanced.\nGrok’s grounding in the realtime zeitgeist is a significant edge over competitors.| created_at: Fri Dec 27 02:09:37 +0000 2024 | favorite_count: 2515 | quote_count: 44 | reply_count: 203 | retweet_count: 140 | lang: en holy cow React Compiler is powerful 🤯 https://t.co/fiZCT0KREU| created_at: Thu May 16 17:48:09 +0000 2024 | favorite_count: 2404 | quote_count: 42 | reply_count: 85 | retweet_count: 166 | lang: en Would you use @nextjs devtools that help you visualize what gets pre-rendered, stream, caches, etc?\nLet us know what you’d like to see! https://t.co/kX6tlNHZew| created_at: Tue Feb 13 13:51:46 +0000 2024 | favorite_count: 2397 | quote_count: 27 | reply_count: 207 | retweet_count: 92 | lang: en Next.js in production at \nhttps://t.co/Fnm4uyNEXb https://t.co/bNjM0YrnuI| created_at: Fri Jul 24 21:04:55 +0000 2020 | favorite_count: 2339 | quote_count: 30 | reply_count: 28 | retweet_count: 216 | lang: en One of the best typefaces in the world is fully open source and on GitHub.\nWhat a time to be alive.\nhttps://t.co/J1A6iChRdB https://t.co/H7ie2b3Hsi| created_at: Tue Feb 05 00:19:14 +0000 2019 | favorite_count: 2261 | quote_count: 21 | reply_count: 14 | retweet_count: 450 | lang: en Possibly the most counterintuitive thing in life is that if you’re tired, you can work out to gain more energy.| created_at: Tue Nov 12 13:58:42 +0000 2024 | favorite_count: 2352 | quote_count: 13 | reply_count: 82 | retweet_count: 112 | lang: en For our upcoming London meetup, we didn't just create the slides in @v0, we created Google Slides in @v0 https://t.co/sNhCwqCmAF| created_at: Thu Nov 07 16:20:33 +0000 2024 | favorite_count: 2346 | quote_count: 60 | reply_count: 135 | retweet_count: 117 | lang: en Big day https://t.co/7Y2APCHqnu| created_at: Wed Dec 16 14:12:27 +0000 2020 | favorite_count: 2284 | quote_count: 18 | reply_count: 107 | retweet_count: 69 | lang: en △\nhttps://t.co/PsZ3ytwnDI| created_at: Mon Feb 17 09:22:04 +0000 2025 | favorite_count: 2334 | quote_count: 8 | reply_count: 57 | retweet_count: 102 | lang: qme TIL how the AirPods Pro landing page works\ntl;DW: *lots* of images 😂\nhttps://t.co/3inxOYPSoC\nh/t @shuding_ https://t.co/slkCbT7Lqg| created_at: Tue Oct 29 23:45:49 +0000 2019 | favorite_count: 2232 | quote_count: 83 | reply_count: 67 | retweet_count: 484 | lang: en Next.js 15 is out https://t.co/MsUfmq9xY5| created_at: Mon Oct 21 21:55:31 +0000 2024 | favorite_count: 2250 | quote_count: 36 | reply_count: 58 | retweet_count: 225 | lang: en ♟️Generative AI chess sets. So good.\nAnd love to see @google shipping with @nextjs App Router! https://t.co/8csN2yZ1c3| created_at: Tue Nov 26 22:27:07 +0000 2024 | favorite_count: 2244 | quote_count: 13 | reply_count: 38 | retweet_count: 75 | lang: en So proud of @shadcn joining the @vercel team.\nHis work has changed the way we think about building and distributing UI, and inspired countless developers.| created_at: Tue Aug 08 16:17:12 +0000 2023 | favorite_count: 2209 | quote_count: 11 | reply_count: 60 | retweet_count: 72 | is_quote_status: True | lang: en https://t.co/xiy8LYs63F| created_at: Sun Dec 18 19:01:32 +0000 2022 | favorite_count: 2183 | quote_count: 8 | reply_count: 19 | retweet_count: 240 | lang: zxx So good.\nhttps://t.co/C7F7pyHvOO https://t.co/5Rlk89SEUC| created_at: Mon Jan 30 18:16:42 +0000 2023 | favorite_count: 2159 | quote_count: 30 | reply_count: 55 | retweet_count: 243 | lang: en This React OTP input 🔥\nhttps://t.co/z8E8YbV2b0 https://t.co/6yZhh0wqZg| created_at: Mon Feb 19 21:21:07 +0000 2024 | favorite_count: 2211 | quote_count: 11 | reply_count: 36 | retweet_count: 196 | lang: en wow. https://t.co/KTdaRyDizp using @nextjs https://t.co/mR3ns4Nvf2| created_at: Mon Nov 21 19:46:22 +0000 2022 | favorite_count: 2146 | quote_count: 23 | reply_count: 46 | retweet_count: 157 | lang: en Cookie banners.\nJust visited a US website, from the US, that ships a 457.11kB (minified!) JS bundle of a GDPR \"banner SDK\" from a \"trust\" provider. Over 3x the size of React. It ships its very own version of jQuery inside.\nGoing to the \"trust\" provider website yields a 5.3s https://t.co/n2hmS0fbpo| created_at: Mon Oct 21 01:42:24 +0000 2024 | favorite_count: 2142 | quote_count: 48 | reply_count: 146 | retweet_count: 192 | lang: en https://t.co/HS65hAKINF is one of the most impressive applications of modern browser tech. It's a Rust-based immediate mode UI app compiled to the web via Webassembly targetting WebGPU. The whole app is just a large <canvas>. https://t.co/HnUuk5lTZy| created_at: Sat Nov 30 16:45:26 +0000 2024 | favorite_count: 2160 | quote_count: 15 | reply_count: 37 | retweet_count: 182 | lang: en Using Tailwind the first few times\nhttps://t.co/tJs0aMrOoO| created_at: Sat Feb 24 21:55:24 +0000 2024 | favorite_count: 2121 | quote_count: 37 | reply_count: 56 | retweet_count: 167 | lang: en The Air quality in SF is really bad.\nCoincidentally, I’m visiting my family in Argentina in a few days!\nYou could say ( •_•)>⌐■-■ I’m going to Buenos Aires.| created_at: Fri Nov 16 02:54:31 +0000 2018 | favorite_count: 2040 | quote_count: 9 | reply_count: 45 | retweet_count: 108 | lang: en well played zuck https://t.co/DsKiYsESWb| created_at: Sat May 11 22:09:48 +0000 2024 | favorite_count: 2116 | quote_count: 5 | reply_count: 17 | retweet_count: 88 | lang: en 🤯\nhttps://t.co/Qiyw3mCiLh https://t.co/zSmXIBOhRp| created_at: Sat Sep 11 20:43:22 +0000 2021 | favorite_count: 2104 | quote_count: 33 | reply_count: 33 | retweet_count: 159 | lang: qme A production-grade, monorepo-first, full stack @nextjs template.\n$ npx next-forge init\nVery thoughtfully engineered and documented. Covers auth, db & orm, payments, docs, blog, o11y, analytics, emails, and even feature flags & dark mode.\nhttps://t.co/sqIlmsHFc3| created_at: Sun Nov 03 20:24:29 +0000 2024 | favorite_count: 2134 | quote_count: 20 | reply_count: 64 | retweet_count: 181 | lang: en Full stack @v0 is here. It’s beautiful https://t.co/R4Zdw8gshv| created_at: Thu Nov 21 02:59:31 +0000 2024 | favorite_count: 2116 | quote_count: 20 | reply_count: 79 | retweet_count: 115 | is_quote_status: True | lang: en So sleek and fast (https://t.co/a3NpkHoi1h) https://t.co/5lPJhbydIe| created_at: Tue Jan 16 22:44:06 +0000 2024 | favorite_count: 2100 | quote_count: 10 | reply_count: 47 | retweet_count: 95 | lang: en A rite of passage in design engineering: implement the safety triangle for hover interactions https://t.co/oRFT28XxQy| created_at: Wed Mar 06 01:47:15 +0000 2024 | favorite_count: 2089 | quote_count: 21 | reply_count: 30 | retweet_count: 92 | lang: en I was so blown away by @javilopen's Angry Birds replication with AI that I had to verify it myself 😁\nWhat I learned:\n◆ The game was 100% written by AI. Every LOC\n◆ It only took 10-12 hours of total work\n◆ I saw the entire ChatGPT conversation. Over 400 messages exchanged. https://t.co/8sXVAeevh2| created_at: Wed Nov 01 20:01:14 +0000 2023 | favorite_count: 2085 | quote_count: 27 | reply_count: 40 | retweet_count: 204 | lang: en llms.txt and MCP feels like the beginning of the AI internet protocol stack.\nAs these conventions proliferate, the network gets more powerful and intelligent, and a lot more applications become possible.| created_at: Sun Feb 23 19:04:57 +0000 2025 | favorite_count: 2107 | quote_count: 26 | reply_count: 128 | retweet_count: 157 | lang: en The worst take about the web is that you should not use JS/React/etc because HTML alone is good enough.\nIt's an 'austerity mind virus' that has captured even some smart engineers. The idea that the web is for 'documents' and if it stays in its own little box like 1995 it's going https://t.co/Egk9IOzOTj| created_at: Sat Dec 14 23:50:03 +0000 2024 | favorite_count: 2083 | quote_count: 69 | reply_count: 148 | retweet_count: 115 | lang: en Roughly speaking, the value prop of Tailwind is to accomplish, with a bunch of well thought out CSS classes, what otherwise would take you 10 years to do manually (and likely wrongly), with 100 levels of HTML / JSX nested tag soup\nIt's really good.\nhttps://t.co/jB8goAxZeJ https://t.co/ETNGXj7eth| created_at: Thu Nov 07 08:53:07 +0000 2019 | favorite_count: 1992 | quote_count: 25 | reply_count: 43 | retweet_count: 326 | lang: en React is Legos for adults| created_at: Thu Nov 29 16:44:05 +0000 2018 | favorite_count: 1929 | quote_count: 33 | reply_count: 41 | retweet_count: 399 | lang: en Linus Torvalds introducing Linux: underpromise and overdeliver masterclass.\nIt’s just a hobby bro, won’t be big I swear 😁 https://t.co/KHwUZmJndG| created_at: Fri May 03 16:43:22 +0000 2024 | favorite_count: 1944 | quote_count: 15 | reply_count: 15 | retweet_count: 133 | lang: en This @openai researcher got hacked. The classic \"a coin just dropped\" thing with comments turned off and a fake website. An analysis on how the attack works and its tech stack 🧵 https://t.co/spx9G30hns| created_at: Sun Sep 22 23:58:49 +0000 2024 | favorite_count: 2001 | quote_count: 34 | reply_count: 43 | retweet_count: 162 | lang: en https://t.co/hIbxgDpL6Z| created_at: Tue Feb 27 20:35:53 +0000 2024 | favorite_count: 1965 | quote_count: 29 | reply_count: 49 | retweet_count: 110 | lang: zxx Your outie customizes @shadcn into beautiful, fun and unique design tokens and components\nhttps://t.co/Z2JFM2iJOG| created_at: Sun Feb 23 03:02:39 +0000 2025 | favorite_count: 2029 | quote_count: 24 | reply_count: 49 | retweet_count: 107 | lang: en Me: pff, how good can a sidebar be\n@shadcn: https://t.co/BbR1yDfydt| created_at: Fri Oct 18 19:54:07 +0000 2024 | favorite_count: 1985 | quote_count: 4 | reply_count: 28 | retweet_count: 61 | is_quote_status: True | lang: en Hydration error diffs have been achieved internally at demo day 😁 Coming to your @nextjs soon https://t.co/NDMZ7CYNR9| created_at: Fri Mar 01 16:09:27 +0000 2024 | favorite_count: 1948 | quote_count: 69 | reply_count: 105 | retweet_count: 133 | lang: en Probably nothing https://t.co/n0OUWQAwdo| created_at: Sun Feb 19 00:51:07 +0000 2023 | favorite_count: 1893 | quote_count: 27 | reply_count: 17 | retweet_count: 107 | lang: en Excited to introduce https://t.co/IzUlJQAgug to the world.\nCheck out some of the examples we built, like a pricing component built in 22 chat-like instructions.\nhttps://t.co/RTig1o9Jt1 https://t.co/RdSwTNTq0K| created_at: Thu Sep 14 16:07:37 +0000 2023 | favorite_count: 1802 | quote_count: 85 | reply_count: 92 | retweet_count: 191 | lang: en Next.js and React have never been easier, and they've also never been harder 🧵\nHere's the universal 'minimum viable app', a TODO list, implemented with the latest features in 45LOC https://t.co/RfeRb5Z4IZ| created_at: Sun Feb 11 19:57:24 +0000 2024 | favorite_count: 1903 | quote_count: 30 | reply_count: 110 | retweet_count: 115 | lang: en 50+ input components for @nextjs & @tailwindcss by @pacovitiello\nhttps://t.co/UH8MWSXbze\nhttps://t.co/da4oaNxZQO| created_at: Wed Oct 16 13:48:10 +0000 2024 | favorite_count: 1917 | quote_count: 8 | reply_count: 29 | retweet_count: 163 | lang: en In the age of AI, the worst thing you can do is tie up your ego with the skills you developed, whether technical or not.| created_at: Sun Sep 29 22:11:45 +0000 2024 | favorite_count: 1884 | quote_count: 34 | reply_count: 95 | retweet_count: 193 | lang: en We’ve open-sourced the UI infrastructure for Blocks/Artifacts/Canvas-like AI chat experiences. BYO model.\nI see huge potential in bringing this UX paradigm to new verticals: productivity, finance, travel, health, legal, logistics…\nHappy hacking 🤘\nhttps://t.co/NhOU6anq9X| created_at: Fri Nov 08 21:15:55 +0000 2024 | favorite_count: 1894 | quote_count: 18 | reply_count: 41 | retweet_count: 136 | lang: en Most creative JS hack I've seen in a while 😆\nUses ES6+ `Proxy` to implement `www`\nhttps://t.co/TUlpFSRjnE (h/t @shuding_) https://t.co/8ZGfns38f3| created_at: Fri Apr 30 20:39:56 +0000 2021 | favorite_count: 1831 | quote_count: 38 | reply_count: 25 | retweet_count: 297 | lang: en Took a peek at how @grok DeepSearch is implemented, it's very neat.\nOne thing that stood out to me is that it's resilient to aborting the stream half-way and refreshing the page. Most chatbots struggle with this.\nWhen they initially render the conversation, it detects whether https://t.co/hQx3sSlwzy| created_at: Mon Feb 24 19:06:23 +0000 2025 | favorite_count: 1907 | quote_count: 10 | reply_count: 47 | retweet_count: 128 | lang: en And this is all you have to know about programming languages. And complaining in general https://t.co/69gA09re2s| created_at: Fri Nov 15 05:50:27 +0000 2019 | favorite_count: 1779 | quote_count: 22 | reply_count: 20 | retweet_count: 439 | lang: en RIP Bram Moolenaar – 1961-2023\nhttps://t.co/2R95NMLF8B https://t.co/UuS7zuruvN| created_at: Sat Aug 05 15:59:35 +0000 2023 | favorite_count: 1797 | quote_count: 85 | reply_count: 28 | retweet_count: 406 | lang: nl Most common @nextjs request I get by a large margin: help me hire cracked Next.js frontend and design engineers.\nIf that’s you, please reply with your work / products / code. These are exciting opportunities at very high quality companies.| created_at: Mon Jan 20 17:28:56 +0000 2025 | favorite_count: 1847 | quote_count: 14 | reply_count: 355 | retweet_count: 50 | lang: en Remember CSS-in-HTML? https://t.co/FfojsdC0id| created_at: Tue Jul 09 15:42:34 +0000 2019 | favorite_count: 1746 | quote_count: 47 | reply_count: 76 | retweet_count: 239 | lang: en \"GitHub Copilot is a code synthesizer, not a search engine: the vast majority of the code that it suggests is uniquely generated and has never been seen before.\"\nThe future.\nhttps://t.co/eqApkWlyB7| created_at: Tue Jun 29 16:12:10 +0000 2021 | favorite_count: 1792 | quote_count: 53 | reply_count: 26 | retweet_count: 340 | lang: en The web is just getting started| created_at: Wed Oct 07 02:42:49 +0000 2020 | favorite_count: 1751 | quote_count: 34 | reply_count: 39 | retweet_count: 196 | lang: en @olegakbarov React literally powers https://t.co/9L6lDygNNN, Llama powers https://t.co/uMfErrHGaq… (where you work, according to your X bio).\nHow is that an abusive monopoly?| created_at: Tue Nov 14 18:29:11 +0000 2023 | favorite_count: 1774 | quote_count: 13 | reply_count: 58 | retweet_count: 23 | lang: en I love it when I’m done with some computer work so I can do other computer work to relax.| created_at: Sat Nov 04 16:18:34 +0000 2023 | favorite_count: 1760 | quote_count: 41 | reply_count: 47 | retweet_count: 203 | lang: en The secret to product quality is blood sweat and tears| created_at: Thu Feb 06 01:35:24 +0000 2025 | favorite_count: 1804 | quote_count: 55 | reply_count: 103 | retweet_count: 200 | lang: en Vercel’s embeddable CAPTCHA is coming. It will feel like a native extension of your product and adapt to your brand & design. https://t.co/TqjiB5gRjd| created_at: Mon Feb 10 07:03:54 +0000 2025 | favorite_count: 1787 | quote_count: 9 | reply_count: 101 | retweet_count: 41 | lang: en It's Friday. You can deploy. https://t.co/Onq0lDQRFD| created_at: Fri Aug 11 22:13:48 +0000 2023 | favorite_count: 1748 | quote_count: 35 | reply_count: 59 | retweet_count: 94 | lang: en" 11 | } 12 | ], 13 | "requestId": "0cefe03210e105ad73a1c2df12db90e3", 14 | "costDollars": { 15 | "total": 0.001, 16 | "contents": { 17 | "text": 0.001 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type React from "react"; 4 | 5 | import { 6 | useState, 7 | useRef, 8 | useEffect, 9 | useCallback, 10 | useTransition, 11 | startTransition, 12 | } from "react"; 13 | import NextImage from "next/image"; 14 | import { motion } from "framer-motion"; 15 | import { Button } from "@/components/ui/button"; 16 | import { Input } from "@/components/ui/input"; 17 | import { Card } from "@/components/ui/card"; 18 | import { 19 | X, 20 | Sparkles, 21 | Lock, 22 | Dices, 23 | GithubIcon, 24 | Save, 25 | Database, 26 | MessageSquare, 27 | Trash, 28 | } from "lucide-react"; 29 | import { analyseUser, type AlignmentAnalysis } from "./actions/analyze-tweets"; 30 | import { 31 | Tooltip, 32 | TooltipContent, 33 | TooltipProvider, 34 | TooltipTrigger, 35 | } from "@/components/ui/tooltip"; 36 | import { AnalysisPanel } from "./components/analysis-panel"; 37 | import { toast } from "sonner"; 38 | import { cn, getRandomPosition } from "@/lib/utils"; 39 | import { getBestAvatarUrl } from "@/lib/load-avatar"; 40 | import { 41 | initIndexedDB, 42 | cachePlacementsLocally, 43 | loadCachedPlacements, 44 | removeCachedPlacement, 45 | clearLocalCache, 46 | StoredPlacement, 47 | } from "@/lib/indexed-db"; 48 | import { useDebounce } from "@/hooks/use-debounce"; 49 | import { logger } from "@/lib/logger"; 50 | import { 51 | AlertDialogHeader, 52 | AlertDialogFooter, 53 | } from "@/components/ui/alert-dialog"; 54 | import { 55 | AlertDialog, 56 | AlertDialogTrigger, 57 | AlertDialogContent, 58 | AlertDialogTitle, 59 | AlertDialogDescription, 60 | AlertDialogCancel, 61 | AlertDialogAction, 62 | } from "@/components/ui/alert-dialog"; 63 | 64 | interface Position { 65 | x: number; 66 | y: number; 67 | } 68 | 69 | export interface Placement { 70 | id: string; 71 | src: string; 72 | position: Position; 73 | isDragging: boolean; 74 | loading?: boolean; 75 | username?: string; 76 | analysis?: AlignmentAnalysis; 77 | isAiPlaced?: boolean; 78 | timestamp?: Date; 79 | } 80 | 81 | export default function AlignmentChart() { 82 | const hasDoneFirstRandomPlace = useRef(false); 83 | const [images, setImages] = useState([]); 84 | const [username, setUsername] = useState(""); 85 | const [isAnalyzing, setIsAnalyzing] = useState(false); 86 | const [isLoading, setIsLoading] = useState(true); 87 | const chartRef = useRef(null); 88 | const containerRef = useRef(null); 89 | const [activeDragId, setActiveDragId] = useState(null); 90 | const [imageSize, setImageSize] = useState(60); 91 | const [chartSize, setChartSize] = useState({ width: 0, height: 0 }); 92 | const [isMobile, setIsMobile] = useState(false); 93 | const [newAnalysisId, setNewAnalysisId] = useState(null); 94 | 95 | const debouncedSave = useDebounce(cachePlacementsLocally, 500); 96 | 97 | useEffect(() => { 98 | async function loadCachedUsers() { 99 | try { 100 | setIsLoading(true); 101 | await initIndexedDB(); 102 | const cachedPlacements = await loadCachedPlacements(); 103 | 104 | if (cachedPlacements.length > 0) { 105 | const loadedImages: Placement[] = cachedPlacements.map((item) => ({ 106 | ...item, 107 | isDragging: false, 108 | loading: false, 109 | position: item.position, 110 | timestamp: item.timestamp ? new Date(item.timestamp) : new Date(), 111 | })); 112 | 113 | setImages(loadedImages); 114 | toast.success(`Loaded ${loadedImages.length} saved placements`); 115 | } 116 | } catch (error) { 117 | logger.error("Failed to load users from IndexedDB:", error); 118 | } finally { 119 | setIsLoading(false); 120 | } 121 | } 122 | 123 | loadCachedUsers(); 124 | }, []); 125 | 126 | // Save users to IndexedDB whenever the images state changes 127 | useEffect(() => { 128 | if (isLoading) return; 129 | 130 | // Call our debounced save function 131 | debouncedSave(images); 132 | }, [images, isLoading, debouncedSave]); 133 | 134 | useEffect(() => { 135 | const checkMobile = () => { 136 | setIsMobile(window.innerWidth < 768); 137 | }; 138 | 139 | checkMobile(); 140 | window.addEventListener("resize", checkMobile); 141 | 142 | return () => { 143 | window.removeEventListener("resize", checkMobile); 144 | }; 145 | }, []); 146 | 147 | useEffect(() => { 148 | const handleTouchMove = (e: TouchEvent) => { 149 | if ( 150 | e.target instanceof HTMLElement && 151 | e.target.tagName !== "INPUT" && 152 | !e.target.closest(".scrollable") 153 | ) { 154 | e.preventDefault(); 155 | } 156 | }; 157 | 158 | document.addEventListener("touchmove", handleTouchMove, { passive: false }); 159 | 160 | document.body.style.overflow = "hidden"; 161 | 162 | return () => { 163 | document.removeEventListener("touchmove", handleTouchMove); 164 | document.body.style.overflow = ""; 165 | }; 166 | }, []); 167 | 168 | useEffect(() => { 169 | const updateChartSize = () => { 170 | if (containerRef.current && chartRef.current) { 171 | const viewportHeight = window.innerHeight; 172 | const viewportWidth = window.innerWidth; 173 | const isMobileView = viewportWidth < 768; 174 | 175 | const reservedVerticalSpace = isMobileView ? 120 : 140; 176 | const topPadding = isMobileView ? 20 : 0; 177 | const availableHeight = 178 | viewportHeight - reservedVerticalSpace - topPadding; 179 | 180 | const horizontalPadding = isMobileView 181 | ? Math.max(40, viewportWidth * 0.1) 182 | : Math.max(20, viewportWidth * 0.05); 183 | const availableWidth = viewportWidth - horizontalPadding * 2; 184 | 185 | const size = Math.min(availableWidth, availableHeight); //constrained square 186 | 187 | setChartSize({ width: size, height: size }); 188 | 189 | const newImageSize = Math.max(40, Math.min(80, size / 7.5)); 190 | setImageSize(newImageSize); 191 | } 192 | }; 193 | 194 | if (containerRef.current) { 195 | const resizeObserver = new ResizeObserver(() => { 196 | updateChartSize(); 197 | }); 198 | 199 | resizeObserver.observe(containerRef.current); 200 | 201 | updateChartSize(); 202 | 203 | return () => { 204 | if (containerRef.current) { 205 | resizeObserver.unobserve(containerRef.current); 206 | } 207 | resizeObserver.disconnect(); 208 | }; 209 | } 210 | 211 | window.addEventListener("resize", updateChartSize); 212 | return () => window.removeEventListener("resize", updateChartSize); 213 | }, []); 214 | 215 | const alignmentToPosition = (analysis: AlignmentAnalysis): Position => { 216 | const xPercent = ((analysis.lawfulChaotic + 100) / 200) * 100; 217 | const yPercent = ((analysis.goodEvil + 100) / 200) * 100; 218 | 219 | return { x: xPercent, y: yPercent }; 220 | }; 221 | 222 | // Generate a profile image when a username is submitted 223 | const handleRandomAnalyze = async () => { 224 | if (!username.trim()) return; 225 | 226 | if (!hasDoneFirstRandomPlace.current) { 227 | hasDoneFirstRandomPlace.current = true; 228 | toast("Note", { 229 | description: 230 | "Heads up! You just used random placement mode (grey button). If you wanted to use the AI analysis, use the auto-analyze button (purple button)!", 231 | duration: 10000, 232 | }); 233 | return; 234 | } 235 | 236 | const cleanUsername = username.trim().replace(/^@/, ""); 237 | const randomPosition = getRandomPosition(); 238 | 239 | const tempImageId = `image-${Date.now()}`; 240 | const tempImage: Placement = { 241 | id: tempImageId, 242 | src: `https://unavatar.io/x/${cleanUsername}`, // Default initial URL 243 | position: randomPosition, 244 | isDragging: false, 245 | loading: true, 246 | username: cleanUsername, 247 | isAiPlaced: false, 248 | timestamp: new Date(), 249 | }; 250 | 251 | setImages((prev) => [...prev, tempImage]); 252 | setUsername(""); 253 | 254 | try { 255 | const finalUrl = await getBestAvatarUrl(cleanUsername); 256 | 257 | setImages((prev) => 258 | prev.map((img) => 259 | img.id === tempImageId 260 | ? { ...img, src: finalUrl, loading: false } 261 | : img 262 | ) 263 | ); 264 | } catch (error) { 265 | logger.error("Error loading profile image:", error); 266 | 267 | setImages((prev) => 268 | prev.map((img) => 269 | img.id === tempImageId ? { ...img, loading: false } : img 270 | ) 271 | ); 272 | } 273 | }; 274 | 275 | const handleAutoAnalyze = async () => { 276 | if (!username.trim()) return; 277 | 278 | setIsAnalyzing(true); 279 | 280 | const cleanUsername = username.trim().replace(/^@/, ""); 281 | 282 | try { 283 | const tempImageId = `image-${Date.now()}`; 284 | const tempImage: Placement = { 285 | id: tempImageId, 286 | src: `/grid.svg?height=100&width=100&text=Analyzing...`, 287 | position: getRandomPosition(), 288 | isDragging: false, 289 | loading: true, 290 | username: cleanUsername, 291 | isAiPlaced: true, 292 | timestamp: new Date(), 293 | }; 294 | 295 | setImages((prev) => [...prev, tempImage]); 296 | setUsername(""); 297 | 298 | const analysis = await analyseUser(cleanUsername); 299 | if (analysis.isError) { 300 | toast.error(`Error`, { 301 | description: analysis.explanation, 302 | position: "top-right", 303 | duration: 4000, 304 | }); 305 | 306 | setImages((prev) => 307 | prev.filter((img) => img.username !== cleanUsername) 308 | ); 309 | return; 310 | } 311 | const position = alignmentToPosition(analysis); 312 | 313 | const finalUrl = await getBestAvatarUrl(cleanUsername); 314 | 315 | setImages((prev) => 316 | prev.map((img) => 317 | img.id === tempImageId 318 | ? { 319 | ...img, 320 | src: finalUrl, 321 | position, 322 | loading: false, 323 | analysis, 324 | isAiPlaced: true, 325 | timestamp: new Date(), 326 | } 327 | : img 328 | ) 329 | ); 330 | 331 | setNewAnalysisId(tempImageId); 332 | 333 | setTimeout(() => { 334 | setNewAnalysisId(null); 335 | }, 5000); 336 | } catch (error) { 337 | logger.error("Error auto-analyzing:", error); 338 | 339 | setImages((prev) => prev.filter((img) => img.username !== cleanUsername)); 340 | 341 | toast.error(`Error`, { 342 | description: `Couldn't analyze tweets for @${cleanUsername}: ${ 343 | error instanceof Error ? error.message : "Unknown error" 344 | }`, 345 | position: "top-right", 346 | duration: 4000, 347 | }); 348 | } finally { 349 | setIsAnalyzing(false); 350 | } 351 | }; 352 | 353 | const handleMouseDown = (e: React.MouseEvent, id: string) => { 354 | e.preventDefault(); 355 | 356 | const image = images.find((img) => img.id === id); 357 | if (!image || image.loading || image.isAiPlaced) return; 358 | 359 | setActiveDragId(id); 360 | setImages( 361 | images.map((img) => (img.id === id ? { ...img, isDragging: true } : img)) 362 | ); 363 | }; 364 | 365 | const handleTouchStart = (e: React.TouchEvent, id: string) => { 366 | const image = images.find((img) => img.id === id); 367 | if (!image || image.loading || image.isAiPlaced) return; 368 | 369 | setActiveDragId(id); 370 | setImages( 371 | images.map((img) => (img.id === id ? { ...img, isDragging: true } : img)) 372 | ); 373 | }; 374 | 375 | const handleMouseMove = (e: MouseEvent) => { 376 | if (!activeDragId || !chartRef.current) return; 377 | 378 | const chartRect = chartRef.current.getBoundingClientRect(); 379 | const x = ((e.clientX - chartRect.left) / chartRect.width) * 100; 380 | const y = ((e.clientY - chartRect.top) / chartRect.height) * 100; 381 | 382 | setImages( 383 | images.map((img) => 384 | img.id === activeDragId 385 | ? { 386 | ...img, 387 | position: { 388 | x: Math.max(0, Math.min(x, 100)), 389 | y: Math.max(0, Math.min(y, 100)), 390 | }, 391 | } 392 | : img 393 | ) 394 | ); 395 | }; 396 | 397 | const handleTouchMove = (e: TouchEvent) => { 398 | if (!activeDragId || !chartRef.current) return; 399 | e.preventDefault(); 400 | 401 | const touch = e.touches[0]; 402 | const chartRect = chartRef.current.getBoundingClientRect(); 403 | const x = ((touch.clientX - chartRect.left) / chartRect.width) * 100; 404 | const y = ((touch.clientY - chartRect.top) / chartRect.height) * 100; 405 | 406 | setImages( 407 | images.map((img) => 408 | img.id === activeDragId 409 | ? { 410 | ...img, 411 | position: { 412 | x: Math.max(0, Math.min(x, 100)), 413 | y: Math.max(0, Math.min(y, 100)), 414 | }, 415 | } 416 | : img 417 | ) 418 | ); 419 | }; 420 | 421 | const handleMouseUp = () => { 422 | if (!activeDragId) return; 423 | 424 | setImages( 425 | images.map((img) => 426 | img.id === activeDragId ? { ...img, isDragging: false } : img 427 | ) 428 | ); 429 | 430 | setActiveDragId(null); 431 | }; 432 | 433 | const handleTouchEnd = () => { 434 | if (!activeDragId) return; 435 | 436 | setImages( 437 | images.map((img) => 438 | img.id === activeDragId ? { ...img, isDragging: false } : img 439 | ) 440 | ); 441 | 442 | setActiveDragId(null); 443 | }; 444 | 445 | const handleRemoveImage = async (id: string) => { 446 | setImages(images.filter((img) => img.id !== id)); 447 | 448 | removeCachedPlacement(id).catch((error) => { 449 | logger.error("Error removing placement from IndexedDB:", error); 450 | }); 451 | }; 452 | 453 | useEffect(() => { 454 | if (activeDragId) { 455 | // Mouse events 456 | window.addEventListener("mousemove", handleMouseMove); 457 | window.addEventListener("mouseup", handleMouseUp); 458 | 459 | // Touch events for mobile 460 | window.addEventListener("touchmove", handleTouchMove, { passive: false }); 461 | window.addEventListener("touchend", handleTouchEnd); 462 | window.addEventListener("touchcancel", handleTouchEnd); 463 | } 464 | 465 | return () => { 466 | // Remove mouse events 467 | window.removeEventListener("mousemove", handleMouseMove); 468 | window.removeEventListener("mouseup", handleMouseUp); 469 | 470 | // Remove touch events 471 | window.removeEventListener("touchmove", handleTouchMove); 472 | window.removeEventListener("touchend", handleTouchEnd); 473 | window.removeEventListener("touchcancel", handleTouchEnd); 474 | }; 475 | }, [activeDragId, images]); 476 | 477 | const analysesForPanel = images 478 | .filter((img) => img.isAiPlaced && img.analysis && !img.loading) 479 | .map((img) => ({ 480 | id: img.id, 481 | username: img.username || "unknown", 482 | imageSrc: img.src, 483 | analysis: img.analysis!, 484 | timestamp: img.timestamp || new Date(), 485 | })) 486 | .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); 487 | 488 | return ( 489 |
495 |
496 |
{ 499 | e.preventDefault(); 500 | handleAutoAnalyze(); 501 | }} 502 | > 503 | setUsername(e.target.value)} 507 | className="pr-20 rounded-full pl-4" 508 | autoCapitalize="none" 509 | spellCheck="false" 510 | type="text" 511 | disabled={isAnalyzing} 512 | /> 513 |
514 | 515 | 516 | 517 | 527 | 528 | 529 |

Randomly place a user on the chart

530 |
531 |
532 |
533 | 534 | 535 | 536 | 537 | 547 | 548 | 549 |

Analyze a user's tweets and place them on the chart

550 |
551 |
552 |
553 |
554 |
555 | 556 |
557 | 568 | 579 | 590 | 601 | 602 | 610 |
620 |
621 |
622 |
623 |
624 |
625 | 626 | {images.map((img) => ( 627 | handleMouseDown(e, img.id)} 652 | onTouchStart={(e) => handleTouchStart(e, img.id)} 653 | > 654 |
670 | 680 | 681 | {img.loading && img.isAiPlaced && ( 682 |
683 |
684 |
685 | )} 686 | 687 | {!img.loading && ( 688 | 706 | )} 707 | 708 | {img.isAiPlaced && !img.loading && ( 709 |
710 | 717 |
718 | )} 719 | 720 |
721 | @{img.username} 722 | {img.isAiPlaced && ( 723 | 724 | AI Placed 725 | 726 | )} 727 |
728 |
729 |
730 | ))} 731 |
732 | 733 |
734 | 735 | 739 | 740 | dub.sh/xchart 741 | 742 | 743 | 744 | original by{" "} 745 | 749 | rauchg 750 | 751 | ; magic ai version by{" "} 752 | 756 | f1shy-dev 757 | 758 | 759 | 760 |
761 | 762 | 763 | 764 | 765 | 775 | 776 | 777 | 778 | Are you sure? 779 | 780 | This will remove {images.length > 1 ? "all of your" : "your"}{" "} 781 | {images.length} saved placements from the chart. 782 | 783 | 784 | 785 | Cancel 786 | { 788 | setImages([]); 789 | clearLocalCache().catch(logger.error); 790 | }} 791 | > 792 | Continue 793 | 794 | 795 | 796 | 797 | 798 | 799 | {/* {isLoading && ( 800 |
801 |
802 |
803 |

Loading saved users...

804 |
805 |
806 | )} */} 807 |
808 | ); 809 | } 810 | --------------------------------------------------------------------------------