Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
677b9d0
1
Parent(s): f97b6ec
feat: show hardware pricing and sandbox explanation in tool approval UI
Browse filesUsers 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 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 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 |
-
{
|
| 131 |
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
{!!args.timeout && (
|
| 133 |
-
<>
|
| 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
|