diff --git a/internal/api/handlers_info.go b/internal/api/handlers_info.go
index 35aea93..4e3190c 100644
--- a/internal/api/handlers_info.go
+++ b/internal/api/handlers_info.go
@@ -477,25 +477,16 @@ func (s *Server) handleProvidersQuota(w http.ResponseWriter, r *http.Request) {
}
}
case "zai":
- if p.APIKey == "" {
- q.Error = "no API key"
- results = append(results, q)
- continue
- }
- req, _ := http.NewRequest("GET", "https://api.z.ai/api/monitor/usage/quota/limit", nil)
- req.Header.Set("Authorization", "Bearer "+p.APIKey)
- req.Header.Set("Accept", "application/json")
- resp, err := client.Do(req)
- if err != nil {
- q.Error = err.Error()
+ // Z.AI (GLM) est utilisé uniquement via Crush, pas de quota check externe
+ q.Healthy = true
+ q.Data = map[string]interface{}{"note": "crush-only"}
+ case "claude", "anthropic":
+ // Claude Code n'a pas d'API externe, vérifier l'installation
+ claudePath := "/usr/bin/claude"
+ if _, err := os.Stat(claudePath); err == nil {
+ q.Healthy = true
} else {
- body, _ := io.ReadAll(resp.Body)
- resp.Body.Close()
- var data map[string]interface{}
- if json.Unmarshal(body, &data) == nil {
- q.Data = data
- q.Healthy = true
- }
+ q.Error = "claude code not installed"
}
default:
q.Error = "quota not supported"
diff --git a/web/src/components/Config.jsx b/web/src/components/Config.jsx
index a835888..4e99746 100644
--- a/web/src/components/Config.jsx
+++ b/web/src/components/Config.jsx
@@ -353,8 +353,7 @@ function PanelProviders({ providers, editProvider, providerForm, setProviderForm
{p.name}
- {p.apiKey && {t('config.keyConfigured')}}
- {!p.apiKey && {t('config.noKey')}}
+ {p.active && active}
{isValidationTarget && validationStatus?.valid && {t('config.keyValid')}}
{isValidationTarget && !validationStatus?.valid && {validationStatus?.error}}
diff --git a/web/src/components/Studio.jsx b/web/src/components/Studio.jsx
index 63cff2a..eed1e38 100644
--- a/web/src/components/Studio.jsx
+++ b/web/src/components/Studio.jsx
@@ -285,6 +285,8 @@ export default function Studio({ api }) {
const [streamToolCalls, setStreamToolCalls] = useState([])
const [loaded, setLoaded] = useState(false)
const [tokenInfo, setTokenInfo] = useState({ used: 0, max: 100000, summarizeAt: 80000 })
+ const [contextCollapsed, setContextCollapsed] = useState(false)
+ const [messagesCollapsed, setMessagesCollapsed] = useState(false)
const messagesEnd = useRef(null)
const textareaRef = useRef(null)
const abortRef = useRef(null)
@@ -336,12 +338,18 @@ export default function Studio({ api }) {
const handleSummarize = useCallback(async () => {
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'system', content: 'Résumé de la conversation en cours...', time: new Date().toISOString() }])
+ setContextCollapsed('animating')
try {
const data = await api.summarizeChat()
setTokenInfo(prev => ({ ...prev, used: data.tokens || 0 }))
- setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'system', content: '✓ Conversation résumée automatiquement. Le contexte a été compressé.', time: new Date().toISOString() }])
+ setTimeout(() => {
+ setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'system', content: '✓ Conversation résumée automatiquement. Le contexte a été compressé.', time: new Date().toISOString(), compressed: true }])
+ setContextCollapsed(true)
+ setMessagesCollapsed(true)
+ }, 600)
} catch (err) {
setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'system', content: `Erreur de résumé: ${err.message}`, time: new Date().toISOString() }])
+ setContextCollapsed(false)
}
}, [api])
@@ -396,7 +404,7 @@ export default function Studio({ api }) {
if (text === '/model') {
api.getProviders().then(data => {
const active = data.providers?.find(p => p.active)
- const modelMsg = active ? `Provider: ${active.name}\nModèle: ${active.model}` : 'Aucun provider actif configuré'
+ const modelMsg = active ? active.name : 'Aucun provider actif configuré'
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: modelMsg, time: new Date().toISOString() }])
}).catch(() => {
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: 'Erreur: impossible de récupérer les providers', time: new Date().toISOString() }])
@@ -525,6 +533,34 @@ export default function Studio({ api }) {
}
}
+ const handleToggleCollapsed = useCallback(() => {
+ setMessagesCollapsed(prev => !prev)
+ }, [])
+
+ const renderMessages = () => {
+ if (messagesCollapsed && messages.length > 4) {
+ const visibleCount = 4
+ const hiddenCount = messages.length - visibleCount
+ return (
+ <>
+ {messages.slice(0, visibleCount).map(msg => (
+
+ ))}
+
+
+
{hiddenCount} messages antérieurs compressés
+
clic pour développer
+
+ >
+ )
+ }
+ return messages.map(msg => (
+
+ ))
+ }
+
if (!loaded) {
return (
@@ -540,27 +576,31 @@ export default function Studio({ api }) {
return (
- {messages.map(msg => (
-
- ))}
+ {renderMessages()}
{(streaming || streamThinking || loading || streamToolCalls.length > 0) && (
)}
-
+
-
-
+
+
= tokenInfo.summarizeAt ? 'warn' : ''}`}
+ className={`studio-token-fill ${tokenInfo.used >= tokenInfo.summarizeAt ? 'warn' : ''} ${contextCollapsed === true ? 'compressed' : ''} ${contextCollapsed === 'animating' ? 'animating' : ''}`}
style={{ width: `${Math.min(100, (tokenInfo.used / tokenInfo.max) * 100)}%` }}
/>
-
+
{(tokenInfo.used / 1000).toFixed(1)}k / {(tokenInfo.max / 1000).toFixed(0)}k tokens
- {tokenInfo.used >= tokenInfo.summarizeAt && ' · résumé automatique déclenché'}
+ {contextCollapsed === true && ' · compressé'}
+ {tokenInfo.used >= tokenInfo.summarizeAt && contextCollapsed !== true && ' · résumé auto.'}
+ {contextCollapsed === true && (
+
+ )}