/* eslint-disable no-unused-vars -- components used only inside JSX look unused to this config, which has no eslint-plugin-react */
import { useEffect, useState } from 'react'
import { RouteProvider } from 'react-router-dom'
import { getSdk, } from '../../../internal/host-compat/src/runtime.js'
import { registerLocales } from '../../../internal/host-compat/src/i18n.js'
import Icon from '../../../internal/host-compat/src/components/Icon'
import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage'
import { connectorsApi } from '../../../internal/connector-ui/src/api'
import { Field, Notice, RadioCards, TextInput } from '../../../internal/connector-ui/src/controls'
import en from './i18n/github.en.json'
import ProposalCard from './ProposalCard'
import { checkFailure, checkItem, extraBody, extraForm, extraValid, followName, githubSecret, parseRepos, PERMISSIONS, reposIssue, tokenProblem, WRITE_MODES, writeModeOf } from './repos'
import { finishTest, resultOf, startTest, subscribe } from './results'
import './github.css'
registerLocales({ github: { en } })
const TOKEN_URL = 'https://github.com/settings/personal-access-tokens/new'
function TokenLink({ t, className, label }) {
return (
{label || t('tokenLink')}
)
}
// The fine-grained token. It is typed only to replace it.
function GitHubToken({ form, setForm, t }) {
const problem = tokenProblem(form)
const hint = <>{t('tokenHelp')}{!form.id && <>{' '}>}>
return (
{(p) => (
setForm({ ...form, token: e.target.value })} />
)}
)
}
// The repositories the agent may use. The host refuses any other one before
// the add-on is called.
function GitHubFields({ form, setForm, t }) {
const issue = reposIssue(form.repos_text)
const count = parseRepos(form.repos_text).length
const change = (text) => setForm({ ...form, repos_text: text, name: followName(form.name, form.repos_text, text) })
return (
<>
{(p) => (
>
)
}
// The write mode: the host enforces it for every write tool of this add-on.
function WriteMode({ form, setForm, t }) {
const mode = writeModeOf(form.write_mode)
return (
{() => (
setForm({ ...form, write_mode: m })}
options={WRITE_MODES.map((m) => ({
value: m,
label: ({t(`writeMode.${m}`)}{t(`writeMode.line.${m}`)}),
}))} />
)}
{t(`writeMode.note.${mode}`)}
)
}
// What the page does not do, always shown; and how to make the token, open
// while there is no connection yet.
function Help({ t }) {
const [open, setOpen] = useState(null)
useEffect(() => {
let live = true
connectorsApi.list().then((r) => { if (live && open === null) setOpen(!(r.connectors || []).some((c) => c.kind === 'github')) }).catch(() => {})
return () => { live = false }
// eslint-disable-next-line react-hooks/exhaustive-deps -- runs once; the person's own toggle wins afterwards
}, [])
const level = (k) => t(`setup.table.levels.${k}`)
return (
{t('limits')}
setOpen(e.currentTarget.open)}>
{t('setup.title')}
- {t('setup.step1')}
- {t('setup.step2')}
- {t('setup.step3')}
- {t('setup.step4')}
{t('setup.table.caption')}
| {t('setup.table.permission')} | {t('setup.table.read')} | {t('setup.table.write')} |
{PERMISSIONS.map((p) => (
| {t(`setup.table.${p.id}`)} | {level(p.read)} | {level(p.write)} |
))}
)
}
function useResult(id) {
const [, tick] = useState(0)
useEffect(() => subscribe(() => tick((n) => n + 1)), [])
return resultOf(id)
}
// The Test result of one connection: a line for each repository, drawn in
// its row, with a tone.
function Checks({ c, t }) {
const r = useResult(c.id)
if (!r) return null
if (r.busy) return {t('check.running')}
return (
{r.items.map((i) => (
-
{i.text}
))}
{r.at > 0 &&
{t('check.at', { time: new Date(r.at).toLocaleTimeString() })}
}
)
}
function RowInfo({ c, t }) {
const repos = c.resources || []
const shown = Boolean(resultOf(c.id)) && repos.length > 0
const mode = writeModeOf(c.write_mode)
return (
<>
{!shown && {repos.length === 0 ? t('noRepos') : t('reposOf', { count: repos.length, list: repos.join(', ') })}}
{t(`writeMode.row.${mode}`)}
>
)
}
// The Test button: the host asks who the token is; this asks, for each listed
// repository, whether the token reaches it. The result goes to the row, so the
// text it returns for the top notice is empty.
async function testExtra(c, t) {
startTest(c.id)
const items = []
for (const repo of c.resources || []) {
let r
try {
const res = await getSdk().api.read({ arguments: { tool: 'check', repo } })
r = res?.data ? { ...res.data, repo } : { repo, reachable: false }
} catch (e) {
r = { repo, failed: checkFailure(e) }
}
const i = checkItem(r)
items.push({ repo, tone: i.tone, text: t(i.key, i.vars) })
}
finishTest(c.id, items)
return ''
}
export function createGitHub() {
function Component({ page, path, search }) {
if (page !== 'apps') return null
return (
)
}
return { Component, components: { ProposalCard }, chatActions: [], dispose() {} }
}