feat: add web search block functionality and integrate with existing plugins
- Introduced a new web search block plugin to handle web search queries and results. - Updated copilot, doc block, and pro block plugins to include web search context in AI completions. - Implemented utility functions for parsing and building web search markdown. - Enhanced API to support web search requests and responses. - Added configuration for web search URL and timeout settings. - Updated size limit checks to account for web search content.
This commit is contained in:
@@ -3,6 +3,8 @@ import {
|
||||
API_KEY,
|
||||
PRO_URL,
|
||||
PRO_FRONTEND_TIMEOUT_MS,
|
||||
WEB_SEARCH_URL,
|
||||
WEB_SEARCH_FRONTEND_TIMEOUT_MS,
|
||||
TTS_URL,
|
||||
TTS_STATUS_URL,
|
||||
TTS_CONFIG_URL,
|
||||
@@ -81,6 +83,9 @@ function getCancelUrl(apiUrl) {
|
||||
if (/\/v1\/pro\/completions$/i.test(normalized)) {
|
||||
return normalized.replace(/\/v1\/pro\/completions$/i, '/v1/pro/completions/cancel')
|
||||
}
|
||||
if (/\/v1\/web-search$/i.test(normalized)) {
|
||||
return normalized.replace(/\/v1\/web-search$/i, '/v1/web-search/cancel')
|
||||
}
|
||||
if (/\/v1\/completions$/i.test(normalized)) {
|
||||
return normalized.replace(/\/v1\/completions$/i, '/v1/completions/cancel')
|
||||
}
|
||||
@@ -298,6 +303,57 @@ export async function fetchProSuggestionStream(payload, apiUrl = PRO_URL) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchWebSearchStream(payload, apiUrl = WEB_SEARCH_URL) {
|
||||
const {
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
languageId = 'markdown',
|
||||
signal,
|
||||
timeoutMs = WEB_SEARCH_FRONTEND_TIMEOUT_MS,
|
||||
onEvent,
|
||||
} = payload || {}
|
||||
|
||||
const settings = useSettingsStore()
|
||||
const requestId = generateRequestId()
|
||||
|
||||
return consumeSseJson({
|
||||
url: apiUrl,
|
||||
requestId,
|
||||
signal,
|
||||
timeoutMs,
|
||||
body: {
|
||||
prefix,
|
||||
suffix,
|
||||
languageId: String(languageId || 'markdown').trim() || 'markdown',
|
||||
privacy_mode: settings.privacyMode,
|
||||
user_preferences: {
|
||||
language: settings.language,
|
||||
currency: settings.currency,
|
||||
timezone: settings.detectedTimezone,
|
||||
},
|
||||
},
|
||||
onEvent(event, data) {
|
||||
if (event === 'queued' || event === 'started' || event === 'resource') {
|
||||
onEvent?.(event, data)
|
||||
return
|
||||
}
|
||||
if (event === 'progress') {
|
||||
onEvent?.(String(data?.phase || ''), data)
|
||||
return
|
||||
}
|
||||
if (event === 'error') {
|
||||
onEvent?.('error', data)
|
||||
}
|
||||
},
|
||||
onDone(data) {
|
||||
return {
|
||||
content: String(data?.content || ''),
|
||||
createdAt: String(data?.created_at || data?.createdAt || ''),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchTTS(text, instruct = '', apiUrl = TTS_URL) {
|
||||
const requestId = generateRequestId()
|
||||
return consumeSseJson({
|
||||
|
||||
@@ -6,6 +6,8 @@ const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL)
|
||||
export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions`
|
||||
export const PRO_URL = import.meta.env.VITE_PRO_URL || `${API_BASE_URL}/v1/pro/completions`
|
||||
export const PRO_FRONTEND_TIMEOUT_MS = Number(import.meta.env.VITE_PRO_FRONTEND_TIMEOUT_MS || 3660000)
|
||||
export const WEB_SEARCH_URL = import.meta.env.VITE_WEB_SEARCH_URL || `${API_BASE_URL}/v1/web-search`
|
||||
export const WEB_SEARCH_FRONTEND_TIMEOUT_MS = Number(import.meta.env.VITE_WEB_SEARCH_FRONTEND_TIMEOUT_MS || 3660000)
|
||||
export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr`
|
||||
export const CONVERT_URL = import.meta.env.VITE_CONVERT_URL || `${API_BASE_URL}/v1/convert`
|
||||
export const EXPORT_PDF_URL = import.meta.env.VITE_EXPORT_PDF_URL || `${API_BASE_URL}/v1/export/pdf`
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
export const WEB_SEARCH_NODE_TYPE = 'web_search_block'
|
||||
export const WEB_SEARCH_TRIGGER_TEXT = '[WEBSEARCH]'
|
||||
export const WEB_SEARCH_RESULT_FENCE_LANG = 'llm-websearch'
|
||||
export const WEB_SEARCH_CONTEXT_LIMIT = 32 * 1024
|
||||
|
||||
const WEB_SEARCH_TRIGGER_RE = /^\[websearch\]$/i
|
||||
const WEB_SEARCH_RESULT_RE = /(^|\n)(`{3,})llm-websearch(?:\s+date=([^\s`]+))?[^\n]*\n([\s\S]*?)\n\2(?=\n|$)/g
|
||||
|
||||
function normalizeMarkdownText(value = '') {
|
||||
return String(value || '').replace(/\r\n?/g, '\n')
|
||||
}
|
||||
|
||||
function pickFence(content = '') {
|
||||
const matches = String(content || '').match(/`{3,}/g) || []
|
||||
const maxLen = matches.reduce((max, item) => Math.max(max, item.length), 2)
|
||||
return '`'.repeat(maxLen + 1)
|
||||
}
|
||||
|
||||
function sanitizeDate(value = '') {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) return ''
|
||||
const parsed = new Date(text)
|
||||
if (Number.isNaN(parsed.getTime())) return ''
|
||||
return parsed.toISOString()
|
||||
}
|
||||
|
||||
function clipText(value = '', limit = 0) {
|
||||
if (!limit || value.length <= limit) return value
|
||||
return `${value.slice(0, limit)}...`
|
||||
}
|
||||
|
||||
export function parseWebSearchTriggerSyntax(value = '') {
|
||||
const text = normalizeMarkdownText(value).trim()
|
||||
if (!WEB_SEARCH_TRIGGER_RE.test(text)) return null
|
||||
return {
|
||||
content: '',
|
||||
createdAt: '',
|
||||
collapsed: false,
|
||||
autoStart: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildWebSearchResultMarkdown(attrs = {}) {
|
||||
const content = normalizeMarkdownText(attrs.content || '').trimEnd()
|
||||
const createdAt = sanitizeDate(attrs.createdAt) || new Date().toISOString()
|
||||
const fence = pickFence(content)
|
||||
return `${fence}${WEB_SEARCH_RESULT_FENCE_LANG} date=${createdAt}\n${content}\n${fence}`
|
||||
}
|
||||
|
||||
export function parseWebSearchResultMarkdown(value = '', meta = '') {
|
||||
const content = normalizeMarkdownText(value).replace(/\s+$/, '')
|
||||
const dateMatch = String(meta || '').match(/(?:^|\s)date=([^\s`]+)/i)
|
||||
return {
|
||||
content,
|
||||
createdAt: sanitizeDate(dateMatch?.[1] || ''),
|
||||
collapsed: false,
|
||||
autoStart: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildWebSearchContextFence(attrs = {}) {
|
||||
const content = normalizeMarkdownText(attrs.content || '').trim()
|
||||
if (!content) return ''
|
||||
const fence = pickFence(content)
|
||||
return `${fence}markdown\n${content}\n${fence}`
|
||||
}
|
||||
|
||||
export function extractWebSearchContextFromMarkdown(markdown = '', contentLimit = 0) {
|
||||
const normalized = normalizeMarkdownText(markdown)
|
||||
const contexts = []
|
||||
|
||||
normalized.replace(WEB_SEARCH_RESULT_RE, (_full, _prefix, _fence, date, content) => {
|
||||
const fence = buildWebSearchContextFence({
|
||||
content: clipText(normalizeMarkdownText(content || '').trim(), contentLimit),
|
||||
createdAt: date || '',
|
||||
})
|
||||
if (fence) contexts.push(fence)
|
||||
return _full
|
||||
})
|
||||
|
||||
return contexts.join('\n\n')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user