mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
* feat: inline PR comments in the reviews UI
Click the diff gutter "+" on a line to open an inline comment composer
(rendered like the finding card via a Pierre annotation); submitting
posts a real inline PR review comment as the signed-in user through a
new POST /reviews/{owner}/{repo}/{number}/comments. The "+" press-drag →
"Add to Chat" selection path is unchanged.
* feat: GitHub-parity comment box, PR comments dropdown, collapse nav
- Comment composer now mirrors GitHub's box: Write/Preview tabs (markdown
rendered via the existing Markdown component) and a markdown toolbar
(heading, bold, italic, quote, code, link, bulleted/numbered/task list).
- Surface other people's inline PR comments in a Devin-style dropdown in the
review header (search + link to the thread on GitHub). New
GET /reviews/{owner}/{repo}/{number}/comments lists them and flags the
reviewer's own (marker-bearing) comments so they're filtered out.
- Collapse the global nav by default on a review detail page, restoring the
prior preference on leave.
* feat: bigger comment-toolbar icons; open dropdown comments inline
- Enlarge the markdown toolbar glyphs (Phosphor) in the comment composer —
they were rendering at 10px.
- Clicking a comment in the PR comments dropdown now opens it inline in the
diff as a read-only finding-style card (InlineComment), scrolling its line
into view, instead of navigating to GitHub. Falls back to GitHub when the
comment's file/line isn't in the current diff.
* fix: drive "Add to Chat" from native text selection
The gutter "+" is now comment-only; wiring its click to the composer
conflicted with its old double-duty as the drag-to-select handle, which
broke selection → "Add to Chat". Switch to Devin's model: disable Pierre's
interactive line selection and instead map a native text highlight in the
diff to a line range (via the data-line / data-line-type attributes Pierre
stamps on each line, read from the diff's open shadow root) to show the
"Add to Chat" popup. ⌘L and the existing attachment/popup path are unchanged.
* feat: gutter "+" drag selects a range for multi-line comments
Re-enable Pierre's gutter line selection so dragging the "+" down the
gutter comments across a range (click still comments on a single line);
onLineSelectionEnd routes the range to the composer. Native code-text
selection still drives "Add to Chat" — Pierre only line-selects from the
gutter, and onLineSelectionEnd bails when a native text selection is
present, so a code highlight never opens the composer.
* fix: keep the range highlighted while its comment composer is open
Previously opening the composer cleared the selection, so the lines being
commented on lost their highlight. Drive the controlled selection from the
open comment draft's range so the rows stay highlighted until the composer
is closed.
* fix: address PR review — paginate comments, fall back for outdated ones
- list_review_comments now pages through all PR review comments (bounded by
_MAX_REVIEW_COMMENT_PAGES) instead of returning only the first 100, so older
comments still show in the dropdown.
- Surface GitHub's outdated flag (position == null) as is_outdated; opening such
a comment (or one whose line isn't in the diff) now opens it on GitHub instead
of silently rendering nothing, plus a timeout fallback if the annotation never
mounts (e.g. collapsed context).
809 lines
22 KiB
TypeScript
809 lines
22 KiB
TypeScript
/**
|
|
* Typed client for the open-swe dashboard backend.
|
|
*
|
|
* All requests are sent with credentials so the httpOnly `osw_session`
|
|
* cookie set by the OAuth callback rides along on cross-origin calls.
|
|
*/
|
|
|
|
const API_BASE = (import.meta.env.VITE_DASHBOARD_API_BASE_URL ?? "").replace(
|
|
/\/$/,
|
|
""
|
|
)
|
|
|
|
if (!API_BASE && typeof window !== "undefined") {
|
|
console.warn("VITE_DASHBOARD_API_BASE_URL is not set")
|
|
}
|
|
|
|
const GITHUB_IMAGE_HOST_RE =
|
|
/^(?:www\.)?github\.com$|\.githubusercontent\.com$/i
|
|
|
|
/**
|
|
* Build an authenticated proxy URL for GitHub-hosted PR images. Private-repo
|
|
* attachments can't be loaded directly by the browser, so they're routed
|
|
* through the dashboard backend which holds the App token. Non-GitHub image
|
|
* URLs are returned unchanged.
|
|
*/
|
|
export function reviewImageProxyUrl(
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
src: string
|
|
): string {
|
|
let parsed: URL
|
|
try {
|
|
parsed = new URL(src)
|
|
} catch {
|
|
return src
|
|
}
|
|
if (
|
|
parsed.protocol !== "https:" ||
|
|
!GITHUB_IMAGE_HOST_RE.test(parsed.hostname)
|
|
) {
|
|
return src
|
|
}
|
|
if (
|
|
/^(?:www\.)?github\.com$/i.test(parsed.hostname) &&
|
|
!parsed.pathname.startsWith("/user-attachments/")
|
|
) {
|
|
return src
|
|
}
|
|
const path = `/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/image`
|
|
return `${API_BASE}/dashboard/api${path}?url=${encodeURIComponent(src)}`
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public readonly status: number,
|
|
message: string
|
|
) {
|
|
super(message)
|
|
this.name = "ApiError"
|
|
}
|
|
}
|
|
|
|
export function isGithubReauthError(error: unknown): boolean {
|
|
if (!(error instanceof ApiError)) return false
|
|
if (error.status === 401) return true
|
|
return /github token|re-login required/i.test(error.message)
|
|
}
|
|
|
|
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const res = await fetch(`${API_BASE}/dashboard/api${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(init.headers ?? {}),
|
|
},
|
|
})
|
|
if (!res.ok) {
|
|
let message = res.statusText
|
|
try {
|
|
const body = await res.json()
|
|
if (body?.detail)
|
|
message =
|
|
typeof body.detail === "string"
|
|
? body.detail
|
|
: JSON.stringify(body.detail)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
throw new ApiError(res.status, message)
|
|
}
|
|
if (res.status === 204) return undefined as T
|
|
return (await res.json()) as T
|
|
}
|
|
|
|
export interface SessionUser {
|
|
login: string
|
|
email: string | null
|
|
avatar_url: string | null
|
|
is_admin: boolean
|
|
slack_oauth_enabled?: boolean
|
|
}
|
|
|
|
export interface ModelOption {
|
|
id: string
|
|
label: string
|
|
efforts: Array<string>
|
|
default_effort: string
|
|
supports_images: boolean
|
|
}
|
|
|
|
export interface OptionsPayload {
|
|
models: Array<ModelOption>
|
|
default_agent_model: string
|
|
default_agent_reasoning_effort: string
|
|
default_agent_subagent_model: string
|
|
default_agent_subagent_reasoning_effort: string
|
|
}
|
|
|
|
export interface Profile {
|
|
login?: string
|
|
email?: string
|
|
default_model?: string
|
|
reasoning_effort?: string
|
|
default_subagent_model?: string | null
|
|
subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
base_branch?: string | null
|
|
branch_prefix?: string | null
|
|
auto_fix_ci?: boolean
|
|
create_prs?: boolean
|
|
review_draft_prs?: boolean | null
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface ProfileUpdate {
|
|
default_model: string
|
|
reasoning_effort: string
|
|
default_subagent_model?: string | null
|
|
subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
base_branch?: string | null
|
|
branch_prefix?: string | null
|
|
auto_fix_ci?: boolean
|
|
create_prs?: boolean
|
|
review_draft_prs?: boolean | null
|
|
}
|
|
|
|
export interface TeamSettings {
|
|
review_draft_prs: boolean
|
|
pr_summaries: boolean
|
|
review_trace_links: boolean
|
|
org_guidelines?: string | null
|
|
default_agent_model?: string | null
|
|
default_agent_reasoning_effort?: string | null
|
|
default_agent_subagent_model?: string | null
|
|
default_agent_subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
default_reviewer_model?: string | null
|
|
default_reviewer_reasoning_effort?: string | null
|
|
default_reviewer_subagent_model?: string | null
|
|
default_reviewer_subagent_reasoning_effort?: string | null
|
|
default_grouping_model?: string | null
|
|
default_grouping_reasoning_effort?: string | null
|
|
default_chat_model?: string | null
|
|
default_chat_reasoning_effort?: string | null
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface ProviderCredentialStatus {
|
|
connected: boolean
|
|
site?: string
|
|
endpoint?: string
|
|
api_key_last4?: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface TeamCredentialsStatus {
|
|
datadog: ProviderCredentialStatus
|
|
langsmith: ProviderCredentialStatus
|
|
}
|
|
|
|
export interface DatadogConnectBody {
|
|
site: string
|
|
api_key: string
|
|
app_key: string
|
|
}
|
|
|
|
export interface LangSmithConnectBody {
|
|
api_key: string
|
|
endpoint?: string | null
|
|
}
|
|
|
|
export interface CurrentsCredentialStatus {
|
|
connected: boolean
|
|
api_key_last4?: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface CurrentsConnectBody {
|
|
api_key: string
|
|
}
|
|
|
|
export interface NotionCredentialStatus {
|
|
connected: boolean
|
|
token_expires_at?: string | null
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface UserMapping {
|
|
github_login: string
|
|
work_email: string
|
|
slack_user_id?: string | null
|
|
source?: string
|
|
status?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface UserMappingsPage {
|
|
items: Array<UserMapping>
|
|
total: number
|
|
page: number
|
|
page_size: number
|
|
}
|
|
|
|
export type UsageLeaderboardPeriod = "7d" | "30d" | "all"
|
|
|
|
export interface UsageLeaderboardRow {
|
|
rank: number
|
|
user: {
|
|
name: string
|
|
github_login: string | null
|
|
email: string | null
|
|
}
|
|
favorite_model: string
|
|
agent_runs: number
|
|
prs_opened: number
|
|
merged_prs: number
|
|
agent_loc: number
|
|
additions: number
|
|
deletions: number
|
|
}
|
|
|
|
export interface ReviewerStatsCounterRow {
|
|
name: string
|
|
count: number
|
|
}
|
|
|
|
export interface ReviewerStatsPayload {
|
|
period: UsageLeaderboardPeriod
|
|
reviewed_prs: number
|
|
prs_with_findings: number
|
|
findings_recorded: number
|
|
surfaced_findings: number
|
|
addressed_findings: number
|
|
resolved_after_update: number
|
|
dismissed_findings: number
|
|
unresolved_surfaced_findings: number
|
|
resolution_rate: number
|
|
human_replies: number
|
|
severity_counts: Record<string, number>
|
|
top_categories: Array<ReviewerStatsCounterRow>
|
|
generated_at_ms: number | null
|
|
}
|
|
|
|
export interface UsageLeaderboardPayload {
|
|
period: UsageLeaderboardPeriod
|
|
rows: Array<UsageLeaderboardRow>
|
|
total_members: number
|
|
current_user_rank: number | null
|
|
generated_at_ms: number | null
|
|
reviewer_stats: ReviewerStatsPayload
|
|
}
|
|
|
|
export interface Repository {
|
|
full_name: string
|
|
private: boolean
|
|
}
|
|
|
|
export interface Installation {
|
|
id: number
|
|
account: string | null
|
|
account_type: string | null
|
|
}
|
|
|
|
export interface ReposPayload {
|
|
installations: Array<Installation>
|
|
repositories: Array<Repository>
|
|
}
|
|
|
|
export type ReviewStyleStatus = "idle" | "running" | "completed" | "failed"
|
|
|
|
export interface ReviewStyle {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
status: ReviewStyleStatus
|
|
custom_prompt: string | null
|
|
analysis_summary: string | null
|
|
top_reviewers: Array<string>
|
|
prs_sampled: number
|
|
reviews_sampled: number
|
|
analysis_thread_id: string | null
|
|
analysis_run_id: string | null
|
|
error: string | null
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface AgentInstructions {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
instructions: string
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export type RepoSnapshotStatus = "none" | "building" | "ready" | "failed"
|
|
|
|
export interface RepoSnapshot {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
dockerfile: string
|
|
snapshot_id: string | null
|
|
snapshot_name: string | null
|
|
status: RepoSnapshotStatus
|
|
status_message: string | null
|
|
build_log: string | null
|
|
fs_capacity_bytes?: number
|
|
vcpus?: number
|
|
mem_bytes?: number
|
|
target?: string | null
|
|
build_args?: Record<string, string> | null
|
|
build_started_at?: string | null
|
|
last_built_at?: string | null
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface RepoSnapshotUpdateBody {
|
|
dockerfile: string
|
|
fs_capacity_bytes?: number | null
|
|
vcpus?: number | null
|
|
mem_bytes?: number | null
|
|
target?: string | null
|
|
build_args?: Record<string, string> | null
|
|
}
|
|
|
|
export type FindingSeverity = "low" | "medium" | "high" | "critical"
|
|
export type FindingConfidence = "low" | "medium" | "high"
|
|
export type FindingStatus = "open" | "resolved" | "dismissed"
|
|
export type FindingGroup = "bug" | "investigate" | "informational"
|
|
|
|
export interface FindingInteraction {
|
|
kind: "human_reply" | "bot_reply"
|
|
author?: string
|
|
body?: string
|
|
created_at?: string
|
|
}
|
|
|
|
export interface ReviewFinding {
|
|
id: string
|
|
severity: FindingSeverity
|
|
confidence: FindingConfidence
|
|
category: string
|
|
title: string
|
|
description: string
|
|
suggestion: string | null
|
|
file: string
|
|
start_line: number | null
|
|
end_line: number | null
|
|
side: "LEFT" | "RIGHT"
|
|
in_diff: boolean
|
|
status: FindingStatus
|
|
outdated: boolean
|
|
resolution_note: string | null
|
|
diff_hunk: string | null
|
|
github_thread_resolved: boolean
|
|
github_review_comment_id: number | null
|
|
interactions: Array<FindingInteraction>
|
|
group: FindingGroup
|
|
}
|
|
|
|
export interface ReviewCommentCreate {
|
|
path: string
|
|
line: number
|
|
side: "LEFT" | "RIGHT"
|
|
body: string
|
|
start_line?: number | null
|
|
start_side?: "LEFT" | "RIGHT" | null
|
|
}
|
|
|
|
export interface ReviewCommentResult {
|
|
id: number
|
|
html_url: string
|
|
}
|
|
|
|
export interface PrReviewComment {
|
|
id: number
|
|
author: string
|
|
author_avatar_url: string
|
|
path: string
|
|
line: number | null
|
|
side: "LEFT" | "RIGHT"
|
|
body: string
|
|
html_url: string
|
|
created_at: string
|
|
is_open_swe: boolean
|
|
// Outdated: the line no longer appears in the current diff, so it can't render inline.
|
|
is_outdated: boolean
|
|
}
|
|
|
|
export interface ReviewCommentsPayload {
|
|
comments: Array<PrReviewComment>
|
|
}
|
|
|
|
export interface ReviewCounts {
|
|
open: number
|
|
resolved: number
|
|
dismissed: number
|
|
bugs: number
|
|
flags: number
|
|
}
|
|
|
|
export interface ReviewSummary {
|
|
thread_id: string
|
|
owner: string
|
|
repo: string
|
|
number: number
|
|
title: string
|
|
url: string
|
|
head_ref: string
|
|
base_ref: string
|
|
author: string
|
|
head_sha: string
|
|
watch: boolean
|
|
status: "running" | "error" | "idle"
|
|
counts: ReviewCounts
|
|
updated_at: string | null
|
|
full_name?: string
|
|
}
|
|
|
|
export interface ReviewListPayload {
|
|
reviews: Array<ReviewSummary>
|
|
page: number
|
|
has_more: boolean
|
|
}
|
|
|
|
export interface ReviewUserRef {
|
|
login: string
|
|
avatar_url?: string | null
|
|
}
|
|
|
|
export interface ReviewCheckRun {
|
|
name: string
|
|
status: string
|
|
conclusion: string | null
|
|
url: string | null
|
|
}
|
|
|
|
export interface ReviewPrDetails {
|
|
state: string
|
|
title: string
|
|
body: string
|
|
additions: number
|
|
deletions: number
|
|
changed_files: number
|
|
commits: number
|
|
head_sha: string
|
|
head_ref: string
|
|
base_ref: string
|
|
author: ReviewUserRef | null
|
|
assignees: Array<ReviewUserRef>
|
|
requested_reviewers: Array<ReviewUserRef>
|
|
labels: Array<{ name: string; color: string | null }>
|
|
}
|
|
|
|
export interface ReviewDiffGroup {
|
|
index: number
|
|
title: string
|
|
summary: string
|
|
files: Array<string>
|
|
}
|
|
|
|
export interface ReviewDetail extends ReviewSummary {
|
|
pr: ReviewPrDetails
|
|
checks: Array<ReviewCheckRun>
|
|
findings: Array<ReviewFinding>
|
|
diff_groups: Array<ReviewDiffGroup>
|
|
diff_groups_stale: boolean
|
|
}
|
|
|
|
export interface ReviewDiffFile {
|
|
path: string
|
|
previousPath: string | null
|
|
status: "added" | "removed" | "modified" | "renamed"
|
|
additions: number
|
|
deletions: number
|
|
originalContent: string
|
|
modifiedContent: string
|
|
unrenderable?: boolean
|
|
}
|
|
|
|
export interface ReviewDiffPayload {
|
|
files: Array<ReviewDiffFile>
|
|
total_additions: number
|
|
total_deletions: number
|
|
truncated: boolean
|
|
}
|
|
|
|
export interface ReviewChatMeta {
|
|
available: boolean
|
|
assistant_id: string
|
|
}
|
|
|
|
export interface ReviewChatThread {
|
|
thread_id: string
|
|
title: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
/**
|
|
* Absolute base URL for the PR chat's LangGraph StreamProvider. The SDK builds
|
|
* request URLs as `new URL(apiUrl + path)`, so this must be absolute — a
|
|
* same-origin base is promoted using the current origin.
|
|
*/
|
|
export function reviewChatApiBase(
|
|
owner: string,
|
|
repo: string,
|
|
number: number
|
|
): string {
|
|
const path = `${API_BASE}/dashboard/api/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat`
|
|
if (/^https?:\/\//.test(path)) return path
|
|
if (typeof window !== "undefined") {
|
|
return `${window.location.origin}${path.startsWith("/") ? "" : "/"}${path}`
|
|
}
|
|
return path
|
|
}
|
|
|
|
export type ReviewerEvalScoreMode = "all_findings" | "surfaced_findings"
|
|
export type ReviewerEvalSeverity = "low" | "medium" | "high" | "critical"
|
|
|
|
export interface ReviewerEvalConfig {
|
|
dataset_name: string
|
|
experiment_prefix: string
|
|
max_concurrency: number
|
|
langsmith_project: string
|
|
langgraph_url: string
|
|
assistant_id: string
|
|
model_id: string
|
|
reasoning_effort: string
|
|
score_mode: ReviewerEvalScoreMode
|
|
severity_threshold: ReviewerEvalSeverity
|
|
cap: number
|
|
}
|
|
|
|
export interface ReviewerEvalProgress {
|
|
completed: number
|
|
total: number | null
|
|
}
|
|
|
|
export interface ReviewerEvalStatus {
|
|
name: string
|
|
status: "idle" | "running" | "completed" | "failed"
|
|
run_name?: string
|
|
langsmith_project: string
|
|
limit: number | null
|
|
config_snapshot?: ReviewerEvalConfig
|
|
started_at: string | null
|
|
finished_at: string | null
|
|
created_by: string | null
|
|
pid: number | null
|
|
exit_code: number | null
|
|
experiment_url: string | null
|
|
error: string | null
|
|
log_tail: string | null
|
|
progress?: ReviewerEvalProgress | null
|
|
github_run_url?: string | null
|
|
trigger?: string | null
|
|
updated_at: string
|
|
}
|
|
|
|
export const api = {
|
|
me: () => request<SessionUser>("/me"),
|
|
options: () => request<OptionsPayload>("/options"),
|
|
profile: () => request<Profile>("/profile"),
|
|
saveProfile: (body: ProfileUpdate) =>
|
|
request<Profile>("/profile", { method: "PUT", body: JSON.stringify(body) }),
|
|
repos: () => request<ReposPayload>("/repos"),
|
|
listReviewStyles: () => request<Array<ReviewStyle>>("/review-styles"),
|
|
createReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>("/review-styles", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(`/review-styles/${encodeURIComponent(full_name)}`),
|
|
saveReviewStylePrompt: (full_name: string, custom_prompt: string) =>
|
|
request<ReviewStyle>(`/review-styles/${encodeURIComponent(full_name)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ custom_prompt }),
|
|
}),
|
|
analyzeReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(
|
|
`/review-styles/${encodeURIComponent(full_name)}/analyze`,
|
|
{
|
|
method: "POST",
|
|
}
|
|
),
|
|
cancelReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(
|
|
`/review-styles/${encodeURIComponent(full_name)}/cancel`,
|
|
{
|
|
method: "POST",
|
|
}
|
|
),
|
|
deleteReviewStyle: (full_name: string) =>
|
|
request<void>(`/review-styles/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
listAgentInstructions: () =>
|
|
request<Array<AgentInstructions>>("/agent-instructions"),
|
|
createAgentInstructions: (full_name: string) =>
|
|
request<AgentInstructions>("/agent-instructions", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getAgentInstructions: (full_name: string) =>
|
|
request<AgentInstructions>(
|
|
`/agent-instructions/${encodeURIComponent(full_name)}`
|
|
),
|
|
saveAgentInstructions: (full_name: string, instructions: string) =>
|
|
request<AgentInstructions>(
|
|
`/agent-instructions/${encodeURIComponent(full_name)}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ instructions }),
|
|
}
|
|
),
|
|
deleteAgentInstructions: (full_name: string) =>
|
|
request<void>(`/agent-instructions/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
listRepoSnapshots: () => request<Array<RepoSnapshot>>("/repo-snapshots"),
|
|
createRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>("/repo-snapshots", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>(`/repo-snapshots/${encodeURIComponent(full_name)}`),
|
|
getRepoSnapshotTemplate: (full_name: string) =>
|
|
request<{ dockerfile: string }>(
|
|
`/repo-snapshots/template?full_name=${encodeURIComponent(full_name)}`
|
|
),
|
|
saveRepoSnapshot: (full_name: string, body: RepoSnapshotUpdateBody) =>
|
|
request<RepoSnapshot>(`/repo-snapshots/${encodeURIComponent(full_name)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
buildRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>(
|
|
`/repo-snapshots/${encodeURIComponent(full_name)}/build`,
|
|
{ method: "POST" }
|
|
),
|
|
deleteRepoSnapshot: (full_name: string) =>
|
|
request<void>(`/repo-snapshots/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
getTeamSettings: () => request<TeamSettings>("/team-settings"),
|
|
saveTeamSettings: (body: TeamSettings) =>
|
|
request<TeamSettings>("/team-settings", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
getTeamCredentials: () => request<TeamCredentialsStatus>("/team-credentials"),
|
|
connectDatadog: (body: DatadogConnectBody) =>
|
|
request<TeamCredentialsStatus>("/team-credentials/datadog", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectDatadog: () =>
|
|
request<TeamCredentialsStatus>("/team-credentials/datadog", {
|
|
method: "DELETE",
|
|
}),
|
|
connectLangSmith: (body: LangSmithConnectBody) =>
|
|
request<TeamCredentialsStatus>("/team-credentials/langsmith", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectLangSmith: () =>
|
|
request<TeamCredentialsStatus>("/team-credentials/langsmith", {
|
|
method: "DELETE",
|
|
}),
|
|
getMyCurrentsStatus: () =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents"),
|
|
connectCurrents: (body: CurrentsConnectBody) =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectCurrents: () =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents", {
|
|
method: "DELETE",
|
|
}),
|
|
getMyNotionStatus: () =>
|
|
request<NotionCredentialStatus>("/my-credentials/notion"),
|
|
disconnectNotion: () =>
|
|
request<NotionCredentialStatus>("/my-credentials/notion", {
|
|
method: "DELETE",
|
|
}),
|
|
listEnabledReviewRepos: () =>
|
|
request<{ repos: Array<string> }>("/enabled-review-repos"),
|
|
setEnabledReviewRepo: (full_name: string, enabled: boolean) =>
|
|
request<{ repos: Array<string> }>("/enabled-review-repos", {
|
|
method: "PUT",
|
|
body: JSON.stringify({ full_name, enabled }),
|
|
}),
|
|
usageLeaderboard: (period: UsageLeaderboardPeriod = "30d", limit = 10) =>
|
|
request<UsageLeaderboardPayload>(
|
|
`/agent-usage-leaderboard?period=${encodeURIComponent(period)}&limit=${limit}`
|
|
),
|
|
myMapping: () => request<Partial<UserMapping>>("/my-mapping"),
|
|
adminListUserMappings: (page = 1, pageSize = 20) =>
|
|
request<UserMappingsPage>(
|
|
`/admin/user-mappings?page=${page}&page_size=${pageSize}`
|
|
),
|
|
adminDeleteUserMapping: (github_login: string) =>
|
|
request<{ deleted: boolean }>(
|
|
`/admin/user-mappings/${encodeURIComponent(github_login)}`,
|
|
{ method: "DELETE" }
|
|
),
|
|
listReviews: (page: number, mine: boolean) =>
|
|
request<ReviewListPayload>(`/reviews?page=${page}&mine=${mine}`),
|
|
getReview: (owner: string, repo: string, number: number) =>
|
|
request<ReviewDetail>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}`
|
|
),
|
|
getReviewDiff: (owner: string, repo: string, number: number) =>
|
|
request<ReviewDiffPayload>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/diff`
|
|
),
|
|
getReviewChat: (owner: string, repo: string, number: number) =>
|
|
request<ReviewChatMeta>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat`
|
|
),
|
|
listReviewChatThreads: (owner: string, repo: string, number: number) =>
|
|
request<{ threads: Array<ReviewChatThread> }>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat/threads`
|
|
),
|
|
deleteReviewChatThread: (
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
threadId: string
|
|
) =>
|
|
request<void>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat/threads/${encodeURIComponent(threadId)}`,
|
|
{ method: "DELETE" }
|
|
),
|
|
reReview: (owner: string, repo: string, number: number) =>
|
|
request<{
|
|
success: boolean
|
|
queued: boolean
|
|
thread_id: string
|
|
pr_url: string
|
|
}>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/re-review`,
|
|
{ method: "POST" }
|
|
),
|
|
createReviewComment: (
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
body: ReviewCommentCreate
|
|
) =>
|
|
request<ReviewCommentResult>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/comments`,
|
|
{ method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
listReviewComments: (owner: string, repo: string, number: number) =>
|
|
request<ReviewCommentsPayload>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/comments`
|
|
),
|
|
getReviewerEval: () => request<ReviewerEvalStatus>("/admin/evals/reviewer"),
|
|
logout: () => request<void>("/auth/logout", { method: "POST" }),
|
|
}
|
|
|
|
export function loginUrl(redirectTo?: string): string {
|
|
const target =
|
|
redirectTo ?? (typeof window !== "undefined" ? window.location.origin : "")
|
|
const qs = target ? `?redirect_to=${encodeURIComponent(target)}` : ""
|
|
return `${API_BASE}/dashboard/api/auth/login${qs}`
|
|
}
|
|
|
|
export function slackConnectUrl(): string {
|
|
return `${API_BASE}/dashboard/api/slack/login`
|
|
}
|
|
|
|
export function notionConnectUrl(): string {
|
|
return `${API_BASE}/dashboard/api/notion/login`
|
|
}
|