PageSourceSearch

https://helkos.fi/assets/AimeIntegrations-Dty1mWfH.js

js helkos.fi collected 2026-10-09 22:05:08 UTC 12,601 bytes, 1 lines download raw bytes

1import{j as e}from"./vendor-motion-BYSGJZRG.js";import{r as i}from"./vendor-react-BAWrOFBJ.js";import{aa as te,aj as ne,s as d,au as ie,av as re,az as le,C as x,b as f,d as g,e as _,l as P,f as j,B as b,g as m}from"./index-BD6qdh5D.js";import{I as H}from"./input-a2GrFMNb.js";import{L as v}from"./label-BLB-lZEB.js";import{S as oe}from"./switch-CHPbvCOk.js";import{C as ce}from"./checkbox-BYWoujIn.js";import{S as de}from"./separator-CneCMjDS.js";import"./vendor-pdf-Cq2FS4Cb.js";import"./vendor-radix-Scc5zhsa.js";import"./vendor-charts-DWQnO0ie.js";const me=["aime.organisations.read","aime.curricula.read","aime.lessons.read","aime.lessons.create","aime.lessons.edit_draft","aime.lessons.submit_review","aime.lessons.publish"],ue={"aime.organisations.read":"Read organisations","aime.curricula.read":"Read curricula","aime.lessons.read":"Read lessons and drafts","aime.lessons.create":"Create draft lessons","aime.lessons.edit_draft":"Edit draft content, questions and teacher plan","aime.lessons.submit_review":"Send a draft for review","aime.lessons.publish":"Approve and publish a version"},W=["aime.lessons.create","aime.lessons.edit_draft","aime.lessons.submit_review","aime.lessons.publish"],he={"aime.organisations.read":null,"aime.curricula.read":null,"aime.lessons.read":null,"aime.lessons.create":"curriculum.create","aime.lessons.edit_draft":"curriculum.create","aime.lessons.submit_review":"curriculum.create","aime.lessons.publish":"curriculum.publish"},pe=["aime.organisations.read","aime.curricula.read","aime.lessons.read"];function xe(n){return n?!n.scopes.some(r=>W.includes(r)):!0}function fe(n){return me.filter(r=>{const o=he[r];return!o||n.includes(o)})}const $="id,user_id,enabled,scopes,label,expires_at,last_used_at,last_client_id,last_client_name,created_at",ge=["integration_denied","lesson_created","lesson_draft_updated","lesson_blocks_written","lesson_questions_written","lesson_review","lesson_approved","lesson_published","lesson_draft"],M="https://hgrqtshnwswtcoemunsz.supabase.co/functions/v1/mcp",_e=[{name:"ChatGPT",note:"Custom MCP connectors require a paid plan, and some accounts must switch on developer-mode connectors first.",steps:["Settings → Connectors → add a custom connector (MCP).","Paste the endpoint below and choose OAuth. No client id or secret is needed — the connector registers itself.","Sign in on this platform and approve the request, then enable the connector in your chat."]},{name:"Gemini",note:"Only Gemini surfaces that support remote MCP servers with OAuth can connect. Where Gemini offers no remote MCP client, use a local MCP bridge or another client instead — the endpoint itself is unchanged.",steps:["Open the extension/tool settings of the Gemini surface you use and add a remote MCP server.","Paste the endpoint below and complete the OAuth sign-in on this platform.","Ask the assistant to run aime_whoami to confirm the connection."]},{name:"Generic MCP client",note:"The server speaks MCP Streamable HTTP with OAuth 2.1 discovery and dynamic client registration, so no provider-specific setup exists.",steps:["Add a remote MCP (Streamable HTTP) server using the endpoint below.","Let the client discover authorisation automatically; it registers itself and opens this platform's consent page.","Approve the request, then list the tools — every AIME tool is prefixed aime_."]}];function T(n){if(!n)return"—";const r=new Date(n);return Number.isNaN(r.getTime())?"—":r.toLocaleString("en-GB",{dateStyle:"short",timeStyle:"short"})}function Y(n){return n&&(n.last_client_name||n.last_client_id)||"External assistant"}function Pe(){const{user:n}=te(),{capabilities:r,loading:o,can:V}=ne(),N=V("platform.administer"),[J,w]=i.useState(!0),[I,C]=i.useState(null),[t,K]=i.useState(null),[O,Q]=i.useState([]),[X,Z]=i.useState([]),[L,ee]=i.useState([]),[G,R]=i.useState(pe),[D,q]=i.useState(""),[S,B]=i.useState(""),[u,y]=i.useState(!1),A=i.useMemo(()=>fe(r),[r]),h=i.useCallback(async()=>{if(!n){w(!1),C("You are signed out. Sign in again to manage assistant access.");return}w(!0),C(null);try{const[s,a,l,k]=await Promise.all([d.from("aime_integration_grants").select($).eq("user_id",n.id).maybeSingle(),N?d.from("aime_integration_grants").select($).order("created_at",{ascending:!1}):Promise.resolve({data:[],error:null}),d.from("education_profiles").select("user_id,full_name,email"),d.from("aime_audit_events").select("id,actor_user_id,action,target_id,metadata,created_at").in("action",ge).order("created_at",{ascending:!1}).limit(100)]);if(s.error)throw new Error(s.error.message);const c=s.data??null;K(c),Q((a.data??[]).filter(p=>
1p.user_id!==n.id)),Z(l.data??[]),ee((k.data??[]).filter(p=>{var z;return((z=p.metadata)==null?void 0:z.source)==="mcp"})),c&&(R(c.scopes.filter(p=>A.includes(p))),q(c.label??""),B(c.expires_at?c.expires_at.slice(0,10):""))}catch(s){C(s.message)}finally{w(!1)}},[n,N,A]);i.useEffect(()=>{o||h()},[o,h]);const E=async s=>{if(n){y(!0);try{const a={user_id:n.id,enabled:s,scopes:G,label:D.trim()||null,expires_at:S?new Date(`${S}T23:59:59`).toISOString():null},{error:l}=await d.from("aime_integration_grants").upsert(a,{onConflict:"user_id"});if(l)throw new Error(l.message);m.success(s?"External assistant access saved.":"External assistant access revoked."),await h()}catch(a){m.error(a.message)}finally{y(!1)}}},se=async s=>{y(!0);try{const{error:a}=await d.from("aime_integration_grants").update({enabled:!1}).eq("id",s.id);if(a)throw new Error(a.message);m.success("Access revoked."),await h()}catch(a){m.error(a.message)}finally{y(!1)}},U=s=>{const a=X.find(l=>l.user_id===s);return(a==null?void 0:a.full_name)||(a==null?void 0:a.email)||s.slice(0,8)},ae=(s,a)=>{R(l=>a?Array.from(new Set([...l,s])):l.filter(k=>k!==s))},F=async()=>{try{await navigator.clipboard.writeText(M),m.success("Endpoint copied.")}catch{m.error("Copying failed — select the address and copy it manually.")}};return e.jsx(ie,{title:"External AI assistants",description:"Let an external AI assistant work inside AIME on your behalf, with exactly the permissions you choose. Any assistant that speaks the open MCP standard can connect; nothing is shared automatically and you can switch it off at any time.",backTo:"/tutor/aime/dashboard",children:J||o?e.jsx(re,{label:"Loading assistant access…"}):I?e.jsx(le,{message:I,onRetry:h}):e.jsxs("div",{className:"space-y-6",children:[e.jsxs(x,{children:[e.jsx(f,{children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx(g,{children:"Your assistant connection"}),e.jsx(_,{children:"The assistant always acts as you. It can never see more than you can, and it can never approve or publish work unless your own role allows it."})]}),e.jsx(P,{variant:t!=null&&t.enabled?"default":"secondary",children:t!=null&&t.enabled?"Enabled":"Not enabled"})]})}),e.jsxs(j,{className:"space-y-6",children:[e.jsxs("div",{className:"flex items-center justify-between rounded-lg border p-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx(v,{htmlFor:"assistant-enabled",children:"Allow assistant access"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:t!=null&&t.enabled?xe(t)?"The assistant can read only.":"The assistant can also make changes within the scopes below.":"Switch on to authorise the scopes you tick below."})]}),e.jsx(oe,{id:"assistant-enabled",checked:!!(t!=null&&t.enabled),disabled:u,onCheckedChange:s=>E(s)})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsx(v,{children:"What the assistant may do"}),e.jsx("div",{className:"grid gap-2 sm:grid-cols-2",children:A.map(s=>e.jsxs("label",{className:"flex items-start gap-3 rounded-lg border p-3 text-sm",children:[e.jsx(ce,{checked:G.includes(s),disabled:u,onCheckedChange:a=>ae(s,a===!0)}),e.jsxs("span",{children:[e.jsx("span",{className:"font-medium",children:ue[s]}),W.includes(s)&&e.jsx(P,{variant:"outline",className:"ml-2 align-middle",children:"Makes changes"}),e.jsx("span",{className:"block text-xs text-muted-foreground",children:s})]})]},s))}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Only actions your AIME role already permits appear here. Approving and publishing stay with Supervisors."})]}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(v,{htmlFor:"assistant-label",children:"Label (optional)"}),e.jsx(H,{id:"assistant-label",value:D,placeholder:"My assistant on this laptop",onChange:s=>q(s.target.value)})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(v,{htmlFor:"assistant-expiry",children:"Expires (optional)"}),e.jsx(H,{id:"assistant-expiry",type:"date",value:S,onChange:s=>B(s.target.value)})]})]}),e.jsx(de,{}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx(b,{disabled:u,onClick:()=>E(!0),children:"Save access"}),(t==null?void 0:t.enabled)&&e.jsx(b,{variant:"destructive",disabled:u,onClick:()=>E(!1),children:"Revoke access"}),e.jsxs("span",{className:"text-sm text-muted-foreground",children:["Last used: ",T((t==null?void 0:t.last_used_at)??null),t!=null&&t.last_used_at?` · ${Y(t)}`:""]})]})]})]}),e.jsxs(x,{children:[e.jsxs(f,{children:[e.jsx(g,{children:"Connection address"}),e.jsx(_,{children:"One standards-based MCP endpoint, the same for every assistant. Sign-in happens on this platform — no password, key or session token is ever copied anywhere."})]}),e.jsxs(j,{className:"space-y-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("code",{className:"flex-1 break-all rounded-md border bg-muted px-3 py-2 text-xs",children:M}),e.jsx(b,{variant:"outline",onClick:F,children:"Copy endpoint"})]}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Streamable HTTP MCP with OAuth 2.1 discovery and dynamic client registration. After connecting, ask the assistant to run ",e.jsx("code",{children:"aime_whoami"})," — it reports exactly what it may do."]})]})]}),e.jsx("div",{className:"grid gap-4 lg:grid-cols-3",children:_e.map(s=>e.jsxs(x,{children:[e.jsxs(f,{children:[e.jsx(g,{className:"text-base",children:s.name}),e.jsx(_,{children:s.note})]}),e.jsxs(j,{className:"space-y-3",children:[e.jsx("ol",{className:"list-decimal space-y-1 pl-4 text-sm text-muted-foreground",children:s.steps.map(a=>e.jsx("li",{children:a},a))}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("code",{className:"flex-1 break-all rounded-md border bg-muted px-2 py-1 text-[11px]",children:M}),e.jsx(b,{size:"sm",variant:"ghost",onClick:F,children:"Copy"})]})]})]},s.name))}),N&&e.jsxs(x,{children:[e.jsxs(f,{children:[e.jsx(g,{children:"Other accounts with assistant access"}),e.jsx(_,{children:"Platform administrators can revoke any connection."})]}),e.jsx(j,{children:O.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No other account has enabled assistant access."}):e.jsx("div",{className:"space-y-2",children:O.map(s=>e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 rounded-lg border p-3",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("p",{className:"text-sm font-medium",children:U(s.user_id
1)}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:[s.scopes.length," scope",s.scopes.length===1?"":"s"," · ",Y(s)," · last used ",T(s.last_used_at)]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(P,{variant:s.enabled?"default":"secondary",children:s.enabled?"Enabled":"Revoked"}),s.enabled&&e.jsx(b,{size:"sm",variant:"destructive",disabled:u,onClick:()=>se(s),children:"Revoke"})]})]},s.id))})})]}),e.jsxs(x,{children:[e.jsxs(f,{children:[e.jsx(g,{children:"Assistant activity"}),e.jsx(_,{children:"Every action an external assistant took in AIME, including refused attempts."})]}),e.jsx(j,{children:L.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No assistant activity recorded yet."}):e.jsx("div",{className:"space-y-2",children:L.map(s=>{var a;return e.jsxs("div",{className:"rounded-lg border p-3 text-sm",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[e.jsx("span",{className:"font-medium",children:s.action.replace(/_/g," ")}),e.jsx("span",{className:"text-xs text-muted-foreground",children:T(s.created_at)})]}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:[s.actor_user_id?U(s.actor_user_id):"Unknown user"," · ",((a=s.metadata)==null?void 0:a.client_name)||"External assistant",s.target_id?` · ${s.target_id.slice(0,8)}`:""]})]},s.id)})})})]})]})})}export{Pe as default};

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.