akseljoonas HF Staff commited on
Commit
fe018ce
·
1 Parent(s): 7e02504

removed avatars and made ui improvements

Browse files
agent/core/agent_loop.py CHANGED
@@ -147,8 +147,6 @@ class Handlers:
147
  while iteration < max_iterations:
148
  messages = session.context_manager.get_messages()
149
  tools = session.tool_router.get_tool_specs_for_llm()
150
- logger.info(f"_INFERENCE_API_KEY {_INFERENCE_API_KEY} {session.config.model_name.startswith('huggingface/')}")
151
-
152
  try:
153
  # ── Stream the LLM response ──────────────────────────
154
  response = await acompletion(
 
147
  while iteration < max_iterations:
148
  messages = session.context_manager.get_messages()
149
  tools = session.tool_router.get_tool_specs_for_llm()
 
 
150
  try:
151
  # ── Stream the LLM response ──────────────────────────
152
  response = await acompletion(
frontend/src/components/Chat/AssistantMessage.tsx CHANGED
@@ -1,5 +1,4 @@
1
- import { Box, Stack, Avatar, Typography } from '@mui/material';
2
- import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
3
  import MarkdownContent from './MarkdownContent';
4
  import ToolCallGroup from './ToolCallGroup';
5
  import type { Message } from '@/types/agent';
@@ -48,61 +47,47 @@ export default function AssistantMessage({ message, isStreaming = false }: Assis
48
  };
49
 
50
  return (
51
- <Stack direction="row" spacing={1.5} alignItems="flex-start">
52
- <Avatar
53
- sx={{
54
- width: 28,
55
- height: 28,
56
- bgcolor: 'primary.main',
57
- flexShrink: 0,
58
- mt: 0.5,
59
- }}
60
- >
61
- <SmartToyOutlinedIcon sx={{ fontSize: 16, color: '#fff' }} />
62
- </Avatar>
63
-
64
- <Box sx={{ flex: 1, minWidth: 0 }}>
65
- {/* Role label + timestamp */}
66
- <Stack direction="row" alignItems="baseline" spacing={1} sx={{ mb: 0.5 }}>
67
- <Typography
68
- variant="caption"
69
- sx={{
70
- fontWeight: 700,
71
- fontSize: '0.72rem',
72
- color: 'var(--muted-text)',
73
- textTransform: 'uppercase',
74
- letterSpacing: '0.04em',
75
- }}
76
- >
77
- Assistant
78
- </Typography>
79
- <Typography
80
- variant="caption"
81
- sx={{
82
- fontSize: '0.66rem',
83
- color: 'var(--muted-text)',
84
- opacity: 0.6,
85
- }}
86
- >
87
- {new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
88
- </Typography>
89
- </Stack>
90
-
91
- {/* Message bubble */}
92
- <Box
93
  sx={{
94
- maxWidth: { xs: '95%', md: '85%' },
95
- bgcolor: 'var(--surface)',
96
- borderRadius: 1.5,
97
- borderTopLeftRadius: 4,
98
- px: { xs: 1.5, md: 2.5 },
99
- py: 1.5,
100
- border: '1px solid var(--border)',
101
  }}
102
  >
103
- {renderSegments()}
104
- </Box>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </Box>
106
- </Stack>
107
  );
108
  }
 
1
+ import { Box, Stack, Typography } from '@mui/material';
 
2
  import MarkdownContent from './MarkdownContent';
3
  import ToolCallGroup from './ToolCallGroup';
4
  import type { Message } from '@/types/agent';
 
47
  };
48
 
49
  return (
50
+ <Box sx={{ minWidth: 0 }}>
51
+ {/* Role label + timestamp */}
52
+ <Stack direction="row" alignItems="baseline" spacing={1} sx={{ mb: 0.5 }}>
53
+ <Typography
54
+ variant="caption"
55
+ sx={{
56
+ fontWeight: 700,
57
+ fontSize: '0.72rem',
58
+ color: 'var(--muted-text)',
59
+ textTransform: 'uppercase',
60
+ letterSpacing: '0.04em',
61
+ }}
62
+ >
63
+ Assistant
64
+ </Typography>
65
+ <Typography
66
+ variant="caption"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  sx={{
68
+ fontSize: '0.66rem',
69
+ color: 'var(--muted-text)',
70
+ opacity: 0.6,
 
 
 
 
71
  }}
72
  >
73
+ {new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
74
+ </Typography>
75
+ </Stack>
76
+
77
+ {/* Message bubble */}
78
+ <Box
79
+ sx={{
80
+ maxWidth: { xs: '95%', md: '85%' },
81
+ bgcolor: 'var(--surface)',
82
+ borderRadius: 1.5,
83
+ borderTopLeftRadius: 4,
84
+ px: { xs: 1.5, md: 2.5 },
85
+ py: 1.5,
86
+ border: '1px solid var(--border)',
87
+ }}
88
+ >
89
+ {renderSegments()}
90
  </Box>
91
+ </Box>
92
  );
93
  }
frontend/src/components/Chat/MarkdownContent.tsx CHANGED
@@ -94,26 +94,6 @@ const markdownSx: SxProps<Theme> = {
94
  },
95
  };
96
 
97
- /** Blinking cursor shown at the end of streaming text. */
98
- const StreamingCursor = () => (
99
- <Box
100
- component="span"
101
- sx={{
102
- display: 'inline-block',
103
- width: '2px',
104
- height: '1.1em',
105
- bgcolor: 'var(--text)',
106
- ml: '2px',
107
- verticalAlign: 'text-bottom',
108
- animation: 'cursorBlink 1s step-end infinite',
109
- '@keyframes cursorBlink': {
110
- '0%, 100%': { opacity: 1 },
111
- '50%': { opacity: 0 },
112
- },
113
- }}
114
- />
115
- );
116
-
117
  /**
118
  * Throttled content for streaming: render the full markdown through
119
  * ReactMarkdown but only re-parse every ~80ms to avoid layout thrashing.
@@ -175,7 +155,6 @@ export default function MarkdownContent({ content, sx, isStreaming = false }: Ma
175
  return (
176
  <Box sx={[markdownSx, ...(Array.isArray(sx) ? sx : sx ? [sx] : [])]}>
177
  <ReactMarkdown remarkPlugins={remarkPlugins}>{displayContent}</ReactMarkdown>
178
- {isStreaming && <StreamingCursor />}
179
  </Box>
180
  );
181
  }
 
94
  },
95
  };
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  /**
98
  * Throttled content for streaming: render the full markdown through
99
  * ReactMarkdown but only re-parse every ~80ms to avoid layout thrashing.
 
155
  return (
156
  <Box sx={[markdownSx, ...(Array.isArray(sx) ? sx : sx ? [sx] : [])]}>
157
  <ReactMarkdown remarkPlugins={remarkPlugins}>{displayContent}</ReactMarkdown>
 
158
  </Box>
159
  );
160
  }
frontend/src/components/Chat/UserMessage.tsx CHANGED
@@ -1,5 +1,4 @@
1
- import { Box, Stack, Typography, Avatar, IconButton, Tooltip } from '@mui/material';
2
- import PersonOutlineIcon from '@mui/icons-material/PersonOutline';
3
  import CloseIcon from '@mui/icons-material/Close';
4
  import type { Message } from '@/types/agent';
5
 
@@ -99,19 +98,6 @@ export default function UserMessage({
99
  {new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
100
  </Typography>
101
  </Box>
102
-
103
- <Avatar
104
- sx={{
105
- width: 28,
106
- height: 28,
107
- bgcolor: 'var(--hover-bg)',
108
- border: '1px solid var(--border)',
109
- flexShrink: 0,
110
- mt: 0.5,
111
- }}
112
- >
113
- <PersonOutlineIcon sx={{ fontSize: 16, color: 'var(--muted-text)' }} />
114
- </Avatar>
115
  </Stack>
116
  );
117
  }
 
1
+ import { Box, Stack, Typography, IconButton, Tooltip } from '@mui/material';
 
2
  import CloseIcon from '@mui/icons-material/Close';
3
  import type { Message } from '@/types/agent';
4
 
 
98
  {new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
99
  </Typography>
100
  </Box>
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  </Stack>
102
  );
103
  }
frontend/src/components/CodePanel/CodePanel.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import { useRef, useEffect, useMemo } from 'react';
2
- import { Box, Stack, Typography, IconButton } from '@mui/material';
3
  import CloseIcon from '@mui/icons-material/Close';
4
  import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
5
  import CheckCircleIcon from '@mui/icons-material/CheckCircle';
@@ -7,6 +7,10 @@ import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
7
  import CodeIcon from '@mui/icons-material/Code';
8
  import TerminalIcon from '@mui/icons-material/Terminal';
9
  import ArticleIcon from '@mui/icons-material/Article';
 
 
 
 
10
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
11
  import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
12
  import ReactMarkdown from 'react-markdown';
@@ -92,10 +96,15 @@ const markdownSx = {
92
  // ── Component ────────────────────────────────────────────────────
93
 
94
  export default function CodePanel() {
95
- const { panelContent, panelTabs, activePanelTab, setActivePanelTab, removePanelTab, plan } =
96
  useAgentStore();
97
  const { setRightPanelOpen, themeMode } = useLayoutStore();
98
  const scrollRef = useRef<HTMLDivElement>(null);
 
 
 
 
 
99
 
100
  const activeTab = panelTabs.find((t) => t.id === activePanelTab);
101
  const currentContent = activeTab || panelContent;
@@ -104,6 +113,66 @@ export default function CodePanel() {
104
  const isDark = themeMode === 'dark';
105
  const syntaxTheme = isDark ? vscDarkPlus : vs;
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  const displayContent = useMemo(() => {
108
  if (!currentContent?.content) return '';
109
  if (!currentContent.language || currentContent.language === 'text') {
@@ -147,6 +216,30 @@ export default function CodePanel() {
147
  );
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  if (currentContent.language === 'python') return renderSyntaxBlock('python');
151
  if (currentContent.language === 'json') return renderSyntaxBlock('json');
152
 
@@ -247,9 +340,94 @@ export default function CodePanel() {
247
  </Typography>
248
  )}
249
 
250
- <IconButton size="small" onClick={() => setRightPanelOpen(false)} sx={{ color: 'var(--muted-text)' }}>
251
- <CloseIcon fontSize="small" />
252
- </IconButton>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  </Box>
254
 
255
  {/* ── Main content area ─────────────────────────────────── */}
 
1
+ import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
2
+ import { Box, Stack, Typography, IconButton, Button, Tooltip } from '@mui/material';
3
  import CloseIcon from '@mui/icons-material/Close';
4
  import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
5
  import CheckCircleIcon from '@mui/icons-material/CheckCircle';
 
7
  import CodeIcon from '@mui/icons-material/Code';
8
  import TerminalIcon from '@mui/icons-material/Terminal';
9
  import ArticleIcon from '@mui/icons-material/Article';
10
+ import EditIcon from '@mui/icons-material/Edit';
11
+ import UndoIcon from '@mui/icons-material/Undo';
12
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
13
+ import CheckIcon from '@mui/icons-material/Check';
14
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
15
  import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
16
  import ReactMarkdown from 'react-markdown';
 
96
  // ── Component ────────────────────────────────────────────────────
97
 
98
  export default function CodePanel() {
99
+ const { panelContent, panelTabs, activePanelTab, setActivePanelTab, removePanelTab, plan, updatePanelTabContent, setEditedScript } =
100
  useAgentStore();
101
  const { setRightPanelOpen, themeMode } = useLayoutStore();
102
  const scrollRef = useRef<HTMLDivElement>(null);
103
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
104
+ const [isEditing, setIsEditing] = useState(false);
105
+ const [editedContent, setEditedContent] = useState('');
106
+ const [originalContent, setOriginalContent] = useState('');
107
+ const [copied, setCopied] = useState(false);
108
 
109
  const activeTab = panelTabs.find((t) => t.id === activePanelTab);
110
  const currentContent = activeTab || panelContent;
 
113
  const isDark = themeMode === 'dark';
114
  const syntaxTheme = isDark ? vscDarkPlus : vs;
115
 
116
+ // Check if this is an editable script tab
117
+ const isEditableScript = activeTab?.id === 'script' && activeTab?.language === 'python';
118
+ const hasUnsavedChanges = isEditing && editedContent !== originalContent;
119
+
120
+ // Sync edited content when switching tabs or content changes
121
+ useEffect(() => {
122
+ if (currentContent?.content && isEditableScript) {
123
+ setOriginalContent(currentContent.content);
124
+ if (!isEditing) {
125
+ setEditedContent(currentContent.content);
126
+ }
127
+ }
128
+ }, [currentContent?.content, isEditableScript, isEditing]);
129
+
130
+ // Exit editing when switching away from script tab
131
+ useEffect(() => {
132
+ if (!isEditableScript && isEditing) {
133
+ setIsEditing(false);
134
+ }
135
+ }, [isEditableScript, isEditing]);
136
+
137
+ const handleStartEdit = useCallback(() => {
138
+ if (currentContent?.content) {
139
+ setEditedContent(currentContent.content);
140
+ setOriginalContent(currentContent.content);
141
+ setIsEditing(true);
142
+ setTimeout(() => textareaRef.current?.focus(), 0);
143
+ }
144
+ }, [currentContent?.content]);
145
+
146
+ const handleCancelEdit = useCallback(() => {
147
+ setEditedContent(originalContent);
148
+ setIsEditing(false);
149
+ }, [originalContent]);
150
+
151
+ const handleSaveEdit = useCallback(() => {
152
+ if (activeTab && editedContent !== originalContent) {
153
+ updatePanelTabContent(activeTab.id, editedContent);
154
+ const toolCallId = activeTab.parameters?.tool_call_id as string | undefined;
155
+ if (toolCallId) {
156
+ setEditedScript(toolCallId, editedContent);
157
+ }
158
+ setOriginalContent(editedContent);
159
+ }
160
+ setIsEditing(false);
161
+ }, [activeTab, editedContent, originalContent, updatePanelTabContent, setEditedScript]);
162
+
163
+ const handleCopy = useCallback(async () => {
164
+ const contentToCopy = isEditing ? editedContent : (currentContent?.content || '');
165
+ if (contentToCopy) {
166
+ try {
167
+ await navigator.clipboard.writeText(contentToCopy);
168
+ setCopied(true);
169
+ setTimeout(() => setCopied(false), 2000);
170
+ } catch (err) {
171
+ console.error('Failed to copy:', err);
172
+ }
173
+ }
174
+ }, [isEditing, editedContent, currentContent?.content]);
175
+
176
  const displayContent = useMemo(() => {
177
  if (!currentContent?.content) return '';
178
  if (!currentContent.language || currentContent.language === 'text') {
 
216
  );
217
  }
218
 
219
+ // Editing mode: show textarea
220
+ if (isEditing && isEditableScript) {
221
+ return (
222
+ <textarea
223
+ ref={textareaRef}
224
+ value={editedContent}
225
+ onChange={(e) => setEditedContent(e.target.value)}
226
+ spellCheck={false}
227
+ style={{
228
+ width: '100%',
229
+ height: '100%',
230
+ background: 'transparent',
231
+ border: 'none',
232
+ outline: 'none',
233
+ resize: 'none',
234
+ color: 'var(--text)',
235
+ fontFamily: '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace',
236
+ fontSize: '13px',
237
+ lineHeight: 1.55,
238
+ }}
239
+ />
240
+ );
241
+ }
242
+
243
  if (currentContent.language === 'python') return renderSyntaxBlock('python');
244
  if (currentContent.language === 'json') return renderSyntaxBlock('json');
245
 
 
340
  </Typography>
341
  )}
342
 
343
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
344
+ {/* Copy button */}
345
+ {currentContent?.content && (
346
+ <Tooltip title={copied ? 'Copied!' : 'Copy'} placement="top">
347
+ <IconButton
348
+ size="small"
349
+ onClick={handleCopy}
350
+ sx={{
351
+ color: copied ? 'var(--accent-green)' : 'var(--muted-text)',
352
+ '&:hover': {
353
+ color: 'var(--accent-yellow)',
354
+ bgcolor: 'var(--hover-bg)',
355
+ },
356
+ }}
357
+ >
358
+ {copied ? <CheckIcon sx={{ fontSize: 18 }} /> : <ContentCopyIcon sx={{ fontSize: 18 }} />}
359
+ </IconButton>
360
+ </Tooltip>
361
+ )}
362
+ {/* Edit controls for script tab */}
363
+ {isEditableScript && !isEditing && (
364
+ <Button
365
+ size="small"
366
+ startIcon={<EditIcon sx={{ fontSize: 14 }} />}
367
+ onClick={handleStartEdit}
368
+ sx={{
369
+ textTransform: 'none',
370
+ color: 'var(--muted-text)',
371
+ fontSize: '0.75rem',
372
+ py: 0.5,
373
+ '&:hover': {
374
+ color: 'var(--accent-yellow)',
375
+ bgcolor: 'var(--hover-bg)',
376
+ },
377
+ }}
378
+ >
379
+ Edit
380
+ </Button>
381
+ )}
382
+ {isEditing && (
383
+ <>
384
+ <Button
385
+ size="small"
386
+ startIcon={<UndoIcon sx={{ fontSize: 14 }} />}
387
+ onClick={handleCancelEdit}
388
+ sx={{
389
+ textTransform: 'none',
390
+ color: 'var(--muted-text)',
391
+ fontSize: '0.75rem',
392
+ py: 0.5,
393
+ '&:hover': {
394
+ color: 'var(--accent-red)',
395
+ bgcolor: 'var(--hover-bg)',
396
+ },
397
+ }}
398
+ >
399
+ Cancel
400
+ </Button>
401
+ <Button
402
+ size="small"
403
+ variant="contained"
404
+ onClick={handleSaveEdit}
405
+ disabled={!hasUnsavedChanges}
406
+ sx={{
407
+ textTransform: 'none',
408
+ fontSize: '0.75rem',
409
+ py: 0.5,
410
+ bgcolor: hasUnsavedChanges ? 'var(--accent-green)' : 'var(--hover-bg)',
411
+ color: hasUnsavedChanges ? '#000' : 'var(--muted-text)',
412
+ '&:hover': {
413
+ bgcolor: hasUnsavedChanges ? 'var(--accent-green)' : 'var(--hover-bg)',
414
+ opacity: 0.9,
415
+ },
416
+ '&.Mui-disabled': {
417
+ bgcolor: 'var(--hover-bg)',
418
+ color: 'var(--muted-text)',
419
+ opacity: 0.5,
420
+ },
421
+ }}
422
+ >
423
+ Save
424
+ </Button>
425
+ </>
426
+ )}
427
+ <IconButton size="small" onClick={() => setRightPanelOpen(false)} sx={{ color: 'var(--muted-text)' }}>
428
+ <CloseIcon fontSize="small" />
429
+ </IconButton>
430
+ </Box>
431
  </Box>
432
 
433
  {/* ── Main content area ─────────────────────────────────── */}
frontend/src/components/SessionSidebar/SessionSidebar.tsx CHANGED
@@ -130,7 +130,7 @@ export default function SessionSidebar({ onClose }: SessionSidebarProps) {
130
  fontSize: '0.7rem',
131
  py: 0.25,
132
  '& .MuiAlert-message': { py: 0 },
133
- borderColor: '#FF9D00',
134
  color: 'var(--text)',
135
  }}
136
  >
@@ -296,7 +296,7 @@ export default function SessionSidebar({ onClose }: SessionSidebarProps) {
296
  py: 1.25,
297
  border: 'none',
298
  borderRadius: '10px',
299
- bgcolor: '#FF9D00',
300
  color: '#000',
301
  fontSize: '0.85rem',
302
  fontWeight: 700,
 
130
  fontSize: '0.7rem',
131
  py: 0.25,
132
  '& .MuiAlert-message': { py: 0 },
133
+ borderColor: '#C7A500',
134
  color: 'var(--text)',
135
  }}
136
  >
 
296
  py: 1.25,
297
  border: 'none',
298
  borderRadius: '10px',
299
+ bgcolor: '#C7A500',
300
  color: '#000',
301
  fontSize: '0.85rem',
302
  fontWeight: 700,
frontend/src/components/WelcomeScreen/WelcomeScreen.tsx CHANGED
@@ -12,8 +12,8 @@ import { useAgentStore } from '@/store/agentStore';
12
  import { apiFetch } from '@/utils/api';
13
  import { isInIframe, triggerLogin } from '@/hooks/useAuth';
14
 
15
- /** HF brand orange */
16
- const HF_ORANGE = '#FF9D00';
17
 
18
  export default function WelcomeScreen() {
19
  const { createSession } = useSessionStore();
@@ -149,11 +149,11 @@ export default function WelcomeScreen() {
149
  borderRadius: '12px',
150
  bgcolor: HF_ORANGE,
151
  color: '#000',
152
- boxShadow: '0 4px 24px rgba(255, 157, 0, 0.3)',
153
  textDecoration: 'none',
154
  '&:hover': {
155
  bgcolor: '#FFB340',
156
- boxShadow: '0 6px 32px rgba(255, 157, 0, 0.45)',
157
  },
158
  }}
159
  >
@@ -174,10 +174,10 @@ export default function WelcomeScreen() {
174
  borderRadius: '12px',
175
  bgcolor: HF_ORANGE,
176
  color: '#000',
177
- boxShadow: '0 4px 24px rgba(255, 157, 0, 0.3)',
178
  '&:hover': {
179
  bgcolor: '#FFB340',
180
- boxShadow: '0 6px 32px rgba(255, 157, 0, 0.45)',
181
  },
182
  }}
183
  >
@@ -202,13 +202,13 @@ export default function WelcomeScreen() {
202
  borderRadius: '12px',
203
  bgcolor: HF_ORANGE,
204
  color: '#000',
205
- boxShadow: '0 4px 24px rgba(255, 157, 0, 0.3)',
206
  '&:hover': {
207
  bgcolor: '#FFB340',
208
- boxShadow: '0 6px 32px rgba(255, 157, 0, 0.45)',
209
  },
210
  '&.Mui-disabled': {
211
- bgcolor: 'rgba(255, 157, 0, 0.35)',
212
  color: 'rgba(0,0,0,0.45)',
213
  },
214
  }}
 
12
  import { apiFetch } from '@/utils/api';
13
  import { isInIframe, triggerLogin } from '@/hooks/useAuth';
14
 
15
+ /** HF brand gold */
16
+ const HF_ORANGE = '#C7A500';
17
 
18
  export default function WelcomeScreen() {
19
  const { createSession } = useSessionStore();
 
149
  borderRadius: '12px',
150
  bgcolor: HF_ORANGE,
151
  color: '#000',
152
+ boxShadow: '0 4px 24px rgba(199, 165, 0, 0.3)',
153
  textDecoration: 'none',
154
  '&:hover': {
155
  bgcolor: '#FFB340',
156
+ boxShadow: '0 6px 32px rgba(199, 165, 0, 0.45)',
157
  },
158
  }}
159
  >
 
174
  borderRadius: '12px',
175
  bgcolor: HF_ORANGE,
176
  color: '#000',
177
+ boxShadow: '0 4px 24px rgba(199, 165, 0, 0.3)',
178
  '&:hover': {
179
  bgcolor: '#FFB340',
180
+ boxShadow: '0 6px 32px rgba(199, 165, 0, 0.45)',
181
  },
182
  }}
183
  >
 
202
  borderRadius: '12px',
203
  bgcolor: HF_ORANGE,
204
  color: '#000',
205
+ boxShadow: '0 4px 24px rgba(199, 165, 0, 0.3)',
206
  '&:hover': {
207
  bgcolor: '#FFB340',
208
+ boxShadow: '0 6px 32px rgba(199, 165, 0, 0.45)',
209
  },
210
  '&.Mui-disabled': {
211
+ bgcolor: 'rgba(199, 165, 0, 0.35)',
212
  color: 'rgba(0,0,0,0.45)',
213
  },
214
  }}
frontend/src/store/agentStore.ts CHANGED
@@ -36,6 +36,7 @@ interface AgentStore {
36
  activePanelTab: string | null;
37
  plan: PlanItem[];
38
  currentTurnMessageId: string | null; // Track the current turn's assistant message
 
39
 
40
  // Actions
41
  addMessage: (sessionId: string, message: Message) => void;
@@ -51,6 +52,7 @@ interface AgentStore {
51
  clearTraceLogs: () => void;
52
  setPanelContent: (content: { title: string; content: string; language?: string; parameters?: Record<string, unknown> } | null) => void;
53
  setPanelTab: (tab: PanelTab) => void;
 
54
  setActivePanelTab: (tabId: string) => void;
55
  clearPanelTabs: () => void;
56
  removePanelTab: (tabId: string) => void;
@@ -58,6 +60,9 @@ interface AgentStore {
58
  setCurrentTurnMessageId: (id: string | null) => void;
59
  updateCurrentTurnTrace: (sessionId: string) => void;
60
  showToolOutput: (log: TraceLog) => void;
 
 
 
61
  /** Append a streaming delta to an existing message. */
62
  appendToMessage: (sessionId: string, messageId: string, delta: string) => void;
63
  /** Remove all messages for a session (also clears from localStorage). */
@@ -82,6 +87,7 @@ export const useAgentStore = create<AgentStore>()(
82
  activePanelTab: null,
83
  plan: [],
84
  currentTurnMessageId: null,
 
85
 
86
  addMessage: (sessionId: string, message: Message) => {
87
  set((state) => {
@@ -199,6 +205,15 @@ export const useAgentStore = create<AgentStore>()(
199
  });
200
  },
201
 
 
 
 
 
 
 
 
 
 
202
  setActivePanelTab: (tabId: string) => {
203
  set({ activePanelTab: tabId });
204
  },
@@ -301,6 +316,20 @@ export const useAgentStore = create<AgentStore>()(
301
  });
302
  },
303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  appendToMessage: (sessionId: string, messageId: string, delta: string) => {
305
  set((state) => {
306
  const messages = state.messagesBySession[sessionId] || [];
 
36
  activePanelTab: string | null;
37
  plan: PlanItem[];
38
  currentTurnMessageId: string | null; // Track the current turn's assistant message
39
+ editedScripts: Record<string, string>; // tool_call_id -> edited content
40
 
41
  // Actions
42
  addMessage: (sessionId: string, message: Message) => void;
 
52
  clearTraceLogs: () => void;
53
  setPanelContent: (content: { title: string; content: string; language?: string; parameters?: Record<string, unknown> } | null) => void;
54
  setPanelTab: (tab: PanelTab) => void;
55
+ updatePanelTabContent: (tabId: string, content: string) => void;
56
  setActivePanelTab: (tabId: string) => void;
57
  clearPanelTabs: () => void;
58
  removePanelTab: (tabId: string) => void;
 
60
  setCurrentTurnMessageId: (id: string | null) => void;
61
  updateCurrentTurnTrace: (sessionId: string) => void;
62
  showToolOutput: (log: TraceLog) => void;
63
+ setEditedScript: (toolCallId: string, content: string) => void;
64
+ getEditedScript: (toolCallId: string) => string | undefined;
65
+ clearEditedScripts: () => void;
66
  /** Append a streaming delta to an existing message. */
67
  appendToMessage: (sessionId: string, messageId: string, delta: string) => void;
68
  /** Remove all messages for a session (also clears from localStorage). */
 
87
  activePanelTab: null,
88
  plan: [],
89
  currentTurnMessageId: null,
90
+ editedScripts: {},
91
 
92
  addMessage: (sessionId: string, message: Message) => {
93
  set((state) => {
 
205
  });
206
  },
207
 
208
+ updatePanelTabContent: (tabId: string, content: string) => {
209
+ set((state) => {
210
+ const newTabs = state.panelTabs.map(tab =>
211
+ tab.id === tabId ? { ...tab, content } : tab
212
+ );
213
+ return { panelTabs: newTabs };
214
+ });
215
+ },
216
+
217
  setActivePanelTab: (tabId: string) => {
218
  set({ activePanelTab: tabId });
219
  },
 
316
  });
317
  },
318
 
319
+ setEditedScript: (toolCallId: string, content: string) => {
320
+ set((state) => ({
321
+ editedScripts: { ...state.editedScripts, [toolCallId]: content },
322
+ }));
323
+ },
324
+
325
+ getEditedScript: (toolCallId: string) => {
326
+ return get().editedScripts[toolCallId];
327
+ },
328
+
329
+ clearEditedScripts: () => {
330
+ set({ editedScripts: {} });
331
+ },
332
+
333
  appendToMessage: (sessionId: string, messageId: string, delta: string) => {
334
  set((state) => {
335
  const messages = state.messagesBySession[sessionId] || [];
frontend/src/theme.ts CHANGED
@@ -38,14 +38,14 @@ const darkVars = {
38
  '--surface': '#121416',
39
  '--text': '#E6EEF8',
40
  '--muted-text': '#98A0AA',
41
- '--accent-yellow': '#FF9D00',
42
- '--accent-yellow-weak': 'rgba(255,157,0,0.08)',
43
  '--accent-green': '#2FCC71',
44
  '--accent-red': '#E05A4F',
45
  '--shadow-1': '0 6px 18px rgba(2,6,12,0.55)',
46
  '--radius-lg': '20px',
47
  '--radius-md': '12px',
48
- '--focus': '0 0 0 3px rgba(255,157,0,0.12)',
49
  '--border': 'rgba(255,255,255,0.03)',
50
  '--border-hover': 'rgba(255,255,255,0.1)',
51
  '--code-bg': 'rgba(0,0,0,0.5)',
@@ -75,14 +75,14 @@ const lightVars = {
75
  '--surface': '#F0F1F3',
76
  '--text': '#1A1A2E',
77
  '--muted-text': '#6B7280',
78
- '--accent-yellow': '#FF9D00',
79
- '--accent-yellow-weak': 'rgba(255,157,0,0.08)',
80
  '--accent-green': '#16A34A',
81
  '--accent-red': '#DC2626',
82
  '--shadow-1': '0 4px 12px rgba(0,0,0,0.08)',
83
  '--radius-lg': '20px',
84
  '--radius-md': '12px',
85
- '--focus': '0 0 0 3px rgba(255,157,0,0.15)',
86
  '--border': 'rgba(0,0,0,0.08)',
87
  '--border-hover': 'rgba(0,0,0,0.15)',
88
  '--code-bg': 'rgba(0,0,0,0.04)',
@@ -93,7 +93,7 @@ const lightVars = {
93
  '--msg-gradient': 'linear-gradient(180deg, rgba(0,0,0,0.01), transparent)',
94
  '--body-gradient': 'linear-gradient(180deg, #FFFFFF, #F7F8FA)',
95
  '--scrollbar-thumb': '#C4C8CC',
96
- '--success-icon': '#FF9D00',
97
  '--error-icon': '#DC2626',
98
  '--clickable-text': 'rgba(0, 0, 0, 0.85)',
99
  '--clickable-underline': 'rgba(0,0,0,0.25)',
@@ -176,14 +176,14 @@ function makeTextField() {
176
  export const darkTheme = createTheme({
177
  palette: {
178
  mode: 'dark',
179
- primary: { main: '#FF9D00' },
180
  secondary: { main: '#C7A500' },
181
  background: { default: '#0B0D10', paper: '#0F1316' },
182
  text: { primary: '#E6EEF8', secondary: '#98A0AA' },
183
  divider: 'rgba(255,255,255,0.03)',
184
  success: { main: '#2FCC71' },
185
  error: { main: '#E05A4F' },
186
- warning: { main: '#FF9D00' },
187
  info: { main: '#58A6FF' },
188
  },
189
  typography: sharedTypography,
@@ -199,14 +199,14 @@ export const darkTheme = createTheme({
199
  export const lightTheme = createTheme({
200
  palette: {
201
  mode: 'light',
202
- primary: { main: '#FF9D00' },
203
  secondary: { main: '#B8960A' },
204
  background: { default: '#FFFFFF', paper: '#F7F8FA' },
205
  text: { primary: '#1A1A2E', secondary: '#6B7280' },
206
  divider: 'rgba(0,0,0,0.08)',
207
  success: { main: '#16A34A' },
208
  error: { main: '#DC2626' },
209
- warning: { main: '#FF9D00' },
210
  info: { main: '#2563EB' },
211
  },
212
  typography: sharedTypography,
 
38
  '--surface': '#121416',
39
  '--text': '#E6EEF8',
40
  '--muted-text': '#98A0AA',
41
+ '--accent-yellow': '#C7A500',
42
+ '--accent-yellow-weak': 'rgba(199,165,0,0.08)',
43
  '--accent-green': '#2FCC71',
44
  '--accent-red': '#E05A4F',
45
  '--shadow-1': '0 6px 18px rgba(2,6,12,0.55)',
46
  '--radius-lg': '20px',
47
  '--radius-md': '12px',
48
+ '--focus': '0 0 0 3px rgba(199,165,0,0.12)',
49
  '--border': 'rgba(255,255,255,0.03)',
50
  '--border-hover': 'rgba(255,255,255,0.1)',
51
  '--code-bg': 'rgba(0,0,0,0.5)',
 
75
  '--surface': '#F0F1F3',
76
  '--text': '#1A1A2E',
77
  '--muted-text': '#6B7280',
78
+ '--accent-yellow': '#C7A500',
79
+ '--accent-yellow-weak': 'rgba(199,165,0,0.08)',
80
  '--accent-green': '#16A34A',
81
  '--accent-red': '#DC2626',
82
  '--shadow-1': '0 4px 12px rgba(0,0,0,0.08)',
83
  '--radius-lg': '20px',
84
  '--radius-md': '12px',
85
+ '--focus': '0 0 0 3px rgba(199,165,0,0.15)',
86
  '--border': 'rgba(0,0,0,0.08)',
87
  '--border-hover': 'rgba(0,0,0,0.15)',
88
  '--code-bg': 'rgba(0,0,0,0.04)',
 
93
  '--msg-gradient': 'linear-gradient(180deg, rgba(0,0,0,0.01), transparent)',
94
  '--body-gradient': 'linear-gradient(180deg, #FFFFFF, #F7F8FA)',
95
  '--scrollbar-thumb': '#C4C8CC',
96
+ '--success-icon': '#C7A500',
97
  '--error-icon': '#DC2626',
98
  '--clickable-text': 'rgba(0, 0, 0, 0.85)',
99
  '--clickable-underline': 'rgba(0,0,0,0.25)',
 
176
  export const darkTheme = createTheme({
177
  palette: {
178
  mode: 'dark',
179
+ primary: { main: '#C7A500' },
180
  secondary: { main: '#C7A500' },
181
  background: { default: '#0B0D10', paper: '#0F1316' },
182
  text: { primary: '#E6EEF8', secondary: '#98A0AA' },
183
  divider: 'rgba(255,255,255,0.03)',
184
  success: { main: '#2FCC71' },
185
  error: { main: '#E05A4F' },
186
+ warning: { main: '#C7A500' },
187
  info: { main: '#58A6FF' },
188
  },
189
  typography: sharedTypography,
 
199
  export const lightTheme = createTheme({
200
  palette: {
201
  mode: 'light',
202
+ primary: { main: '#C7A500' },
203
  secondary: { main: '#B8960A' },
204
  background: { default: '#FFFFFF', paper: '#F7F8FA' },
205
  text: { primary: '#1A1A2E', secondary: '#6B7280' },
206
  divider: 'rgba(0,0,0,0.08)',
207
  success: { main: '#16A34A' },
208
  error: { main: '#DC2626' },
209
+ warning: { main: '#C7A500' },
210
  info: { main: '#2563EB' },
211
  },
212
  typography: sharedTypography,