CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
App.jsx2459 linesDownload Raw Back to src
1import React, { useState, useEffect, useRef } from "react";2import { Heart, MessageCircle, Share2, Bookmark } from "lucide-react";3 4// Language Selector Component5function LanguageSelector({ selectedLanguage, onLanguageChange }) {6  return (7    <div style={{ display: "flex", gap: "10px", marginBottom: "20px", justifyContent: "center" }}>8      <button9        onClick={() => onLanguageChange("english")}10        style={{11          padding: "10px 24px",12          background: selectedLanguage === "english" ? "#fff" : "#1a1a1a",13          color: selectedLanguage === "english" ? "#000" : "#888",14          border: `1px solid ${selectedLanguage === "english" ? "#fff" : "#222"}`,15          borderRadius: "8px",16          cursor: "pointer",17          fontWeight: 600,18          fontSize: "14px",19          transition: "all 0.2s ease",20          textTransform: "uppercase",21          letterSpacing: "0.5px",22        }}23      >24         english25      </button>26      <button27        onClick={() => onLanguageChange("hindi")}28        style={{29          padding: "10px 24px",30          background: selectedLanguage === "hindi" ? "#fff" : "#1a1a1a",31          color: selectedLanguage === "hindi" ? "#000" : "#888",32          border: `1px solid ${selectedLanguage === "hindi" ? "#fff" : "#222"}`,33          borderRadius: "8px",34          cursor: "pointer",35          fontWeight: 600,36          fontSize: "14px",37          transition: "all 0.2s ease",38          textTransform: "uppercase",39          letterSpacing: "0.5px",40        }}41      >42         हिंदी 43      </button>44    </div>45  );46}47 48// Create Post Form Component49function CreatePostForm({ onSubmit, onCancel, loading }) {50  const [name, setName] = useState("");51  const [caption, setCaption] = useState("");52  const [mediaUrl, setMediaUrl] = useState("");53  const [mediaType, setMediaType] = useState("image");54  const [category, setCategory] = useState("tech");55  const [errors, setErrors] = useState({});56 57  const handleSubmit = (e) => {58    e.preventDefault();59    const newErrors = {};60 61    if (!name.trim()) newErrors.name = "Name is required";62    if (!caption.trim()) newErrors.caption = "Caption is required";63    if (!mediaUrl.trim()) newErrors.media_url = "Media URL is required";64    if (!mediaType.trim()) newErrors.media_type = "Media type is required";65    if (!category.trim()) newErrors.category = "Category is required";66 67    if (Object.keys(newErrors).length > 0) {68      setErrors(newErrors);69      return;70    }71 72    onSubmit({73      name: name.trim(),74      caption: caption.trim(),75      media_url: mediaUrl.trim(),76      media_type: mediaType.trim(),77      category: category.trim(),78    });79  };80 81  return (82    <div83      style={{84        position: "fixed",85        top: 0,86        left: 0,87        right: 0,88        bottom: 0,89        background: "rgba(0,0,0,0.9)",90        backdropFilter: "blur(8px)",91        display: "flex",92        alignItems: "center",93        justifyContent: "center",94        zIndex: 1001,95        padding: "20px",96      }}97      onClick={onCancel}98    >99      <div100        style={{101          background: "#0a0a0a",102          border: "1px solid #1a1a1a",103          borderRadius: "12px",104          padding: "32px",105          maxWidth: 500,106          width: "100%",107          boxShadow: "0 20px 50px rgba(0, 0, 0, 0.6)",108          maxHeight: "90vh",109          overflowY: "auto",110        }}111        onClick={(e) => e.stopPropagation()}112      >113        <h2114          style={{115            fontSize: "24px",116            fontWeight: 600,117            marginBottom: "8px",118            background: "linear-gradient(to bottom,#10775f,#139c77,#33b89b)",119            WebkitBackgroundClip: "text",120            WebkitTextFillColor: "transparent",121          }}122        >123          Create New Post124        </h2>125        <p style={{ color: "#666", fontSize: "13px", marginBottom: "24px" }}>126          All fields are required. Your post will be indexed with AI embeddings.127        </p>128 129        <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "16px" }}>130          {/* Name Field */}131          <div>132            <label style={{ display: "block", fontSize: "12px", color: "#aaa", marginBottom: "6px", fontWeight: 600 }}>133              Author Name / Post Title *134            </label>135            <input136              type="text"137              value={name}138              onChange={(e) => {139                setName(e.target.value);140                if (errors.name) setErrors({ ...errors, name: "" });141              }}142              placeholder="e.g., John Doe"143              style={{144                width: "100%",145                padding: "10px 12px",146                background: "#111",147                border: `1px solid ${errors.name ? "#dc2626" : "#222"}`,148                borderRadius: "8px",149                color: "#fff",150                fontSize: "13px",151                boxSizing: "border-box",152              }}153            />154            {errors.name && <div style={{ fontSize: "11px", color: "#f87171", marginTop: "4px" }}>{errors.name}</div>}155          </div>156 157          {/* Caption Field */}158          <div>159            <label style={{ display: "block", fontSize: "12px", color: "#aaa", marginBottom: "6px", fontWeight: 600 }}>160              Caption / Description *161            </label>162            <textarea163              value={caption}164              onChange={(e) => {165                setCaption(e.target.value);166                if (errors.caption) setErrors({ ...errors, caption: "" });167              }}168              placeholder="Describe your post..."169              style={{170                width: "100%",171                padding: "10px 12px",172                background: "#111",173                border: `1px solid ${errors.caption ? "#dc2626" : "#222"}`,174                borderRadius: "8px",175                color: "#fff",176                fontSize: "13px",177                fontFamily: "system-ui,sans-serif",178                resize: "vertical",179                minHeight: "80px",180                boxSizing: "border-box",181              }}182            />183            {errors.caption && <div style={{ fontSize: "11px", color: "#f87171", marginTop: "4px" }}>{errors.caption}</div>}184          </div>185 186          {/* Media URL Field */}187          <div>188            <label style={{ display: "block", fontSize: "12px", color: "#aaa", marginBottom: "6px", fontWeight: 600 }}>189              Media URL (Image) *190            </label>191            <input192              type="url"193              value={mediaUrl}194              onChange={(e) => {195                setMediaUrl(e.target.value);196                if (errors.media_url) setErrors({ ...errors, media_url: "" });197              }}198              placeholder="https://example.com/image.jpg"199              style={{200                width: "100%",201                padding: "10px 12px",202                background: "#111",203                border: `1px solid ${errors.media_url ? "#dc2626" : "#222"}`,204                borderRadius: "8px",205                color: "#fff",206                fontSize: "13px",207                boxSizing: "border-box",208              }}209            />210            {errors.media_url && <div style={{ fontSize: "11px", color: "#f87171", marginTop: "4px" }}>{errors.media_url}</div>}211            {mediaUrl && (212              <img213                src={mediaUrl}214                alt="Preview"215                style={{216                  marginTop: "8px",217                  maxWidth: "100%",218                  height: "auto",219                  maxHeight: "150px",220                  borderRadius: "6px",221                  objectFit: "cover",222                }}223                onError={(e) => {224                  e.target.style.display = "none";225                }}226              />227            )}228          </div>229 230          {/* Media Type Field */}231          <div>232            <label style={{ display: "block", fontSize: "12px", color: "#aaa", marginBottom: "6px", fontWeight: 600 }}>233              Media Type *234            </label>235            <select236              value={mediaType}237              onChange={(e) => {238                setMediaType(e.target.value);239                if (errors.media_type) setErrors({ ...errors, media_type: "" });240              }}241              style={{242                width: "100%",243                padding: "10px 12px",244                background: "#111",245                border: `1px solid ${errors.media_type ? "#dc2626" : "#222"}`,246                borderRadius: "8px",247                color: "#fff",248                fontSize: "13px",249                boxSizing: "border-box",250              }}251            >252              <option value="image">Image</option>253              <option value="video">Video</option>254            </select>255            {errors.media_type && <div style={{ fontSize: "11px", color: "#f87171", marginTop: "4px" }}>{errors.media_type}</div>}256          </div>257 258          {/* Category Field */}259          <div>260            <label style={{ display: "block", fontSize: "12px", color: "#aaa", marginBottom: "6px", fontWeight: 600 }}>261              Category *262            </label>263            <select264              value={category}265              onChange={(e) => {266                setCategory(e.target.value);267                if (errors.category) setErrors({ ...errors, category: "" });268              }}269              style={{270                width: "100%",271                padding: "10px 12px",272                background: "#111",273                border: `1px solid ${errors.category ? "#dc2626" : "#222"}`,274                borderRadius: "8px",275                color: "#fff",276                fontSize: "13px",277                boxSizing: "border-box",278              }}279            >280              <option value="tech">Tech</option>281              <option value="ai">AI</option>282              <option value="healthcare">Healthcare</option>283              <option value="food">Food</option>284              <option value="art">Art</option>285              <option value="education">Education</option>286              <option value="travel">Travel</option>287              <option value="music">Music</option>288              <option value="sports">Sports</option>289              <option value="web3">Web3</option>290              <option value="finance">Finance</option>291              <option value="movies">Movies</option>292              <option value="nature">Nature</option>293              <option value="socialmedia">Social Media</option>294              <option value="stocks">Stocks</option>295              <option value="vehicles">Vehicles</option>296              <option value="cafes">Cafes</option>297            </select>298            {errors.category && <div style={{ fontSize: "11px", color: "#f87171", marginTop: "4px" }}>{errors.category}</div>}299          </div>300 301          {/* Buttons */}302          <div style={{ display: "flex", gap: "12px", marginTop: "12px" }}>303            <button304              type="submit"305              disabled={loading}306              style={{307                flex: 1,308                padding: "12px 16px",309                background: loading ? "#333" : "#fff",310                color: loading ? "#666" : "#000",311                border: "none",312                borderRadius: "8px",313                cursor: loading ? "not-allowed" : "pointer",314                fontWeight: 700,315                fontSize: "14px",316                transition: "all 0.2s ease",317              }}318            >319              {loading ? "Creating..." : "Create Post"}320            </button>321            <button322              type="button"323              onClick={onCancel}324              disabled={loading}325              style={{326                flex: 1,327                padding: "12px 16px",328                background: "#1a1a1a",329                color: "#888",330                border: "1px solid #222",331                borderRadius: "8px",332                cursor: loading ? "not-allowed" : "pointer",333                fontWeight: 600,334                fontSize: "14px",335              }}336            >337              Cancel338            </button>339          </div>340        </form>341      </div>342    </div>343  );344}345 346function CategorySelector({ categories, onApply, onSkip }) {347  const [selected, setSelected] = useState([]);348  const handleSelect = (cat) => {349    setSelected((prev) =>350      prev.includes(cat)351        ? prev.filter((c) => c !== cat)352        : prev.length < 3353        ? [...prev, cat]354        : prev355    );356  };357  return (358    <div style={{359      position: 'fixed',360      top: 0,361      left: 0,362      right: 0,363      bottom: 0,364      background: 'rgba(0,0,0,0.85)',365      backdropFilter: 'blur(8px)',366      display: 'flex',367      alignItems: 'center',368      justifyContent: 'center',369      zIndex: 1000,370      padding: '20px'371    }}>372      <div style={{373        background: '#0a0a0a',374        border: '1px solid #1a1a1a',375        borderRadius: 12,376        padding: '40px 32px',377        maxWidth: 500,378        width: '100%',379        boxShadow: '0 20px 50px rgba(0, 0, 0, 0.6)',380        textAlign: 'center',381        animation: 'fadeInUp 0.6s ease-out'382      }}>383        <style>{`384          @keyframes fadeInUp {385            from { opacity: 0; transform: translateY(15px); }386            to { opacity: 1; transform: translateY(0); }387          }388        `}</style>389        <h2 style={{ 390          fontSize: '28px',391          fontWeight: 400,392          marginBottom: '8px',393          background: 'linear-gradient(to bottom,#10775f,#139c77,#33b89b)',394          WebkitBackgroundClip: 'text',395          WebkitTextFillColor: 'transparent',396          letterSpacing: '-0.5px'397        }}>Select Interests</h2>398        <p style={{ 399          color: '#666', 400          fontSize: '13px', 401          marginBottom: 32,402          lineHeight: '1.5'403        }}>The algorithm needs a starting point. Choose 3 categories to begin your personalized feed.</p>404        405        <div style={{ 406          display: 'flex', 407          flexWrap: 'wrap', 408          gap: 8, 409          justifyContent: 'center', 410          marginBottom: 36 411        }}>412          {categories.map((cat) => (413            <button414              key={cat}415              onClick={() => handleSelect(cat)}416              style={{417                background: selected.includes(cat) ? '#33b89b' : '#111',418                color: selected.includes(cat) ? '#000' : '#888',419                border: '1px solid',420                borderColor: selected.includes(cat) ? '#33b89b' : '#222',421                borderRadius: 6,422                padding: '7px 14px',423                fontWeight: 600,424                cursor: 'pointer',425                fontSize: '12px',426                transition: 'all 0.2s ease',427                textTransform: 'uppercase',428                letterSpacing: '0.5px'429              }}430            >431              {cat}432            </button>433          ))}434        </div>435        436        <div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>437          <button438            onClick={() => onApply(selected)}439            disabled={selected.length !== 3}440            style={{441              background: selected.length === 3 ? '#fff' : '#1a1a1a',442              color: selected.length === 3 ? '#000' : '#444',443              border: 'none',444              borderRadius: 8,445              padding: '12px 32px',446              fontWeight: 700,447              fontSize: '14px',448              cursor: selected.length === 3 ? 'pointer' : 'not-allowed',449              transition: 'all 0.3s ease',450              flex: 1,451              textTransform: 'uppercase'452            }}453          >Initialize Feed</button>454          <button455            onClick={onSkip}456            style={{457              background: 'transparent',458              color: '#444',459              border: '1px solid #222',460              borderRadius: 8,461              padding: '12px 24px',462              fontWeight: 600,463              fontSize: '14px',464              cursor: 'pointer',465              transition: 'all 0.3s ease',466              textTransform: 'uppercase'467            }}468            onMouseOver={(e) => e.target.style.color = '#fff'}469            onMouseOut={(e) => e.target.style.color = '#444'}470          >Skip</button>471        </div>472      </div>473    </div>474  );475}476 477// List of all categories478const ALL_CATEGORIES = [479  'nature', 'tech', 'healthcare', 'food', 'art', 'education', 'travel', 'music', 'sports', 'ai', 'web3', 'socialmedia', 'finance', 'movies', 'stocks', 'vehicles', 'cafes'480];481 482const API_BASE = window.location.hostname === "localhost" ? "http://localhost:7860" : "";483const USER_ID = "default_user";484 485const SOURCE_BADGE = {486  query: { bg: "#1a4d2e", color: "#4ade80", text: "Query Match" },487  interest: { bg: "#1e3a8a", color: "#60a5fa", text: "For You" },488  random: { bg: "#4c1d95", color: "#a78bfa", text: "Discover" },489};490const RANK_COLOR = {491  primary: "#f59e0b",      // PRIMARY tier (>= 15 likes)492  secondary: "#60a5fa",    // SECONDARY tier (>= 10 likes)493  tertiary: "#a78bfa",494  quaternary: "#34d399",495  quinary: "#f472b6",496  senary: "#38bdf8",497  septenary: "#fb923c",498  octonary: "#4ade80",499  PRIMARY: "#f59e0b",      // Tier-based constants500  SECONDARY: "#60a5fa",501  TERTIARY: "#a78bfa",502};503const CATEGORY_COLOR = {504  tech: "#3b82f6",505  ai: "#8b5cf6",506  healthcare: "#10b981",507  web3: "#f59e0b",508  socialmedia: "#ec4899",509  food: "#f97316",510  sports: "#06b6d4",511  finance: "#84cc16",512  movies: "#ef4444",513  music: "#a855f7",514  education: "#14b8a6",515  travel: "#6366f1",516  art: "#f43f5e",517  nature: "#22c55e",518  unknown: "#6b7280",519  liked: "#f59e0b",520  stocks: "#84cc16",521  crypto: "#f59e0b",522};523const getCatColor = (c) => CATEGORY_COLOR[c?.toLowerCase()] || "#6b7280";524const getBadge = (s) => SOURCE_BADGE[s] || SOURCE_BADGE.query;525 526const BENTO_PATTERN = [527  { col: 1, row: 2 },528  { col: 2, row: 1 },529  { col: 1, row: 1 },530  { col: 1, row: 1 },531  { col: 1, row: 1 },532  { col: 2, row: 2 },533  { col: 1, row: 1 },534  { col: 1, row: 2 },535  { col: 2, row: 1 },536  { col: 1, row: 1 },537  { col: 1, row: 1 },538  { col: 2, row: 1 },539];540 541function BentoCard({ post, span, visible, animDelay }) {542  const cc = getCatColor(post.category),543    isBig = span.col === 2 && span.row === 2,544    isWide = span.col === 2 && span.row === 1;545  return (546    <div547      style={{548        gridColumn: `span ${span.col}`,549        gridRow: `span ${span.row}`,550        background: post.media_url551          ? `#0e0e0e url("${post.media_url}") center/cover no-repeat`552          : "#0e0e0e",553        border: "1px solid #1e1e1e",554        borderRadius: "10px",555        overflow: "hidden",556        position: "relative",557        opacity: visible ? 1 : 0,558        transform: visible ? "scale(1)" : "scale(0.95)",559        transition: `opacity 0.45s ease ${animDelay}ms, transform 0.45s ease ${animDelay}ms`,560        display: "flex",561        flexDirection: "column",562        justifyContent: "flex-end",563      }}564    >565      <div566        style={{567          position: "absolute",568          inset: 0,569          zIndex: 1,570          background: post.media_url571            ? "linear-gradient(to bottom,transparent 20%,rgba(0,0,0,.55) 60%,rgba(0,0,0,.92) 100%)"572            : "linear-gradient(to bottom,#111 0%,#0a0a0a 100%)",573        }}574      />575      <div576        style={{577          padding: isBig ? "11px 12px" : isWide ? "8px 10px" : "7px 9px",578          position: "relative",579          zIndex: 2,580        }}581      >582        {post.category && post.category !== "unknown" && (583          <div584            style={{585              display: "inline-block",586              background: cc + "28",587              color: cc,588              fontSize: "8px",589              fontWeight: 800,590              letterSpacing: "0.6px",591              padding: "2px 6px",592              borderRadius: "4px",593              marginBottom: "4px",594              textTransform: "uppercase",595            }}596          >597            {post.category}598          </div>599        )}600        <div601          style={{602            fontWeight: 700,603            fontSize: isBig ? "12px" : "11px",604            color: "#f0f0f0",605            lineHeight: 1.3,606            marginBottom: "3px",607            overflow: "hidden",608            display: "-webkit-box",609            WebkitLineClamp: isBig ? 2 : 1,610            WebkitBoxOrient: "vertical",611          }}612        >613          {post.name}614        </div>615        <div616          style={{617            fontSize: "9px",618            color: post.media_url ? "rgba(255,255,255,.55)" : "#555",619            lineHeight: 1.35,620            overflow: "hidden",621            display: "-webkit-box",622            WebkitLineClamp: isBig ? 3 : 2,623            WebkitBoxOrient: "vertical",624          }}625        >626          {post.caption}627        </div>628      </div>629    </div>630  );631}632 633function BentoGrid({ posts, visible }) {634  const [cv, setCv] = useState(false);635  useEffect(() => {636    if (posts.length > 0) {637      const t = setTimeout(() => setCv(true), 80);638      return () => clearTimeout(t);639    }640  }, [posts]);641  if (!posts.length)642    return (643      <div644        style={{645          display: "grid",646          gridTemplateColumns: "repeat(4,1fr)",647          gridAutoRows: "60px",648          gap: "5px",649          marginBottom: "28px",650        }}651      >652        {BENTO_PATTERN.map((s, i) => (653          <div654            key={i}655            style={{656              gridColumn: `span ${s.col}`,657              gridRow: `span ${s.row}`,658              background: "#0e0e0e",659              borderRadius: "10px",660              animation: `pulse 1.8s ease-in-out ${i * 60}ms infinite`,661            }}662          />663        ))}664        <style>{`@keyframes pulse{0%,100%{opacity:.25}50%{opacity:.5}}`}</style>665      </div>666    );667  return (668    <div669      style={{670        display: "grid",671        gridTemplateColumns: "repeat(4,1fr)",672        gridAutoRows: "60px",673        gap: "5px",674        marginBottom: "28px",675        opacity: visible ? 1 : 0,676        transform: visible ? "none" : "translateY(8px)",677        transition: "opacity .5s ease,transform .5s ease",678      }}679    >680      {posts.slice(0, 12).map((p, i) => (681        <BentoCard682          key={p.post_id}683          post={p}684          span={BENTO_PATTERN[i] || { col: 1, row: 1 }}685          visible={cv}686          animDelay={i * 35}687        />688      ))}689    </div>690  );691}692 693function InterestPills({ rankedInterests }) {694  if (!rankedInterests?.length) return null;695  return (696    <div697      style={{698        display: "flex",699        gap: "6px",700        flexWrap: "wrap",701        marginBottom: "16px",702        alignItems: "center",703      }}704    >705      <span706        style={{707          fontSize: "9px",708          color: "#444",709          letterSpacing: "1px",710          textTransform: "uppercase",711          marginRight: "2px",712        }}713      >714        Your interests715      </span>716      {rankedInterests.map((r) => {717        const rc = RANK_COLOR[r.rank] || "#6b7280",718          cc = getCatColor(r.category);719        720        // Color code by tier721        const tierColor = r.tier === "PRIMARY" 722          ? "#f59e0b" // Amber for PRIMARY 723          : r.tier === "SECONDARY" 724          ? "#60a5fa" // Blue for SECONDARY725          : rc; // Default color for others726 727        return (728          <div729            key={`${r.category}-${r.tier || r.rank}`}730            style={{731              display: "flex",732              alignItems: "center",733              gap: "4px",734              background: tierColor + "12",735              border: `1px solid ${tierColor}33`,736              borderRadius: "20px",737              padding: "3px 9px",738            }}739          >740            <span741              style={{742                fontSize: "8px",743                color: tierColor,744                fontWeight: 800,745                textTransform: "uppercase",746                letterSpacing: "0.4px",747              }}748            >749              {r.tier || r.rank}750            </span>751            <span style={{ fontSize: "9px", color: cc, fontWeight: 700 }}>752              {r.category}753            </span>754            <span style={{ fontSize: "8px", color: "#555" }}>{r.count}♥</span>755          </div>756        );757      })}758    </div>759  );760}761 762// rag component 763function RAGResponse({ data, loading }) {764  if (!data && !loading) return null;765 766  if (loading) {767    return (768      <div style={{ textAlign: "center", padding: "40px" }}>769        <div770          style={{771            display: "inline-block",772            width: "24px",773            height: "24px",774            border: "2px solid #222",775            borderTopColor: "#fff",776            borderRadius: "50%",777            animation: "spin .8s linear infinite",778          }}779        />780        <style>{`@keyframes spin{to{transform:rotate(360deg)}}`}</style>781      </div>782    );783  }784 785  if (!data) return null;786 787  return (788    <div789      style={{790        background: "#0a0a0a",791        border: "1px solid #1a1a1a",792        borderRadius: "12px",793        padding: "24px",794        marginBottom: "28px",795      }}796    >797{/* img loader for rag */}798      {data.featured_image?.url && (799        <div style={{ marginBottom: "24px" }}>800          <img801            src={data.featured_image.url}802            alt="Featured"803            style={{804              width: "100%",805              height: "auto",806              maxHeight: "300px",807              objectFit: "cover",808              borderRadius: "10px",809              marginBottom: "8px",810            }}811            onError={(e) => {812              e.target.style.display = "none";813            }}814          />815          <div style={{ fontSize: "11px", color: "#666" }}>816            From: <strong>{data.featured_image.source}</strong>817          </div>818        </div>819      )}820 821{/* summary  */}822      {data.summary && (823        <div style={{ marginBottom: "24px" }}>824          <h4 style={{ marginBottom: "10px", fontSize: "14px", color: "#aaa" }}>825            Summary826          </h4>827          <p828            style={{829              margin: 0,830              color: "#ccc",831              lineHeight: "1.6",832              fontSize: "13px",833            }}834          >835            {data.summary}836          </p>837        </div>838      )}839      {data.key_insights && data.key_insights.length > 0 && (840        <div style={{ marginBottom: "24px" }}>841          <h4 style={{ marginBottom: "14px", fontSize: "14px", color: "#aaa" }}>842            Key Insights ({data.key_insights.length})843          </h4>844          <div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>845            {data.key_insights.map((insight, idx) => (846              <div847                key={idx}848                style={{849                  background: "#111",850                  padding: "14px",851                  borderRadius: "8px",852                  border: "1px solid #1a1a1a",853                }}854              >855                <div style={{ display: "flex", gap: "10px" }}>856                  <div857                    style={{858                      background: "#60a5fa",859                      color: "#000",860                      width: "24px",861                      height: "24px",862                      borderRadius: "50%",863                      display: "flex",864                      alignItems: "center",865                      justifyContent: "center",866                      fontWeight: 700,867                      fontSize: "12px",868                      flexShrink: 0,869                    }}870                  >871                    {insight.rank}872                  </div>873                  <div style={{ flex: 1 }}>874                    <div875                      style={{876                        fontWeight: 600,877                        fontSize: "13px",878                        marginBottom: "6px",879                        color: "#fff",880                      }}881                    >882                      {insight.point}883                    </div>884                    <div885                      style={{886                        fontSize: "12px",887                        color: "#aaa",888                        lineHeight: "1.5",889                        marginBottom: "6px",890                      }}891                    >892                      {insight.explanation}893                    </div>894                    <div895                      style={{896                        fontSize: "11px",897                        color: "#666",898                      }}899                    >900                       Source: <strong>{insight.post_reference}</strong>901                    </div>902                  </div>903                </div>904              </div>905            ))}906          </div>907        </div>908      )}909 910      {data.ai_perspective && (911        <div style={{ marginBottom: "24px" }}>912          <h4 style={{ marginBottom: "12px", fontSize: "14px", color: "#aaa" }}>913            AI's Perspective914          </h4>915          <div916            style={{917              background: "#111",918              padding: "16px",919              borderRadius: "8px",920              border: "1px solid #1a1a1a",921              borderLeft: "3px solid #8b5cf6",922            }}923          >924            <p925              style={{926                margin: 0,927                color: "#ccc",928                lineHeight: "1.6",929                fontSize: "13px",930                whiteSpace: "pre-wrap",931              }}932            >933              {data.ai_perspective}934            </p>935          </div>936        </div>937      )}938 939      {/* Source Posts Grid */}940      {data.source_posts && data.source_posts.length > 0 && (941        <div>942          <h4 style={{ marginBottom: "14px", fontSize: "14px", color: "#aaa" }}>943            Source Posts ({data.source_posts.length})944          </h4>945          <div946            style={{947              display: "grid",948              gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",949              gap: "12px",950            }}951          >952            {data.source_posts.map((post, idx) => (953              <div954                key={idx}955                style={{956                  background: "#111",957                  border: "1px solid #1a1a1a",958                  borderRadius: "8px",959                  overflow: "hidden",960                }}961              >962                {post.media_url && (963                  <img964                    src={post.media_url}965                    alt={post.name}966                    style={{967                      width: "100%",968                      height: "120px",969                      objectFit: "cover",970                      background: "#0a0a0a",971                    }}972                    onError={(e) => {973                      e.target.style.display = "none";974                    }}975                  />976                )}977                <div style={{ padding: "12px" }}>978                  <div979                    style={{980                      fontWeight: 600,981                      fontSize: "12px",982                      marginBottom: "6px",983                      color: "#fff",984                    }}985                  >986                    {post.name}987                  </div>988                  <div989                    style={{990                      fontSize: "11px",991                      color: "#aaa",992                      lineHeight: "1.4",993                      marginBottom: "8px",994                      overflow: "hidden",995                      display: "-webkit-box",996                      WebkitLineClamp: 2,997                      WebkitBoxOrient: "vertical",998                    }}999                  >1000                    {post.caption}1001                  </div>1002                  <div style={{ fontSize: "10px", color: "#666" }}>1003                    Score: <strong>{post.similarity_score}</strong>1004                  </div>1005                </div>1006              </div>1007            ))}1008          </div>1009        </div>1010      )}1011      {data.metadata && (1012        <div1013          style={{1014            marginTop: "20px",1015            padding: "12px",1016            background: "#000",1017            border: "1px solid #1a1a1a",1018            borderRadius: "8px",1019            fontSize: "10px",1020            color: "#666",1021          }}1022        >1023          <div>Total posts analyzed: {data.metadata.posts_analyzed}</div>1024          <div>Top similarity: {data.metadata.top_similarity}</div>1025        </div>1026      )}1027    </div>1028  );1029}1030 1031export default function App() {1032    const [showCatSelector, setShowCatSelector] = useState(() => {1033      return sessionStorage.getItem('first_search_done') !== 'true';1034    });1035    1036    const handleApplyInitialCats = async (selectedCats) => {1037      sessionStorage.setItem('first_search_done', 'true');1038      setShowCatSelector(false);1039      try {1040        const res = await fetch(API_BASE + '/posts');1041        const data = await res.json();1042        const allPosts = data.posts || [];1043        const normalizedCats = selectedCats.map((c) => c.toLowerCase().trim());1044        const selectedCatSet = new Set(normalizedCats);1045        const postsByCategory = new Map();1046 1047        allPosts.forEach((post) => {1048          const pCat = (post.category || "").toLowerCase().trim();1049          if (!postsByCategory.has(pCat)) {1050            postsByCategory.set(pCat, []);1051          }1052          postsByCategory.get(pCat).push(post);1053        });1054 1055        let catPosts = [];1056        normalizedCats.forEach((normCat) => {1057          const filtered = (postsByCategory.get(normCat) || []).map((p) => ({1058            ...p,1059            source: "query",1060          }));1061          const shuffled = [...filtered].sort(() => Math.random() - 0.5);1062          catPosts = catPosts.concat(shuffled.slice(0, 3)); // 3 per cat to be safe1063        });1064 1065        const otherPosts = allPosts1066          .filter((p) => {1067            const pCat = (p.category || "").toLowerCase().trim();1068            return !selectedCatSet.has(pCat);1069          })1070          .map((p) => ({ ...p, source: "random" }))1071          .sort(() => Math.random() - 0.5);1072 1073        const randomPad = otherPosts.slice(0, 4);1074        let postsToShow = [...catPosts, ...randomPad];1075        1076        // Final fallback: if zero posts, just grab random ones1077        if (postsToShow.length === 0) {1078            postsToShow = allPosts.slice(0, 10).map(p => ({...p, source: 'random'}));1079        }1080 1081        postsToShow.sort(() => Math.random() - 0.5);1082        setResults(postsToShow);1083        setBreakdown({1084          budget: { query: catPosts.length, interest: 0, random: randomPad.length },1085          query_based: catPosts.length,1086          interest_based: 0,1087          random: randomPad.length,1088          total: postsToShow.length,1089          ranked_interests: [],1090        });1091        setShowBento(false);1092      } catch (err) {1093        console.error('[init cats]', err);1094        setError('Failed to load initial posts');1095      }1096    };1097 1098    const handleSkipInitialCats = () => {1099      sessionStorage.setItem('first_search_done', 'true');1100      setShowCatSelector(false);1101    };1102    useEffect(() => {1103      if (sessionStorage.getItem('first_search_done') !== 'true') {1104        setShowCatSelector(true);1105      }1106    }, []);1107  const [query, setQuery] = useState("");1108  const [results, setResults] = useState([]);1109  const [loading, setLoading] = useState(false);1110  const [error, setError] = useState("");1111  const [selectedLanguage, setSelectedLanguage] = useState("english"); // NEW: Language selection1112  const [breakdown, setBreakdown] = useState(null);1113  const [bentoPosts, setBentoPosts] = useState([]);1114  const [bentoLoaded, setBentoLoaded] = useState(false);1115  const [showBento, setShowBento] = useState(true);1116  const [likeEvents, setLikeEvents] = useState([]); // Now stores all engagement events1117  const leRef = useRef([]);1118  const [likedIds, setLikedIds] = useState(new Set());1119  const liRef = useRef(new Set());1120  const [watchTimers, setWatchTimers] = useState({}); // Track watch timers for videos1121  leRef.current = likeEvents;1122  liRef.current = likedIds;1123 1124  // Comment modal state1125  const [commentModal, setCommentModal] = useState({ open: false, postId: null });1126  const [commentText, setCommentText] = useState("");1127  1128  // Saved posts state1129  const [savedPosts, setSavedPosts] = useState(new Set());1130  const savedRef = useRef(new Set());1131  1132  // Shared posts state1133  const [sharedPosts, setSharedPosts] = useState(new Set());1134  const sharedRef = useRef(new Set());1135  const [sharingPost, setSharingPost] = useState(null);1136  1137  // Comments state - maps post_id to array of comments1138  const [postComments, setPostComments] = useState({});1139  1140  // Save state being processed1141  const [savingPost, setSavingPost] = useState(null);1142  1143  // Create post state1144  const [showCreateModal, setShowCreateModal] = useState(false);1145  const [creatingPost, setCreatingPost] = useState(false);1146 1147  const searchInputRef = useRef(null);1148 1149//   rag handlerss 1150  const [question, setQuestion] = useState("");1151  const [ragData, setRagData] = useState(null);1152  const [ragLoading, setRagLoading] = useState(false);1153 1154  function handleRagKeyPress(e) {1155    if (e.key === "Enter") {1156      handleRagAsk();1157    }1158  }1159 1160  async function handleRagAsk() {1161    if (!question.trim()) {1162      setError("Please enter a question");1163      return;1164    }1165 1166    setRagLoading(true);1167    setRagData(null);1168    setError("");1169 1170    try {1171      const res = await fetch(`${API_BASE}/rag`, {1172        method: "POST",1173        headers: { "Content-Type": "application/json" },1174        body: JSON.stringify({1175          question: question.trim(),1176          limit: 5,1177          min_score: 0.1,1178          user_id: USER_ID,1179        }),1180      });1181 1182      if (!res.ok) {1183        throw new Error(`HTTP ${res.status}: ${await res.text()}`);1184      }1185 1186      const data = await res.json();1187      console.log("[RAG Response]", data);1188 1189      if (data.key_insights) {1190        setRagData(data);1191      } else if (data.answer) {1192        setRagData({1193          summary: data.answer,1194          key_insights: [],1195          ai_perspective:1196            "No AI perspective available with this response format.",1197          source_posts: data.sources || [],1198          featured_image: null,1199        });1200      }

Showing the first 1,200 of 2459 lines. Download the file for the rest.