fix: Add pony-alpha model and fix model lookup for prefixed model IDs
- Add openrouter/pony-alpha model to models-api.json fixture - Fix getModel() to lookup models with provider prefix (e.g., openrouter/pony-alpha) When user specifies openrouter/pony-alpha, the code now correctly looks for the full model ID including prefix in the provider's models object This fixes the 'ModelNotFoundError' when using OpenRouter models that have prefixed IDs in the database.
This commit is contained in:
@@ -300,6 +300,26 @@
|
||||
box-shadow: 0 10px 25px rgba(0, 128, 96, 0.25);
|
||||
}
|
||||
|
||||
/* Cancel mode for send button */
|
||||
.primary.cancel-mode {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary.cancel-mode:hover {
|
||||
box-shadow: 0 10px 25px rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
/* Cancelled message styling */
|
||||
.message.assistant.cancelled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.message.assistant.cancelled .body {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.back-home {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
@@ -1932,11 +1952,16 @@
|
||||
}
|
||||
|
||||
const miniBtn = el.miniSendBtn;
|
||||
const originalLabel = miniBtn ? miniBtn.textContent : '';
|
||||
if (miniBtn) {
|
||||
miniBtn.disabled = true;
|
||||
miniBtn.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>'; // Clock icon for planning
|
||||
|
||||
// Set sending state for cancellation support
|
||||
if (typeof state !== 'undefined') {
|
||||
state.isSending = true;
|
||||
state.currentSendingMessageId = tempMessageId;
|
||||
}
|
||||
if (typeof updateSendButtonState === 'function') {
|
||||
updateSendButtonState();
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api('/api/plan', { method: 'POST', body: JSON.stringify(payload) });
|
||||
|
||||
@@ -1986,14 +2011,13 @@
|
||||
renderMessages(session);
|
||||
}
|
||||
} finally {
|
||||
if (miniBtn) {
|
||||
miniBtn.disabled = false;
|
||||
// Restore original SVG icon
|
||||
miniBtn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
||||
</svg>`;
|
||||
// Reset sending state
|
||||
if (typeof state !== 'undefined') {
|
||||
state.isSending = false;
|
||||
state.currentSendingMessageId = null;
|
||||
}
|
||||
if (typeof updateSendButtonState === 'function') {
|
||||
updateSendButtonState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2278,7 +2302,15 @@
|
||||
};
|
||||
|
||||
// document.getElementById('send-btn') removed
|
||||
document.getElementById('mini-send-btn').addEventListener('click', handleSend, true);
|
||||
// Use handleSendButtonClick which handles both send and cancel modes
|
||||
document.getElementById('mini-send-btn').addEventListener('click', (e) => {
|
||||
if (typeof handleSendButtonClick === 'function') {
|
||||
handleSendButtonClick(e);
|
||||
} else {
|
||||
// Fallback to old behavior if function not loaded yet
|
||||
handleSend(e);
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Initialize UI
|
||||
updateBuildModeUI();
|
||||
|
||||
@@ -384,6 +384,8 @@ const state = {
|
||||
usageSummary: null,
|
||||
todos: [], // Current todos from OpenCode
|
||||
currentMessageId: null, // Track which message we're displaying todos for
|
||||
isSending: false, // Track if a message is currently being sent
|
||||
currentSendingMessageId: null, // Track the ID of the message being sent for cancellation
|
||||
};
|
||||
|
||||
// Expose state for builder.html
|
||||
@@ -1824,7 +1826,7 @@ function renderMessages(session) {
|
||||
// Don't hide loading indicator during streaming - only hide when completely done
|
||||
// This allows the spinner to continue until the OpenCode session is fully complete
|
||||
const assistantCard = document.createElement('div');
|
||||
assistantCard.className = 'message assistant';
|
||||
assistantCard.className = 'message assistant' + (status === 'cancelled' ? ' cancelled' : '');
|
||||
const assistantMeta = document.createElement('div');
|
||||
assistantMeta.className = 'meta';
|
||||
const msgCliLabel = (msg.cli || session.cli || 'opencode');
|
||||
@@ -1841,10 +1843,10 @@ function renderMessages(session) {
|
||||
rawBtn.textContent = 'Plugin Compass';
|
||||
assistantMeta.appendChild(rawBtn);
|
||||
|
||||
// Add Undo/Redo buttons - only show for latest message and for opencode messages when done
|
||||
// Add Undo/Redo buttons - show for latest message when done, errored, or cancelled
|
||||
const isOpencodeMsg = msg.cli === 'opencode' || msg.phase === 'build';
|
||||
const isLatestMessage = latestMessage && latestMessage.id === msg.id;
|
||||
const shouldShowUndoRedo = isOpencodeMsg && isLatestMessage && (status === 'done' || status === 'error');
|
||||
const shouldShowUndoRedo = isOpencodeMsg && isLatestMessage && (status === 'done' || status === 'error' || status === 'cancelled');
|
||||
|
||||
if (shouldShowUndoRedo) {
|
||||
const undoBtn = document.createElement('button');
|
||||
@@ -3721,7 +3723,9 @@ async function sendMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (el.miniSendBtn) el.miniSendBtn.disabled = true;
|
||||
// Set sending state and update button
|
||||
state.isSending = true;
|
||||
updateSendButtonState();
|
||||
|
||||
// Ensure we have a valid current session before proceeding
|
||||
if (!state.currentSessionId) {
|
||||
@@ -3801,6 +3805,12 @@ async function sendMessage() {
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
// Track the message ID being sent for potential cancellation
|
||||
if (response?.message?.id) {
|
||||
state.currentSendingMessageId = response.message.id;
|
||||
console.log('[BUILDER] Tracking message for cancellation:', response.message.id);
|
||||
}
|
||||
|
||||
// Clear attachments after successful send
|
||||
pendingAttachments.length = 0;
|
||||
renderAttachmentPreview();
|
||||
@@ -3862,12 +3872,143 @@ async function sendMessage() {
|
||||
renderMessages(currentSession);
|
||||
}
|
||||
} finally {
|
||||
if (el.miniSendBtn) el.miniSendBtn.disabled = false;
|
||||
// Reset sending state when message completes, is cancelled, or errors out
|
||||
// Only reset if we're not currently in a cancellation (cancelMessage handles that)
|
||||
if (state.isSending && state.currentSendingMessageId) {
|
||||
const session = state.sessions.find(s => s.id === state.currentSessionId);
|
||||
const messageId = state.currentSendingMessageId;
|
||||
const message = session?.messages.find(m => m.id === messageId);
|
||||
|
||||
// Reset if message is done, errored, or cancelled
|
||||
if (!message || message.status === 'done' || message.status === 'error' || message.status === 'cancelled') {
|
||||
state.isSending = false;
|
||||
state.currentSendingMessageId = null;
|
||||
updateSendButtonState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel the current message being sent
|
||||
async function cancelMessage() {
|
||||
if (!state.isSending || !state.currentSendingMessageId) {
|
||||
console.log('[CANCEL] No message to cancel');
|
||||
return;
|
||||
}
|
||||
|
||||
const messageId = state.currentSendingMessageId;
|
||||
const sessionId = state.currentSessionId;
|
||||
|
||||
console.log('[CANCEL] Cancelling message:', messageId);
|
||||
setStatus('Cancelling...');
|
||||
|
||||
// Close any active SSE streams for this message
|
||||
if (state.activeStreams.has(messageId)) {
|
||||
const stream = state.activeStreams.get(messageId);
|
||||
stream.close();
|
||||
state.activeStreams.delete(messageId);
|
||||
console.log('[CANCEL] Closed SSE stream for message:', messageId);
|
||||
}
|
||||
|
||||
// Find and update the message status
|
||||
const session = state.sessions.find(s => s.id === sessionId);
|
||||
if (session) {
|
||||
const message = session.messages.find(m => m.id === messageId);
|
||||
if (message) {
|
||||
message.status = 'cancelled';
|
||||
message.reply = message.reply || '(Cancelled by user)';
|
||||
message.cancelled = true;
|
||||
|
||||
// Show undo/redo buttons for the cancelled message
|
||||
message.showUndoRedo = true;
|
||||
|
||||
console.log('[CANCEL] Message marked as cancelled:', messageId);
|
||||
}
|
||||
|
||||
// Remove temp messages
|
||||
const tempMessages = session.messages.filter(m => m.id.startsWith('temp-') && m.status === 'queued');
|
||||
tempMessages.forEach(tempMsg => {
|
||||
const idx = session.messages.indexOf(tempMsg);
|
||||
if (idx > -1) {
|
||||
session.messages.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
|
||||
renderMessages(session);
|
||||
}
|
||||
|
||||
// Reset sending state
|
||||
state.isSending = false;
|
||||
state.currentSendingMessageId = null;
|
||||
|
||||
// Reset send button
|
||||
updateSendButtonState();
|
||||
|
||||
hideLoadingIndicator();
|
||||
setStatus('Message cancelled');
|
||||
|
||||
// Refresh session to sync with server
|
||||
try {
|
||||
await refreshCurrentSession();
|
||||
} catch (err) {
|
||||
console.warn('[CANCEL] Failed to refresh session:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Update send button appearance based on state
|
||||
function updateSendButtonState() {
|
||||
const btn = el.miniSendBtn;
|
||||
if (!btn) return;
|
||||
|
||||
if (state.isSending) {
|
||||
// Show cancel button (square icon)
|
||||
btn.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="6" y="6" width="12" height="12" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
`;
|
||||
btn.title = 'Cancel message';
|
||||
btn.classList.add('cancel-mode');
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
// Show send button (paper plane icon)
|
||||
btn.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
||||
</svg>
|
||||
`;
|
||||
btn.title = 'Send message';
|
||||
btn.classList.remove('cancel-mode');
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle send button click - either send or cancel
|
||||
function handleSendButtonClick(e) {
|
||||
if (state.isSending) {
|
||||
// Cancel the current message
|
||||
cancelMessage();
|
||||
} else {
|
||||
// Check if we're in plan mode and call the appropriate handler
|
||||
if (builderState.mode === 'plan') {
|
||||
// Use handleSend from builder.html which handles plan mode
|
||||
if (typeof handleSend === 'function') {
|
||||
handleSend(e);
|
||||
} else {
|
||||
console.warn('[BUILDER] handleSend not available');
|
||||
}
|
||||
} else {
|
||||
// Send a new message in build mode
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose for builder.html
|
||||
window.sendMessage = sendMessage;
|
||||
window.cancelMessage = cancelMessage;
|
||||
window.handleSendButtonClick = handleSendButtonClick;
|
||||
|
||||
function hookEvents() {
|
||||
if (el.newChat) {
|
||||
@@ -4205,7 +4346,11 @@ function hookEvents() {
|
||||
|
||||
el.messageInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||
sendMessage();
|
||||
if (state.isSending) {
|
||||
cancelMessage();
|
||||
} else {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1200,7 +1200,11 @@ export namespace Provider {
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
|
||||
const info = provider.models[modelID]
|
||||
let info = provider.models[modelID]
|
||||
// Try with provider prefix if not found (e.g., "openrouter/pony-alpha")
|
||||
if (!info && !modelID.includes("/")) {
|
||||
info = provider.models[`${providerID}/${modelID}`]
|
||||
}
|
||||
if (!info) {
|
||||
const availableModels = Object.keys(provider.models)
|
||||
const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 })
|
||||
|
||||
@@ -27310,6 +27310,23 @@
|
||||
"cost": { "input": 0, "output": 0 },
|
||||
"limit": { "context": 1840000, "output": 0 }
|
||||
},
|
||||
"openrouter/pony-alpha": {
|
||||
"id": "openrouter/pony-alpha",
|
||||
"name": "Stealth",
|
||||
"family": "pony",
|
||||
"attachment": false,
|
||||
"reasoning": true,
|
||||
"tool_call": true,
|
||||
"structured_output": true,
|
||||
"temperature": true,
|
||||
"release_date": "2026-02-06",
|
||||
"last_updated": "2026-02-06",
|
||||
"modalities": { "input": ["text"], "output": ["text"] },
|
||||
"open_weights": false,
|
||||
"cost": { "input": 0, "output": 0 },
|
||||
"limit": { "context": 200000, "output": 131000 },
|
||||
"status": "alpha"
|
||||
},
|
||||
"z-ai/glm-4.7": {
|
||||
"id": "z-ai/glm-4.7",
|
||||
"name": "GLM-4.7",
|
||||
|
||||
Reference in New Issue
Block a user