feat(api): add completion request cancellation and mermaid rendering
Add support for cancelling in-progress LLM completion requests via new /v1/completions/cancel endpoint with task tracking. Implement mermaid diagram rendering in the Milkdown editor with a new mermaidPlugin. Update copilotPlugin to properly abort requests with descriptive reasons. Refactor settings panel to handle system theme changes reactively. Add camera capture support for image uploads.
This commit is contained in:
@@ -1,7 +1,51 @@
|
||||
import { API_URL } from './config.js'
|
||||
import { useSettingsStore } from '../stores/settings'
|
||||
|
||||
const API_KEY = 'your-secret-key-here'
|
||||
|
||||
let cachedIP = null
|
||||
|
||||
function generateRequestId() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
}
|
||||
|
||||
function getCancelUrl(apiUrl) {
|
||||
const normalized = String(apiUrl || '').replace(/\/+$/, '')
|
||||
if (!normalized) return '/v1/completions/cancel'
|
||||
if (normalized.endsWith('/v1/completions')) {
|
||||
return `${normalized}/cancel`
|
||||
}
|
||||
return `${normalized}/cancel`
|
||||
}
|
||||
|
||||
function normalizeAbortReason(reason) {
|
||||
if (typeof reason === 'string' && reason.trim()) {
|
||||
return reason.trim().slice(0, 64)
|
||||
}
|
||||
return 'abort'
|
||||
}
|
||||
|
||||
async function sendCancelRequest(cancelUrl, requestId, reason) {
|
||||
try {
|
||||
await fetch(cancelUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
request_id: requestId,
|
||||
reason,
|
||||
}),
|
||||
})
|
||||
} catch (e) {
|
||||
console.debug('[Copilot] cancel request failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function getClientIP() {
|
||||
if (cachedIP) return cachedIP
|
||||
try {
|
||||
@@ -16,15 +60,30 @@ async function getClientIP() {
|
||||
}
|
||||
}
|
||||
|
||||
import { useSettingsStore } from '../stores/settings'
|
||||
|
||||
export async function fetchSuggestion(prefix, suffix, signal, apiUrl = API_URL) {
|
||||
const requestId = generateRequestId()
|
||||
const cancelUrl = getCancelUrl(apiUrl)
|
||||
|
||||
const onAbort = () => {
|
||||
const reason = normalizeAbortReason(signal?.reason)
|
||||
void sendCancelRequest(cancelUrl, requestId, reason)
|
||||
}
|
||||
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
onAbort()
|
||||
} else {
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = useSettingsStore()
|
||||
const clientIP = await getClientIP()
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': 'your-secret-key-here'
|
||||
'X-API-Key': API_KEY,
|
||||
'X-Request-Id': requestId,
|
||||
}
|
||||
|
||||
// Only send IP if privacy mode is OFF
|
||||
@@ -41,15 +100,15 @@ export async function fetchSuggestion(prefix, suffix, signal, apiUrl = API_URL)
|
||||
user_preferences: {
|
||||
language: settings.language,
|
||||
currency: settings.currency,
|
||||
timezone: settings.detectedTimezone
|
||||
}
|
||||
timezone: settings.detectedTimezone,
|
||||
},
|
||||
}
|
||||
|
||||
const res = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
signal
|
||||
signal,
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -95,5 +154,9 @@ export async function fetchSuggestion(prefix, suffix, signal, apiUrl = API_URL)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} finally {
|
||||
if (signal) {
|
||||
signal.removeEventListener('abort', onAbort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user