Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add page for ai-bridge interception logs#20331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
jakehwll wants to merge59 commits intomain
base:main
Choose a base branch
Loading
fromjakehwll/routing-ai-governance
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
59 commits
Select commitHold shift + click to select a range
cfc4aa9
feat!: impl `/aigovernance` to `Admin Settings` (hardcoded on)
jakehwllOct 14, 2025
134495a
feat: add `/aigovernance` routes and layout
jakehwllOct 14, 2025
6cd0819
feat: feed some dummy content
jakehwllOct 14, 2025
1f9fddf
feat: implement `<RequestLogsRow />`
jakehwllOct 14, 2025
0231d38
feat: query interceptions from backend
jakehwllOct 15, 2025
bc71782
feat: render interceptions in frontend
jakehwllOct 15, 2025
6e05475
fix: omit `status` column (lacking data)
jakehwllOct 15, 2025
afab507
fix: ensure that we're checking for `aibridge` for `ai governance` menu
jakehwllOct 15, 2025
828d381
feat: empty and loading states on `<RequestLogsPageView />` table
jakehwllOct 15, 2025
ba289ec
feat: add `<Paywall />` to `/ai-governance/request-logs`
jakehwllOct 15, 2025
b7280f5
chore: add missing todo
jakehwllOct 15, 2025
5abbac5
feat: link documentation to `/ai-coder/ai-bridge`
jakehwllOct 16, 2025
811d0c5
fix: menu checking for `aibridge` experiment on
jakehwllOct 16, 2025
6a74e80
fix: prefer to use `.toLocalString()` over string
jakehwllOct 16, 2025
ebf2e91
feat: add storybook generation
jakehwllOct 21, 2025
37df869
chore: resolve build
jakehwllOct 21, 2025
f282470
feat: use `usePaginatedQuery()` and `<PaginationContainer />`
jakehwllOct 21, 2025
4f09456
feat: implement pagination
jakehwllOct 22, 2025
baad816
fix: improve opening button
jakehwllOct 22, 2025
32ae292
fix: show useravatar and username
jakehwllOct 22, 2025
0d4bd15
fix: improve rendered content from json blob
jakehwllOct 22, 2025
5dc3679
chore: remove redundant `onclick`
jakehwllOct 22, 2025
0346760
fix: improve the gaps in content
jakehwllOct 22, 2025
8ef4207
fix: remove redundant `cursor-pointer`
jakehwllOct 22, 2025
c0113b9
feat: implement filtering, for users
jakehwllOct 23, 2025
ecdc47b
feat: implement filter, provider
jakehwllOct 23, 2025
ec5f26f
fix: move back to clickable row
jakehwllOct 23, 2025
0a556a0
fix: resolve padding on error not existing
jakehwllOct 24, 2025
126c72a
chore: remove export to pass CI (TODO still)
jakehwllOct 24, 2025
e7d32a2
chore: refactor
jakehwllOct 24, 2025
a47a885
chore: improve rendering logic on table
jakehwllOct 24, 2025
cec12d3
feat: add a tooltip to describe the feature
jakehwllOct 24, 2025
256f66f
fix: ensure that experiment is enabled
jakehwllOct 24, 2025
6146518
chore: resolve formatting
jakehwllOct 27, 2025
40aef12
Merge branch 'main' into jakehwll/routing-ai-governance
jakehwllOct 27, 2025
d3eb975
chore: resolve mismerged type
jakehwllOct 27, 2025
23719e6
fix: resolve todo type
jakehwllOct 27, 2025
125097e
fix: resolve `pageTitle()` ordering
jakehwllOct 27, 2025
18edcdb
feat: resolve storybook story
jakehwllOct 28, 2025
0b4bb34
feat: `Close` story on `<RequestLogsRow />`
jakehwllOct 28, 2025
12bdc9a
fix: refactor renames in `<RequestLogsPageView />` stories
jakehwllOct 28, 2025
ec05db5
feat: add `Loading` story in `<RequestLogsPageView />` story
jakehwllOct 28, 2025
930e9af
Merge branch 'main' into jakehwll/routing-ai-governance
jakehwllOct 28, 2025
ac869e1
fix: add `ended_at` to `testHelpers/entities`
jakehwllOct 28, 2025
e18640d
feat: move to using `input` tokens and `output` tokens
jakehwllOct 28, 2025
7d6f8b3
feat: show end time if it exists
jakehwllOct 28, 2025
a47f2bb
chore: resolve docs link to `/docs/ai-coder/ai-bridge`
jakehwllOct 28, 2025
aeb7f29
feat: add tooltips and iconography to `input/output` on table rows
jakehwllOct 28, 2025
32e8777
fix: prefer tailwind over `<Stack />`
jakehwllOct 29, 2025
3415635
fix: reduce `<TooltipProvider />` delays
jakehwllOct 29, 2025
9d09f5a
chore: resolve lint
jakehwllOct 29, 2025
b00b43f
chore: migrate to using `Boolean()` on `isRequestLogsVisible`
jakehwllOct 30, 2025
067a361
chore: inline `Language` constant
jakehwllOct 30, 2025
e762a6c
chore: redact down storybook title
jakehwllOct 30, 2025
347aa14
chore: remove unnecessary `{}` around props
jakehwllOct 30, 2025
7a4bf51
chore: use `viewAnyAIBridgeInterception` permission
jakehwllOct 30, 2025
1b883f3
Merge branch 'main' into jakehwll/routing-ai-governance
jakehwllOct 30, 2025
13a0cc9
Merge branch 'main' into jakehwll/routing-ai-governance
jakehwllOct 30, 2025
f5909e5
Merge branch 'main' into jakehwll/routing-ai-governance
jakehwllOct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletionssite/src/api/api.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2716,6 +2716,16 @@ class ExperimentalApiMethods {
setTimeout(() => res(), 500);
});
};

getAIBridgeInterceptions = async (options: SearchParamOptions) => {
const url = getURLWithSearchParams(
"/api/experimental/aibridge/interceptions",
options,
);
const response =
await this.axios.get<TypesGen.AIBridgeListInterceptionsResponse>(url);
return response.data;
};
}

// This is a hard coded CSRF token/cookie pair for local development. In prod,
Expand Down
21 changes: 21 additions & 0 deletionssite/src/api/queries/aiBridge.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import { API } from "api/api";
import type { AIBridgeListInterceptionsResponse } from "api/typesGenerated";
import { useFilterParamsKey } from "components/Filter/Filter";
import type { UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";

export const paginatedInterceptions = (
searchParams: URLSearchParams,
): UsePaginatedQueryOptions<AIBridgeListInterceptionsResponse, string> => {
return {
queryPayload: () => searchParams.get(useFilterParamsKey) ?? "",
queryKey: ({ payload, pageNumber }) => {
return ["aiBridgeInterceptions", payload, pageNumber] as const;
},
queryFn: ({ limit, offset, payload }) =>
API.experimental.getAIBridgeInterceptions({
offset,
limit,
q: payload,
}),
};
};
15 changes: 15 additions & 0 deletionssite/src/modules/dashboard/Navbar/DeploymentDropdown.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@ interface DeploymentDropdownProps {
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
canViewAIGovernance: boolean;
}

export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
Expand All@@ -26,6 +27,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewAuditLog,
canViewConnectionLog,
canViewHealth,
canViewAIGovernance,
}) => {
if (
!canViewAuditLog &&
Expand DownExpand Up@@ -56,6 +58,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewHealth={canViewHealth}
canViewAIGovernance={canViewAIGovernance}
/>
</PopoverContent>
</Popover>
Expand All@@ -68,6 +71,7 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
canViewAuditLog,
canViewHealth,
canViewConnectionLog,
canViewAIGovernance,
}) => {
return (
<nav>
Expand DownExpand Up@@ -111,6 +115,17 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
</MenuItem>
</PopoverClose>
)}
{canViewAIGovernance && (
<PopoverClose asChild>
<MenuItem
component={NavLink}
to="/aigovernance"
css={styles.menuItem}
>
AI Governance
</MenuItem>
</PopoverClose>
)}
{canViewHealth && (
<PopoverClose asChild>
<MenuItem component={NavLink} to="/health" css={styles.menuItem}>
Expand Down
3 changes: 3 additions & 0 deletionssite/src/modules/dashboard/Navbar/Navbar.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ export const Navbar: FC = () => {
featureVisibility.audit_log && permissions.viewAnyAuditLog;
const canViewConnectionLog =
featureVisibility.connection_log && permissions.viewAnyConnectionLog;
const canViewAIGovernance =
featureVisibility.aibridge && permissions.viewAnyAIBridgeInterception;

const uniqueLinks = new Map<string, LinkConfig>();
for (const link of appearance.support_links ?? []) {
Expand All@@ -44,6 +46,7 @@ export const Navbar: FC = () => {
canViewHealth={canViewHealth}
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewAIGovernance={canViewAIGovernance}
proxyContextValue={proxyContextValue}
/>
);
Expand Down
4 changes: 4 additions & 0 deletionssite/src/modules/dashboard/Navbar/NavbarView.test.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,6 +34,7 @@ describe("NavbarView", () => {
canViewHealth
canViewAuditLog
canViewConnectionLog
canViewAIGovernance
supportLinks={[]}
/>,
);
Expand All@@ -53,6 +54,7 @@ describe("NavbarView", () => {
canViewHealth
canViewAuditLog
canViewConnectionLog
canViewAIGovernance
supportLinks={[]}
/>,
);
Expand All@@ -72,6 +74,7 @@ describe("NavbarView", () => {
canViewHealth
canViewAuditLog
canViewConnectionLog
canViewAIGovernance
supportLinks={[]}
/>,
);
Expand All@@ -92,6 +95,7 @@ describe("NavbarView", () => {
canViewHealth
canViewAuditLog
canViewConnectionLog
canViewAIGovernance
supportLinks={[]}
/>,
);
Expand Down
3 changes: 3 additions & 0 deletionssite/src/modules/dashboard/Navbar/NavbarView.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ interface NavbarViewProps {
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
canViewAIGovernance: boolean;
proxyContextValue?: ProxyContextValue;
}

Expand All@@ -55,6 +56,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewHealth,
canViewAuditLog,
canViewConnectionLog,
canViewAIGovernance,
proxyContextValue,
}) => {
const webPush = useWebpushNotifications();
Expand DownExpand Up@@ -95,6 +97,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewDeployment={canViewDeployment}
canViewHealth={canViewHealth}
canViewConnectionLog={canViewConnectionLog}
canViewAIGovernance={canViewAIGovernance}
/>
</div>

Expand Down
7 changes: 7 additions & 0 deletionssite/src/modules/permissions/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,6 +169,13 @@ export const permissionChecks = {
},
action: "read",
},
viewAnyAIBridgeInterception: {
object: {
resource_type: "aibridge_interception",
any_org: true,
},
action: "read",
},
} as const satisfies Record<string, AuthorizationCheck>;

export const canViewDeploymentSettings = (
Expand Down
32 changes: 32 additions & 0 deletionssite/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
import{
HelpTooltip,
HelpTooltipContent,
HelpTooltipIconTrigger,
HelpTooltipLink,
HelpTooltipLinksGroup,
HelpTooltipText,
HelpTooltipTitle,
}from"components/HelpTooltip/HelpTooltip";
importtype{FC}from"react";
import{docs}from"utils/docs";

exportconstAIGovernanceHelpTooltip:FC=()=>{
return(
<HelpTooltip>
<HelpTooltipIconTrigger/>

<HelpTooltipContent>
<HelpTooltipTitle>What is AI Governance?</HelpTooltipTitle>
<HelpTooltipText>
AI Governance is a proxy that unifies and audits LLM usage across your
organization.
</HelpTooltipText>
<HelpTooltipLinksGroup>
<HelpTooltipLinkhref={docs("/docs/ai-coder/ai-bridge")}>
What we track
</HelpTooltipLink>
</HelpTooltipLinksGroup>
</HelpTooltipContent>
</HelpTooltip>
);
};
32 changes: 32 additions & 0 deletionssite/src/pages/AIGovernancePage/AIGovernanceLayout.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
import{Margins}from"components/Margins/Margins";
import{
PageHeader,
PageHeaderSubtitle,
PageHeaderTitle,
}from"components/PageHeader/PageHeader";
importtype{FC,PropsWithChildren}from"react";
import{Outlet}from"react-router";
import{AIGovernanceHelpTooltip}from"./AIGovernanceHelpTooltip";

constAIGovernanceLayout:FC<PropsWithChildren>=({
children=<Outlet/>,
})=>{
return(
<MarginsclassName="pb-12">
<PageHeader>
<PageHeaderTitle>
<divclassName="flex items-center gap-2">
<span>AI Governance</span>
<AIGovernanceHelpTooltip/>
</div>
</PageHeaderTitle>
<PageHeaderSubtitle>
Manage usage for your organization.
</PageHeaderSubtitle>
</PageHeader>
{children}
</Margins>
);
};

exportdefaultAIGovernanceLayout;
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
import { paginatedInterceptions } from "api/queries/aiBridge";
import { useFilter } from "components/Filter/Filter";
import { useUserFilterMenu } from "components/Filter/UserFilter";
import { usePaginatedQuery } from "hooks/usePaginatedQuery";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import type { FC } from "react";
import { useSearchParams } from "react-router";
import { pageTitle } from "utils/page";
import { useProviderFilterMenu } from "./filter/filter";
import { RequestLogsPageView } from "./RequestLogsPageView";

const RequestLogsPage: FC = () => {
const feats = useFeatureVisibility();
const isRequestLogsVisible = Boolean(feats.aibridge);

const [searchParams, setSearchParams] = useSearchParams();
const interceptionsQuery = usePaginatedQuery(
paginatedInterceptions(searchParams),
);
const filter = useFilter({
searchParams,
onSearchParamsChange: setSearchParams,
onUpdate: interceptionsQuery.goToFirstPage,
});

const userMenu = useUserFilterMenu({
value: filter.values.initiator,
onChange: (option) =>
filter.update({
...filter.values,
initiator: option?.value,
}),
});

const providerMenu = useProviderFilterMenu({
value: filter.values.provider,
onChange: (option) =>
filter.update({
...filter.values,
provider: option?.value,
}),
});

return (
<>
<title>{pageTitle("Request Logs", "AI Governance")}</title>

<RequestLogsPageView
isLoading={interceptionsQuery.isLoading}
isRequestLogsVisible={isRequestLogsVisible}
interceptions={interceptionsQuery.data?.results}
interceptionsQuery={interceptionsQuery}
filterProps={{
filter,
error: interceptionsQuery.error,
menus: {
user: userMenu,
provider: providerMenu,
},
}}
/>
</>
);
};

export default RequestLogsPage;
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
import{MockInterception}from"testHelpers/entities";
importtype{Meta,StoryObj}from"@storybook/react-vite";
import{
getDefaultFilterProps,
MockMenu,
}from"components/Filter/storyHelpers";
import{
mockInitialRenderResult,
mockSuccessResult,
}from"components/PaginationWidget/PaginationContainer.mocks";
importtype{UsePaginatedQueryResult}from"hooks/usePaginatedQuery";
importtype{ComponentProps}from"react";
import{RequestLogsPageView}from"./RequestLogsPageView";

typeFilterProps=ComponentProps<typeofRequestLogsPageView>["filterProps"];

constdefaultFilterProps=getDefaultFilterProps<FilterProps>({
query:"owner:me",
values:{
username:undefined,
provider:undefined,
},
menus:{
user:MockMenu,
provider:MockMenu,
},
});

constinterceptions=[MockInterception,MockInterception,MockInterception];

constmeta:Meta<typeofRequestLogsPageView>={
title:"pages/AIGovernancePage/RequestLogsPageView",
component:RequestLogsPageView,
args:{},
};

exportdefaultmeta;
typeStory=StoryObj<typeofRequestLogsPageView>;

exportconstPaywall:Story={
args:{
isRequestLogsVisible:false,
},
};

exportconstLoaded:Story={
args:{
isRequestLogsVisible:true,
interceptions,
filterProps:{
...defaultFilterProps,
},
interceptionsQuery:{
...mockSuccessResult,
totalRecords:interceptions.length,
data:{ interceptions,total:interceptions.length},
}asUsePaginatedQueryResult,
},
};

exportconstEmpty:Story={
args:{
isRequestLogsVisible:true,
interceptions:[],
filterProps:{
...defaultFilterProps,
},
interceptionsQuery:{
...mockSuccessResult,
totalRecords:0,
data:{interceptions:[],total:0},
}asUsePaginatedQueryResult,
},
};

exportconstLoading:Story={
args:{
isLoading:true,
isRequestLogsVisible:true,
interceptions:[],
filterProps:{
...defaultFilterProps,
},
interceptionsQuery:{
...mockInitialRenderResult,
}asUsePaginatedQueryResult,
},
};
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp