Compare commits
4 Commits
v0.3.5-bet
...
v0.3.5-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8aa935bec | ||
|
|
5627ddd2ce | ||
|
|
d27872572a | ||
|
|
7d0f807fb0 |
@@ -530,6 +530,11 @@ func (s *Server) handleProvidersQuota(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "mimo":
|
||||||
|
q.Healthy = p.APIKey != ""
|
||||||
|
if p.APIKey == "" {
|
||||||
|
q.Error = "no API key"
|
||||||
|
}
|
||||||
case "claude", "anthropic":
|
case "claude", "anthropic":
|
||||||
// Claude Code n'a pas d'API externe, vérifier l'installation
|
// Claude Code n'a pas d'API externe, vérifier l'installation
|
||||||
claudePath := "/usr/bin/claude"
|
claudePath := "/usr/bin/claude"
|
||||||
|
|||||||
@@ -269,6 +269,12 @@ func Default() *MuyueConfig {
|
|||||||
BaseURL: "https://api.minimax.io/v1",
|
BaseURL: "https://api.minimax.io/v1",
|
||||||
Active: true,
|
Active: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "mimo",
|
||||||
|
Model: "MiMo-V2.5-Pro",
|
||||||
|
BaseURL: "https://token-plan-ams.xiaomimimo.com/v1",
|
||||||
|
Active: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "zai",
|
Name: "zai",
|
||||||
Model: "glm",
|
Model: "glm",
|
||||||
|
|||||||
@@ -476,6 +476,8 @@ func getProviderBaseURL(name string) string {
|
|||||||
return "https://api.openai.com/v1"
|
return "https://api.openai.com/v1"
|
||||||
case "zai":
|
case "zai":
|
||||||
return "https://api.z.ai/v1"
|
return "https://api.z.ai/v1"
|
||||||
|
case "mimo":
|
||||||
|
return "https://token-plan-ams.xiaomimimo.com/v1"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -503,11 +505,19 @@ func (o *Orchestrator) sendWithFallback(reqBody ChatRequest, baseURLOverride str
|
|||||||
if o.provider != nil {
|
if o.provider != nil {
|
||||||
providerOrder = append(providerOrder, o.provider)
|
providerOrder = append(providerOrder, o.provider)
|
||||||
}
|
}
|
||||||
|
var zaiProvider *config.AIProvider
|
||||||
for _, p := range providers {
|
for _, p := range providers {
|
||||||
if o.provider == nil || p.Name != o.provider.Name {
|
if o.provider == nil || p.Name != o.provider.Name {
|
||||||
|
if p.Name == "zai" {
|
||||||
|
zaiProvider = p
|
||||||
|
} else {
|
||||||
providerOrder = append(providerOrder, p)
|
providerOrder = append(providerOrder, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if zaiProvider != nil {
|
||||||
|
providerOrder = append(providerOrder, zaiProvider)
|
||||||
|
}
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
var triedProviders []string
|
var triedProviders []string
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ function PanelProviders({ providers, editProvider, providerForm, setProviderForm
|
|||||||
setValidating(null)
|
setValidating(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayed = providers.filter(p => p.name === 'minimax' || p.name === 'zai')
|
const displayed = providers.filter(p => p.name === 'minimax' || p.name === 'mimo')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-providers-list">
|
<div className="config-providers-list">
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default function Dashboard({ api, refreshRef }) {
|
|||||||
}, [loadData, refreshRef])
|
}, [loadData, refreshRef])
|
||||||
|
|
||||||
const minimax = (quota || []).find(p => p.name === 'minimax')
|
const minimax = (quota || []).find(p => p.name === 'minimax')
|
||||||
const zai = (quota || []).find(p => p.name === 'zai')
|
const mimo = (quota || []).find(p => p.name === 'mimo')
|
||||||
|
|
||||||
const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history', 'cat', 'echo', 'grep', 'export', 'alias', 'unalias', 'set', 'unset', 'source', '.', 'fg', 'bg', 'jobs', 'wait', 'true', 'false', 'yes', 'sleep', 'date', 'whoami', 'id', 'uname', 'hostname', 'uptime', 'df', 'free', 'top', 'htop', 'nano', 'vi', 'vim', 'less', 'more', 'tail', 'head', 'man', 'info', 'which', 'whereis', 'type', 'command', 'hash', 'builtin', 'help']
|
const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history', 'cat', 'echo', 'grep', 'export', 'alias', 'unalias', 'set', 'unset', 'source', '.', 'fg', 'bg', 'jobs', 'wait', 'true', 'false', 'yes', 'sleep', 'date', 'whoami', 'id', 'uname', 'hostname', 'uptime', 'df', 'free', 'top', 'htop', 'nano', 'vi', 'vim', 'less', 'more', 'tail', 'head', 'man', 'info', 'which', 'whereis', 'type', 'command', 'hash', 'builtin', 'help']
|
||||||
|
|
||||||
@@ -186,22 +186,22 @@ export default function Dashboard({ api, refreshRef }) {
|
|||||||
<span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>{minimax.error || 'no data'}</span>
|
<span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>{minimax.error || 'no data'}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{zai && zai.data?.models?.map((m, i) => (
|
{mimo && mimo.data?.models?.map((m, i) => (
|
||||||
<div key={i} className="dash-quota-row">
|
<div key={i} className="dash-quota-row">
|
||||||
<span className="dash-quota-name">{String(m.model)}</span>
|
<span className="dash-quota-name">{String(m.model).replace('MiMo-', '')}</span>
|
||||||
<div className="dash-bar">
|
<div className="dash-bar">
|
||||||
<div className="dash-bar-fill" style={{ width: `${Math.min(100, (m.used / m.total) * 100)}%` }} />
|
<div className="dash-bar-fill" style={{ width: `${Math.min(100, (m.used / m.total) * 100)}%` }} />
|
||||||
</div>
|
</div>
|
||||||
<span className="dash-quota-val">{m.used}/{m.total}</span>
|
<span className="dash-quota-val">{m.used}/{m.total}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{zai && !zai.data?.models?.length && (
|
{mimo && !mimo.data?.models?.length && (
|
||||||
<div className="dash-quota-row">
|
<div className="dash-quota-row">
|
||||||
<span className="dash-quota-name">Z.AI</span>
|
<span className="dash-quota-name">MiMo</span>
|
||||||
<span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>{zai.error || 'no data'}</span>
|
<span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>{mimo.error || (mimo.healthy ? '✓ configured' : 'no key')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!minimax && !zai && <span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>No providers</span>}
|
{!minimax && !mimo && <span className="dash-quota-val" style={{ color: 'var(--text-tertiary)' }}>No providers</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -182,9 +182,20 @@ function connectWebSocket(term, fitAddon, initPayload, onStateChange, onFirstMes
|
|||||||
ws.addEventListener('open', () => {
|
ws.addEventListener('open', () => {
|
||||||
ws.send(JSON.stringify(initPayload))
|
ws.send(JSON.stringify(initPayload))
|
||||||
const dims = fitAddon.proposeDimensions()
|
const dims = fitAddon.proposeDimensions()
|
||||||
if (dims) {
|
// Envoyer resize avec dimensions minimales garanties (24x80)
|
||||||
ws.send(JSON.stringify({ type: 'resize', rows: dims.rows, cols: dims.cols }))
|
const rows = dims?.rows || 24
|
||||||
|
const cols = dims?.cols || 80
|
||||||
|
ws.send(JSON.stringify({ type: 'resize', rows, cols }))
|
||||||
|
// Forcer un fit après l'ouverture
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
fitAddon.fit()
|
||||||
|
const newDims = fitAddon.proposeDimensions()
|
||||||
|
if (newDims && newDims.rows > 0 && newDims.cols > 0) {
|
||||||
|
ws.send(JSON.stringify({ type: 'resize', rows: newDims.rows, cols: newDims.cols }))
|
||||||
}
|
}
|
||||||
|
} catch (e) { console.warn('[Shell] fit failed:', e) }
|
||||||
|
}, 50)
|
||||||
if (onStateChange) onStateChange(true)
|
if (onStateChange) onStateChange(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -232,26 +243,41 @@ export default function Shell({ api }) {
|
|||||||
const settingsRef = useRef({ fontSize: 12, fontFamily: "'JetBrains Mono', 'Fira Code', monospace", theme: 'default' })
|
const settingsRef = useRef({ fontSize: 12, fontFamily: "'JetBrains Mono', 'Fira Code', monospace", theme: 'default' })
|
||||||
const pendingCommandsRef = useRef({})
|
const pendingCommandsRef = useRef({})
|
||||||
|
|
||||||
const savedTabs = (() => {
|
const [tabs, setTabs] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(TABS_STORAGE_KEY)
|
const raw = localStorage.getItem(TABS_STORAGE_KEY)
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const parsed = JSON.parse(raw)
|
const parsed = JSON.parse(raw)
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
if (Array.isArray(parsed) && parsed.length > 0 && parsed.length <= MAX_TABS) {
|
||||||
return parsed.map(t => ({ ...t, connected: false }))
|
return parsed.map((t, i) => ({
|
||||||
|
id: t.id || i + 1,
|
||||||
|
name: t.name || `Tab ${i + 1}`,
|
||||||
|
type: t.type || 'local',
|
||||||
|
shell: t.shell || '',
|
||||||
|
host: t.host,
|
||||||
|
port: t.port,
|
||||||
|
user: t.user,
|
||||||
|
key_path: t.key_path,
|
||||||
|
connected: false
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Shell] Failed to parse saved tabs:', e)
|
||||||
|
localStorage.removeItem(TABS_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ id: 1, name: 'Local Shell', type: 'local', shell: '', connected: false },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const [activeTab, setActiveTab] = useState(() => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(TABS_STORAGE_KEY)
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw)
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0) return parsed[0]?.id || 1
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
return null
|
|
||||||
})()
|
|
||||||
|
|
||||||
const [tabs, setTabs] = useState(savedTabs || [
|
|
||||||
{ id: 1, name: 'Local Shell', type: 'local', shell: '', connected: false },
|
|
||||||
])
|
|
||||||
const [activeTab, setActiveTab] = useState(() => {
|
|
||||||
if (savedTabs) {
|
|
||||||
return savedTabs[0]?.id || 1
|
|
||||||
}
|
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
const activeTabRef = useRef(activeTab)
|
const activeTabRef = useRef(activeTab)
|
||||||
@@ -482,36 +508,60 @@ export default function Shell({ api }) {
|
|||||||
let cancelled = false
|
let cancelled = false
|
||||||
const pending = []
|
const pending = []
|
||||||
|
|
||||||
|
// Forcer le layout à se calculer
|
||||||
|
const forceLayout = () => {
|
||||||
|
const el = document.querySelector('.shell-terminal-col')
|
||||||
|
if (el) {
|
||||||
|
el.style.height = ''
|
||||||
|
el.style.minHeight = ''
|
||||||
|
// Forcer reflow
|
||||||
|
void el.offsetHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tryInitTab = (tab, attempt) => {
|
const tryInitTab = (tab, attempt) => {
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
const shellCol = document.querySelector('.shell-terminal-col')
|
if (attempt > 20) {
|
||||||
if (!shellCol || shellCol.offsetParent === null) {
|
console.warn(`[Shell] max attempts reached for tab ${tab.id}`)
|
||||||
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 200))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceLayout()
|
||||||
|
const shellCol = document.querySelector('.shell-terminal-col')
|
||||||
|
if (!shellCol) {
|
||||||
|
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 150))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const container = document.getElementById(`terminal-${tab.id}`)
|
const container = document.getElementById(`terminal-${tab.id}`)
|
||||||
if (!container) {
|
if (!container) {
|
||||||
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
|
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (container.offsetHeight === 0) {
|
|
||||||
|
const rect = container.getBoundingClientRect()
|
||||||
|
if (rect.height < 10 || rect.width < 10) {
|
||||||
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
|
pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tabsRef.current[tab.id]) {
|
if (!tabsRef.current[tab.id]) {
|
||||||
initTerminal(tab.id, tab)
|
initTerminal(tab.id, tab)
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
|
||||||
|
// Multiple fit attempts avec délais croissants
|
||||||
|
const fitAttempts = [0, 50, 100, 200, 400]
|
||||||
|
fitAttempts.forEach(delay => {
|
||||||
|
setTimeout(() => {
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
const entry = tabsRef.current[tab.id]
|
const entry = tabsRef.current[tab.id]
|
||||||
if (entry) {
|
if (entry && entry.fitAddon) {
|
||||||
|
try {
|
||||||
entry.fitAddon.fit()
|
entry.fitAddon.fit()
|
||||||
setTimeout(() => { if (!cancelled) entry.fitAddon.fit() }, 100)
|
} catch (e) { console.warn(`[Shell] fit attempt ${delay}ms failed:`, e) }
|
||||||
}
|
}
|
||||||
|
}, delay)
|
||||||
})
|
})
|
||||||
if (!tabsRef.current[tab.id]) {
|
|
||||||
tryInitTab(tab, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = document.querySelector('.shell-layout')?.parentElement
|
const wrapper = document.querySelector('.shell-layout')?.parentElement
|
||||||
@@ -650,6 +700,19 @@ export default function Shell({ api }) {
|
|||||||
}
|
}
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Redimensionner le nouveau tab actif
|
||||||
|
setTimeout(() => {
|
||||||
|
const newActiveTabId = next.length > 0 ? next[next.length - 1].id : null
|
||||||
|
if (newActiveTabId) {
|
||||||
|
const entry = tabsRef.current[newActiveTabId]
|
||||||
|
if (entry && entry.fitAddon) {
|
||||||
|
try {
|
||||||
|
entry.fitAddon.fit()
|
||||||
|
} catch (e) { console.warn('[Shell] fit after close failed:', e) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
const startRename = (tabId, e) => {
|
const startRename = (tabId, e) => {
|
||||||
|
|||||||
@@ -452,15 +452,15 @@ export default function Studio({ api }) {
|
|||||||
api.getProviders().then(data => {
|
api.getProviders().then(data => {
|
||||||
const providers = data.providers || []
|
const providers = data.providers || []
|
||||||
const minimax = providers.find(p => p.name.toUpperCase() === 'MINIMAX')
|
const minimax = providers.find(p => p.name.toUpperCase() === 'MINIMAX')
|
||||||
const zai = providers.find(p => p.name.toUpperCase() === 'ZAI')
|
const mimo = providers.find(p => p.name.toUpperCase() === 'MIMO')
|
||||||
if (!minimax || !zai) {
|
if (!minimax || !mimo) {
|
||||||
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: 'MiniMax et ZAI doivent être configurés pour utiliser `/model change`.', time: new Date().toISOString() }])
|
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: 'MiniMax et MiMo doivent être configurés pour utiliser `/model change`.', time: new Date().toISOString() }])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const active = providers.find(p => p.active)
|
const active = providers.find(p => p.active)
|
||||||
const activeName = active ? active.name.toUpperCase() : ''
|
const activeName = active ? active.name.toUpperCase() : ''
|
||||||
const switchTo = activeName === 'MINIMAX' ? 'ZAI' : 'MINIMAX'
|
const switchTo = activeName === 'MINIMAX' ? 'MIMO' : 'MINIMAX'
|
||||||
const target = switchTo === 'MINIMAX' ? minimax : zai
|
const target = switchTo === 'MINIMAX' ? minimax : mimo
|
||||||
api.saveProvider({ name: target.name, active: true }).then(() => {
|
api.saveProvider({ name: target.name, active: true }).then(() => {
|
||||||
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: `✓ Provider changé: **${target.name}** (${target.model})`, time: new Date().toISOString() }])
|
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: `✓ Provider changé: **${target.name}** (${target.model})`, time: new Date().toISOString() }])
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user