PageSourceSearch

https://altheo.ai/assets/InternalLinkPanel-CmsiVAb9.js

js altheo.ai collected 2026-10-05 11:48:48 UTC 7,424 bytes, 1 lines download raw bytes

1import{s as F,u as M,r as o,j as e,L as U,A as $,C as j,B as w,a5 as B,R as _,V as D,a as K,J as O,aa as P}from"./index-USxLAGeM.js";const A={broken_cluster_chain:{label:"Broken cluster chain",hint:"Your topical cluster says this article sits beneath another one, but the published page does not link up to it.",tone:"#f87171"},money_page_starved:{label:"Money page starved",hint:"The page your cluster points at receives almost no links from your own body copy.",tone:"#26de81"},unlinked_mention:{label:"Unlinked mention",hint:"This page already talks about another page’s subject without linking to it.",tone:"#38bdf8"},no_editorial_links:{label:"No editorial links",hint:"No page body links here. It may still be in your navigation — the crawler does not see menus.",tone:"#a78bfa"},anchor_confusion:{label:"Anchor confusion",hint:"The same link text points at several different pages.",tone:"#facc15"}};class E extends Error{constructor(s){super(s),this.name="InternalLinkError"}}async function k(n){const{data:s,error:a}=await F.functions.invoke("internal-link-suggester",{body:n});if(a){const r=a.context;if(r&&typeof r.json=="function"){const l=await r.json().catch(()=>null);if(l!=null&&l.error)throw new E(l.error)}throw new Error(a.message||"Request failed")}if(s!=null&&s.error)throw new E(s.error);return s}function q(){return k({action:"state"})}function H(){return k({action:"analyze"})}function J(n,s){return k({action:"set_status",suggestion_id:n,status:s})}function V(n){const s={open:0,applied:1,dismissed:2};return[...n].sort((a,r)=>s[a.status]-s[r.status]||a.priority-r.priority||a.target_url.localeCompare(r.target_url))}function L(n){try{const s=new URL(n);return s.pathname==="/"?"/":s.pathname.replace(/\/$/,"")}catch{return n}}const c="#26de81";function X({kind:n}){const s=A[n]??A.unlinked_mention;return e.jsx("span",{className:"shrink-0 text-xs px-2 py-0.5 rounded-md border",style:{color:s.tone,borderColor:`${s.tone}40`,backgroundColor:`${s.tone}12`},title:s.hint,children:s.label})}function Y({text:n,anchor:s}){if(!s)return e.jsxs("p",{className:"text-sm text-gray-400 italic",children:["“",n,"”"]});const a=n.toLowerCase().indexOf(s.toLowerCase());return a===-1?e.jsxs("p",{className:"text-sm text-gray-400 italic",children:["“",n,"”"]}):e.jsxs("p",{className:"text-sm text-gray-400 italic",children:["“",n.slice(0,a),e.jsx("span",{className:"not-italic font-medium px-1 py-0.5 rounded",style:{color:c,backgroundColor:`${c}1a`},children:n.slice(a,a+s.length)}),n.slice(a+s.length),"”"]}
1)}function Q(){const{user:n}=M(),[s,a]=o.useState(!0),[r,l]=o.useState(!1),[N,h]=o.useState(null),[i,u]=o.useState([]),[g,R]=o.useState({}),[b,z]=o.useState(!1),v=o.useCallback(async()=>{if(n)try{const t=await q();u(t.suggestions)}catch(t){h(t instanceof Error?t.message:"Could not load your suggestions")}finally{a(!1)}},[n]);o.useEffect(()=>{v()},[v]);const I=async()=>{l(!0),h(null);try{const t=await H();u(t.suggestions),R({scanned:t.scanned,edges:t.edges})}catch(t){h(t instanceof Error?t.message:"The analysis did not finish")}finally{l(!1)}},p=async(t,x)=>{u(m=>m.map(d=>d.id===t.id?{...d,status:x}:d));try{await J(t.id,x)}catch(m){u(d=>d.map(y=>y.id===t.id?{...y,status:t.status}:y)),h(m instanceof Error?m.message:"Could not update that suggestion")}},C=o.useMemo(()=>V(i),[i]),T=b?C:C.filter(t=>t.status==="open"),f=i.filter(t=>t.status==="open").length,S=i.length-f;return s?e.jsx("div",{className:"flex items-center justify-center py-12",children:e.jsx(U,{className:"w-5 h-5 animate-spin text-gray-400"})}):e.jsxs("div",{className:"space-y-6",children:[N&&e.jsxs("div",{className:"flex items-start gap-2 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-sm text-red-300",children:[e.jsx($,{className:"w-4 h-4 mt-0.5 shrink-0"}),N]}),e.jsx(j,{accentColor:c,children:e.jsxs("div",{className:"flex items-start justify-between gap-4 flex-wrap",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("h3",{className:"font-heading text-lg font-bold mb-1",children:i.length?"Check the links again":"Find your missing internal links"}),e.jsx("p",{className:"text-sm text-gray-400",children:"Altheo builds the real link graph from the pages it has already crawled, then finds the places one page should link to another and writes the anchor text for you."}),g.scanned!==void 0&&e.jsxs("p",{className:"text-xs text-gray-500 mt-2",children:[g.scanned," pages, ",g.edges," links between them."]}),e.jsx("p",{className:"text-xs text-gray-600 mt-2",children:"Only links inside page bodies are counted — the crawler does not keep navigation menus, so a page in your menu can still show as having no editorial links."})]}),e.jsxs(w,{pillarColor:c,onClick:I,loading:r,className:"shrink-0",children:[i.length?e.jsx(B,{className:"w-4 h-4 mr-1.5"}):e.jsx(_,{className:"w-4 h-4 mr-1.5"}),r?"Reading your pages":i.length?"Check again":"Find missing links"]})]})}),i.length>0&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3 flex-wrap",children:[e.jsxs("div",{children:[e.jsxs("h3",{className:"font-heading text-xl font-bold",children:[f," link",f===1?"":"s"," worth adding"]}),e.jsx("p",{className:"text-sm text-gray-400",children:"Most valuable first. Mark them off as you add them."})]}),S>0&&e.jsxs("button",{onClick:()=>z(t=>!t),className:"text-xs text-gray-500 hover:text-gray-300 transition-colors",children:[b?"Hide":"Show"," ",S," done"]})]}),T.map(t=>{const x=t.status!=="open";return e.jsx(j,{className:x?"opacity-50":"",children:e.jsxs("div",{className:"flex items-start justify-between gap-4 flex-wrap",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2 flex-wrap",children:[e.jsx(X,{kind:t.kind}),t.status==="applied"&&e.jsx("span",{className:"text-xs",style:{color:c},children:"Added"})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm mb-2 flex-wrap",children:[t.source_url?e.jsxs(e.Fragment,{children:[e.jsx("code",{className:"text-xs px-1.5 py-0.5 rounded bg-white/5 text-gray-300",children:L(t.source_url)}),e.jsx(D,{className:"w-3.5 h-3.5 text-gray-600 shrink-0"})]}):null,e.jsx("code",{className:"text-xs px-1.5 py-0.5 rounded bg-white/5 text-gray-300",children:L(t.target_url)})]}),t.context&&e.jsx("div",{className:"mb-2",children:e.jsx(Y,{text:t.context,anchor:t.anchor})}),t.why&&e.jsx("p",{className:"text-sm text-gray-400",children:t.why})]}),e.jsx("div",{className:"flex items-center gap-2 shrink-0",children:t.status==="open"?e.jsxs(e.Fragment,{children:[e.jsxs(w,{variant:"secondary",size:"sm",onClick:()=>p(t,"applied"),children:[e.jsx(K,{className:"w-4 h-4 mr-1.5"}),"Added it"]}),e.jsx("button",{onClick:()=>p(t,"dismissed"),title:"Not worth it",className:"p-1.5 rounded-lg text-gray-500 hover:text-gray-300 hover:bg-white/5 transition-colors",children:e.jsx(O,{className:"w-4 h-4"})})]}):e.jsxs(w,{variant:"ghost",size:"sm",onClick:()=>p(t,"open"),children:[e.jsx(P,{className:"w-4 h-4 mr-1.5"}),"Undo"]})})]})},t.id)})]}),i.length===0&&!r&&e.jsxs(j,{className:"text-center py-10",children:[e.jsx(_,{className:"w-5 h-5 mx-auto mb-3",style:{color:c}}),e.jsx("p",{className:"text-sm text-gray-400 max-w-md mx-auto",children:"Nothing checked yet. Altheo works from pages it has already crawled — if the Site Scan in the Technical pillar has not run, do that first."})]})]})}export{Q as InternalLinkPanel};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.