akseljoonas HF Staff commited on
Commit
677b9d0
·
1 Parent(s): f97b6ec

feat: show hardware pricing and sandbox explanation in tool approval UI

Browse files

Users now see cost/hr next to hardware tier when approving sandbox_create
and hf_jobs tool calls. sandbox_create also explains what it does ("Creates
a temporary HF Space to develop and test scripts before running jobs").

frontend/src/components/Chat/ToolCallGroup.tsx CHANGED
@@ -24,6 +24,32 @@ interface ToolCallGroupProps {
24
  approveTools: (approvals: Array<{ tool_call_id: string; approved: boolean; feedback?: string | null; edited_script?: string | null }>) => Promise<boolean>;
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  // ---------------------------------------------------------------------------
28
  // Visual helpers
29
  // ---------------------------------------------------------------------------
@@ -108,29 +134,49 @@ function InlineApproval({
108
 
109
  return (
110
  <Box sx={{ px: 1.5, py: 1.5, borderTop: '1px solid var(--tool-border)' }}>
111
- {toolName === 'sandbox_create' && args && (
112
- <Box sx={{ mb: 1.5 }}>
113
- <Typography variant="body2" sx={{ color: 'var(--muted-text)', fontSize: '0.75rem', mb: 1 }}>
114
- Create sandbox on{' '}
115
- <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
116
- {String(args.hardware || 'cpu-basic')}
117
- </Box>
118
- {!!args.private && (
119
- <Box component="span" sx={{ color: 'var(--muted-text)' }}>{' (private)'}</Box>
120
- )}
121
- </Typography>
122
- </Box>
123
- )}
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- {toolName === 'hf_jobs' && args && (
 
 
 
126
  <Box sx={{ mb: 1.5 }}>
127
  <Typography variant="body2" sx={{ color: 'var(--muted-text)', fontSize: '0.75rem', mb: 1 }}>
128
  Execute <Box component="span" sx={{ color: 'var(--accent-yellow)', fontWeight: 500 }}>{scriptLabel.replace('Script', 'Job')}</Box> on{' '}
129
  <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
130
- {String(args.hardware_flavor || 'default')}
131
  </Box>
 
 
 
 
 
132
  {!!args.timeout && (
133
- <> with timeout <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
134
  {String(args.timeout)}
135
  </Box></>
136
  )}
@@ -184,7 +230,8 @@ function InlineApproval({
184
  </Box>
185
  )}
186
  </Box>
187
- )}
 
188
 
189
  <Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
190
  <TextField
 
24
  approveTools: (approvals: Array<{ tool_call_id: string; approved: boolean; feedback?: string | null; edited_script?: string | null }>) => Promise<boolean>;
25
  }
26
 
27
+ // ---------------------------------------------------------------------------
28
+ // Hardware pricing ($/hr) — from HF Spaces & Jobs pricing
29
+ // ---------------------------------------------------------------------------
30
+ const HARDWARE_PRICING: Record<string, string> = {
31
+ 'cpu-basic': 'free',
32
+ 'cpu-upgrade': '$0.03/hr',
33
+ 't4-small': '$0.60/hr',
34
+ 't4-medium': '$1.00/hr',
35
+ 'a10g-small': '$1.05/hr',
36
+ 'a10g-large': '$3.15/hr',
37
+ 'a10g-largex2': '$6.30/hr',
38
+ 'a10g-largex4': '$12.60/hr',
39
+ 'a100-large': '$4.13/hr',
40
+ 'a100x4': '$16.52/hr',
41
+ 'a100x8': '$33.04/hr',
42
+ 'l4x1': '$0.80/hr',
43
+ 'l4x4': '$3.20/hr',
44
+ 'l40sx1': '$1.80/hr',
45
+ 'l40sx4': '$7.20/hr',
46
+ 'l40sx8': '$14.40/hr',
47
+ };
48
+
49
+ function costLabel(hardware: string): string | null {
50
+ return HARDWARE_PRICING[hardware] || null;
51
+ }
52
+
53
  // ---------------------------------------------------------------------------
54
  // Visual helpers
55
  // ---------------------------------------------------------------------------
 
134
 
135
  return (
136
  <Box sx={{ px: 1.5, py: 1.5, borderTop: '1px solid var(--tool-border)' }}>
137
+ {toolName === 'sandbox_create' && args && (() => {
138
+ const hw = String(args.hardware || 'cpu-basic');
139
+ const cost = costLabel(hw);
140
+ return (
141
+ <Box sx={{ mb: 1.5 }}>
142
+ <Typography variant="body2" sx={{ color: 'var(--muted-text)', fontSize: '0.75rem', mb: 0.5 }}>
143
+ Create a remote dev environment on{' '}
144
+ <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
145
+ {hw}
146
+ </Box>
147
+ {cost && (
148
+ <Box component="span" sx={{ color: cost === 'free' ? 'var(--accent-green)' : 'var(--accent-yellow)', fontWeight: 500 }}>
149
+ {' '}({cost})
150
+ </Box>
151
+ )}
152
+ {!!args.private && (
153
+ <Box component="span" sx={{ color: 'var(--muted-text)' }}>{' (private)'}</Box>
154
+ )}
155
+ </Typography>
156
+ <Typography variant="body2" sx={{ color: 'var(--muted-text)', fontSize: '0.7rem', opacity: 0.7 }}>
157
+ Creates a temporary HF Space to develop and test scripts before running jobs. Takes 1-2 min to start.
158
+ </Typography>
159
+ </Box>
160
+ );
161
+ })()}
162
 
163
+ {toolName === 'hf_jobs' && args && (() => {
164
+ const hw = String(args.hardware_flavor || 'cpu-basic');
165
+ const cost = costLabel(hw);
166
+ return (
167
  <Box sx={{ mb: 1.5 }}>
168
  <Typography variant="body2" sx={{ color: 'var(--muted-text)', fontSize: '0.75rem', mb: 1 }}>
169
  Execute <Box component="span" sx={{ color: 'var(--accent-yellow)', fontWeight: 500 }}>{scriptLabel.replace('Script', 'Job')}</Box> on{' '}
170
  <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
171
+ {hw}
172
  </Box>
173
+ {cost && (
174
+ <Box component="span" sx={{ color: cost === 'free' ? 'var(--accent-green)' : 'var(--accent-yellow)', fontWeight: 500 }}>
175
+ {' '}({cost})
176
+ </Box>
177
+ )}
178
  {!!args.timeout && (
179
+ <> for up to <Box component="span" sx={{ fontWeight: 500, color: 'var(--text)' }}>
180
  {String(args.timeout)}
181
  </Box></>
182
  )}
 
230
  </Box>
231
  )}
232
  </Box>
233
+ );
234
+ })()}
235
 
236
  <Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
237
  <TextField