Skip to content

Commit d397e96

Browse files
committed
add ai to UI
1 parent 8855760 commit d397e96

File tree

3 files changed

+124
-2
lines changed

3 files changed

+124
-2
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {useTheme} from '@emotion/react';
2+
import color from 'color';
3+
import {motion} from 'framer-motion';
4+
5+
import {Button} from '@sentry/scraps/button';
6+
import {Container, Flex} from '@sentry/scraps/layout';
7+
import {TextArea} from '@sentry/scraps/textarea';
8+
9+
import testableTransition from 'sentry/utils/testableTransition';
10+
import {isChonkTheme} from 'sentry/utils/theme/withChonk';
11+
12+
interface AIChatProps {
13+
onClose: () => void;
14+
onSubmit: (message: string) => void;
15+
}
16+
17+
export function AIChat({onClose, onSubmit}: AIChatProps) {
18+
const theme = useTheme();
19+
20+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
21+
e.preventDefault();
22+
const formData = new FormData(e.target as HTMLFormElement);
23+
const message = formData.get('message') as string;
24+
onSubmit(message);
25+
};
26+
27+
return (
28+
<motion.div {...PANEL_TRANSITION}>
29+
<Container
30+
data-inspector-skip
31+
position="fixed"
32+
width="100%"
33+
maxWidth="560px"
34+
padding="lg"
35+
radius="md"
36+
border="primary"
37+
style={{
38+
bottom: '100px',
39+
left: '50%',
40+
transform: 'translateX(-50%)',
41+
backdropFilter: 'blur(10px)',
42+
backgroundColor: isChonkTheme(theme)
43+
? color(theme.tokens.background.primary).alpha(ALPHA).toString()
44+
: color(theme.background).alpha(ALPHA).toString(),
45+
}}
46+
>
47+
<form onSubmit={handleSubmit}>
48+
<Flex direction="column" gap="lg">
49+
<Container background="primary" radius="md">
50+
<TextArea
51+
rows={3}
52+
placeholder="You can just do things..."
53+
autosize
54+
style={{
55+
minBlockSize: '4lh !important',
56+
minInlineSize: '4lh !important',
57+
backgroundColor: isChonkTheme(theme)
58+
? theme.tokens.background.primary
59+
: theme.background,
60+
}}
61+
/>
62+
</Container>
63+
<Flex direction="row" gap="sm" justify="between">
64+
<Button priority="default" type="button" size="sm" onClick={onClose}>
65+
Close
66+
</Button>
67+
<Button priority="primary" type="submit" size="sm">
68+
Send
69+
</Button>
70+
</Flex>
71+
</Flex>
72+
</form>
73+
</Container>
74+
</motion.div>
75+
);
76+
}
77+
78+
const ALPHA = 0.65;
79+
80+
const PANEL_TRANSITION = {
81+
initial: {opacity: 0, y: 70},
82+
animate: {opacity: 1, y: 0},
83+
exit: {opacity: 0, y: 70},
84+
transition: testableTransition({
85+
type: 'spring',
86+
stiffness: 450,
87+
damping: 25,
88+
}),
89+
};

static/app/stories/inspector/claude.dev.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import * as WebSocket from 'ws';
1111
//
1212
// If an agent process crashes, the orchestrator will respawn it and retry the request, up to 3 times.
1313

14+
const SYSTEM_PROMPT = `
15+
You are a frontend development assistant. Developers talk to you through a chat interface exposed from their browser.
16+
Your task is to fulfill the user's request with minimal code changes that result in best user experience and most maintainable code.
17+
Do not assume that the user is familiar with the codebase at all. Provide detailed explanations of your actions and code changes.
18+
19+
Follow modern frontend development best practices and repository guidelines. Read AGENTS.md and follow it as best as you can.
20+
`;
21+
1422
type SessionId = string;
1523

1624
interface BaseClientMessage {
@@ -143,6 +151,8 @@ class ClaudeAgent extends Agent {
143151
'stream-json',
144152
'--verbose',
145153
'--dangerously-skip-permissions',
154+
'--append-system-prompt',
155+
SYSTEM_PROMPT,
146156
];
147157

148158
this.agent = spawn(command, args, {

static/app/stories/inspector/inspector.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {createPortal} from 'react-dom';
33
import {usePopper} from 'react-popper';
44
import {css, useTheme} from '@emotion/react';
55
import color from 'color';
6+
import {AnimatePresence} from 'framer-motion';
67

78
import {addSuccessMessage} from 'sentry/actionCreators/indicator';
89
import {Tag} from 'sentry/components/core/badge/tag';
@@ -19,6 +20,7 @@ import {
1920
} from 'sentry/components/profiling/profilingContextMenu';
2021
import {NODE_ENV} from 'sentry/constants';
2122
import {IconChevron, IconCopy, IconDocs, IconLink, IconOpen} from 'sentry/icons';
23+
import {IconBot} from 'sentry/icons/iconBot';
2224
import {t} from 'sentry/locale';
2325
import {
2426
isMDXStory,
@@ -31,6 +33,7 @@ import {useHotkeys} from 'sentry/utils/useHotkeys';
3133
import useOrganization from 'sentry/utils/useOrganization';
3234
import {useUser} from 'sentry/utils/useUser';
3335

36+
import {AIChat} from './ai-overlay';
3437
import {
3538
getComponentName,
3639
getComponentStorybookFile,
@@ -321,7 +324,7 @@ export function SentryComponentInspector() {
321324
}
322325

323326
return createPortal(
324-
<Fragment>
327+
<AnimatePresence>
325328
{state.enabled === 'inspector' ? (
326329
<Overlay
327330
ref={tooltipRef}
@@ -411,6 +414,12 @@ export function SentryComponentInspector() {
411414
sourcePath={sourcePath}
412415
el={el}
413416
storybook={getComponentStorybookFile(el, storybookFilesLookup)}
417+
onAIAssistant={() => {
418+
setState(prev => ({
419+
...prev,
420+
enabled: 'ai-assistant',
421+
}));
422+
}}
414423
onAction={() => {
415424
contextMenu.setOpen(false);
416425

@@ -435,6 +444,11 @@ export function SentryComponentInspector() {
435444
id="sub-menu-portal"
436445
/>
437446
</Fragment>
447+
) : state.enabled === 'ai-assistant' ? (
448+
<AIChat
449+
onClose={() => setState(prev => ({...prev, enabled: 'inspector'}))}
450+
onSubmit={() => {}}
451+
/>
438452
) : null}
439453
{state.enabled === null ? null : (
440454
<style>
@@ -465,7 +479,7 @@ export function SentryComponentInspector() {
465479
`}
466480
</style>
467481
)}
468-
</Fragment>,
482+
</AnimatePresence>,
469483
document.body
470484
);
471485
}
@@ -474,6 +488,7 @@ function MenuItem(props: {
474488
componentName: string;
475489
contextMenu: ReturnType<typeof useContextMenu>;
476490
el: TraceElement;
491+
onAIAssistant: () => void;
477492
onAction: () => void;
478493
sourcePath: string;
479494
storybook: string | null;
@@ -607,6 +622,14 @@ function MenuItem(props: {
607622
{t('View Storybook')}
608623
</ProfilingContextMenuItemButton>
609624
) : null}
625+
<ProfilingContextMenuItemButton
626+
{...props.contextMenu.getMenuItemProps({
627+
onClick: props.onAIAssistant,
628+
})}
629+
icon={<IconBot size="xs" />}
630+
>
631+
{t('Edit with AI Assistant')}
632+
</ProfilingContextMenuItemButton>
610633
<ProfilingContextMenuItemButton
611634
{...props.contextMenu.getMenuItemProps({
612635
onClick: () => {

0 commit comments

Comments
 (0)