fix: conversation items (#6815)
This commit is contained in:
parent
e46200868e
commit
f07e43cfe0
4
.github/workflows/jan-server-web-ci-dev.yml
vendored
4
.github/workflows/jan-server-web-ci-dev.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
build-and-preview:
|
||||
runs-on: [ubuntu-24-04-docker]
|
||||
env:
|
||||
JAN_API_BASE: "https://api-dev.menlo.ai/v1"
|
||||
MENLO_PLATFORM_BASE_URL: "https://api-dev.menlo.ai/v1"
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||
docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||
|
||||
- name: Push docker image
|
||||
if: github.event_name == 'push'
|
||||
|
||||
4
.github/workflows/jan-server-web-ci-prod.yml
vendored
4
.github/workflows/jan-server-web-ci-prod.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
env:
|
||||
JAN_API_BASE: "https://api.menlo.ai/v1"
|
||||
MENLO_PLATFORM_BASE_URL: "https://api.menlo.ai/v1"
|
||||
GA_MEASUREMENT_ID: "G-YK53MX8M8M"
|
||||
CLOUDFLARE_PROJECT_NAME: "jan-server-web"
|
||||
steps:
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: make config-yarn && yarn install && yarn build:core && make build-web-app
|
||||
env:
|
||||
JAN_API_BASE: ${{ env.JAN_API_BASE }}
|
||||
MENLO_PLATFORM_BASE_URL: ${{ env.MENLO_PLATFORM_BASE_URL }}
|
||||
GA_MEASUREMENT_ID: ${{ env.GA_MEASUREMENT_ID }}
|
||||
|
||||
- name: Publish to Cloudflare Pages Production
|
||||
|
||||
4
.github/workflows/jan-server-web-ci-stag.yml
vendored
4
.github/workflows/jan-server-web-ci-stag.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
build-and-preview:
|
||||
runs-on: [ubuntu-24-04-docker]
|
||||
env:
|
||||
JAN_API_BASE: "https://api-stag.menlo.ai/v1"
|
||||
MENLO_PLATFORM_BASE_URL: "https://api-stag.menlo.ai/v1"
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||
docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||
|
||||
- name: Push docker image
|
||||
if: github.event_name == 'push'
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# Stage 1: Build stage with Node.js and Yarn v4
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
ARG JAN_API_BASE=https://api-dev.jan.ai/v1
|
||||
ENV JAN_API_BASE=$JAN_API_BASE
|
||||
ARG MENLO_PLATFORM_BASE_URL=https://api-dev.menlo.ai/v1
|
||||
ENV MENLO_PLATFORM_BASE_URL=$MENLO_PLATFORM_BASE_URL
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache \
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
ListConversationItemsResponse
|
||||
} from './types'
|
||||
|
||||
declare const JAN_API_BASE: string
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
|
||||
export class RemoteApi {
|
||||
private authService: JanAuthService
|
||||
@ -28,7 +28,7 @@ export class RemoteApi {
|
||||
async createConversation(
|
||||
data: Conversation
|
||||
): Promise<ConversationResponse> {
|
||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATIONS}`
|
||||
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATIONS}`
|
||||
|
||||
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
||||
url,
|
||||
@ -43,7 +43,7 @@ export class RemoteApi {
|
||||
conversationId: string,
|
||||
data: Conversation
|
||||
): Promise<ConversationResponse> {
|
||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||
|
||||
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
||||
url,
|
||||
@ -70,7 +70,7 @@ export class RemoteApi {
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATIONS}${queryString ? `?${queryString}` : ''}`
|
||||
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATIONS}${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return this.authService.makeAuthenticatedRequest<ListConversationsResponse>(
|
||||
url,
|
||||
@ -114,7 +114,7 @@ export class RemoteApi {
|
||||
}
|
||||
|
||||
async deleteConversation(conversationId: string): Promise<void> {
|
||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||
|
||||
await this.authService.makeAuthenticatedRequest(
|
||||
url,
|
||||
@ -141,7 +141,7 @@ export class RemoteApi {
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_ITEMS(conversationId)}${queryString ? `?${queryString}` : ''}`
|
||||
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_ITEMS(conversationId)}${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return this.authService.makeAuthenticatedRequest<ListConversationItemsResponse>(
|
||||
url,
|
||||
|
||||
@ -31,7 +31,7 @@ export interface ConversationResponse {
|
||||
id: string
|
||||
object: 'conversation'
|
||||
title?: string
|
||||
created_at: number
|
||||
created_at: number | string
|
||||
metadata: ConversationMetadata
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ export interface ConversationItemAnnotation {
|
||||
}
|
||||
|
||||
export interface ConversationItemContent {
|
||||
type?: string
|
||||
file?: {
|
||||
file_id?: string
|
||||
mime_type?: string
|
||||
@ -62,23 +63,50 @@ export interface ConversationItemContent {
|
||||
file_id?: string
|
||||
url?: string
|
||||
}
|
||||
image_file?: {
|
||||
file_id?: string
|
||||
mime_type?: string
|
||||
}
|
||||
input_text?: string
|
||||
output_text?: {
|
||||
annotations?: ConversationItemAnnotation[]
|
||||
text?: string
|
||||
}
|
||||
reasoning_content?: string
|
||||
text?: {
|
||||
value?: string
|
||||
text?: string
|
||||
}
|
||||
type?: string
|
||||
reasoning_content?: string
|
||||
tool_calls?: Array<{
|
||||
id?: string
|
||||
type?: string
|
||||
function?: {
|
||||
name?: string
|
||||
arguments?: string
|
||||
}
|
||||
}>
|
||||
tool_call_id?: string
|
||||
tool_result?: {
|
||||
content?: Array<{
|
||||
type?: string
|
||||
text?: string
|
||||
output_text?: {
|
||||
text?: string
|
||||
}
|
||||
}>
|
||||
output_text?: {
|
||||
text?: string
|
||||
}
|
||||
}
|
||||
text_result?: string
|
||||
}
|
||||
|
||||
export interface ConversationItem {
|
||||
content?: ConversationItemContent[]
|
||||
created_at: number
|
||||
created_at: number | string
|
||||
id: string
|
||||
object: string
|
||||
metadata?: Record<string, unknown>
|
||||
role: string
|
||||
status?: string
|
||||
type?: string
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Thread, ThreadAssistantInfo, ThreadMessage, ContentType } from '@janhq/core'
|
||||
import { Conversation, ConversationResponse, ConversationItem } from './types'
|
||||
import { Conversation, ConversationResponse, ConversationItem, ConversationItemContent, ConversationMetadata } from './types'
|
||||
import { DEFAULT_ASSISTANT } from './const'
|
||||
|
||||
export class ObjectParser {
|
||||
@ -7,7 +7,7 @@ export class ObjectParser {
|
||||
const modelName = thread.assistants?.[0]?.model?.id || undefined
|
||||
const modelProvider = thread.assistants?.[0]?.model?.engine || undefined
|
||||
const isFavorite = thread.metadata?.is_favorite?.toString() || 'false'
|
||||
let metadata = {}
|
||||
let metadata: ConversationMetadata = {}
|
||||
if (modelName && modelProvider) {
|
||||
metadata = {
|
||||
model_id: modelName,
|
||||
@ -23,15 +23,14 @@ export class ObjectParser {
|
||||
|
||||
static conversationToThread(conversation: ConversationResponse): Thread {
|
||||
const assistants: ThreadAssistantInfo[] = []
|
||||
if (
|
||||
conversation.metadata?.model_id &&
|
||||
conversation.metadata?.model_provider
|
||||
) {
|
||||
const metadata: ConversationMetadata = conversation.metadata || {}
|
||||
|
||||
if (metadata.model_id && metadata.model_provider) {
|
||||
assistants.push({
|
||||
...DEFAULT_ASSISTANT,
|
||||
model: {
|
||||
id: conversation.metadata.model_id,
|
||||
engine: conversation.metadata.model_provider,
|
||||
id: metadata.model_id,
|
||||
engine: metadata.model_provider,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
@ -44,16 +43,18 @@ export class ObjectParser {
|
||||
})
|
||||
}
|
||||
|
||||
const isFavorite = conversation.metadata?.is_favorite === 'true'
|
||||
const isFavorite = metadata.is_favorite === 'true'
|
||||
const createdAtMs = parseTimestamp(conversation.created_at)
|
||||
|
||||
return {
|
||||
id: conversation.id,
|
||||
title: conversation.title || '',
|
||||
assistants,
|
||||
created: conversation.created_at,
|
||||
updated: conversation.created_at,
|
||||
created: createdAtMs,
|
||||
updated: createdAtMs,
|
||||
model: {
|
||||
id: conversation.metadata.model_id,
|
||||
provider: conversation.metadata.model_provider,
|
||||
id: metadata.model_id,
|
||||
provider: metadata.model_provider,
|
||||
},
|
||||
isFavorite,
|
||||
metadata: { is_favorite: isFavorite },
|
||||
@ -65,74 +66,70 @@ export class ObjectParser {
|
||||
threadId: string
|
||||
): ThreadMessage {
|
||||
// Extract text content and metadata from the item
|
||||
let textContent = ''
|
||||
let reasoningContent = ''
|
||||
const textSegments: string[] = []
|
||||
const reasoningSegments: string[] = []
|
||||
const imageUrls: string[] = []
|
||||
let toolCalls: any[] = []
|
||||
let finishReason = ''
|
||||
|
||||
if (item.content && item.content.length > 0) {
|
||||
for (const content of item.content) {
|
||||
// Handle text content
|
||||
if (content.text?.value) {
|
||||
textContent = content.text.value
|
||||
}
|
||||
// Handle output_text for assistant messages
|
||||
if (content.output_text?.text) {
|
||||
textContent = content.output_text.text
|
||||
}
|
||||
// Handle reasoning content
|
||||
if (content.reasoning_content) {
|
||||
reasoningContent = content.reasoning_content
|
||||
}
|
||||
// Handle image content
|
||||
if (content.image?.url) {
|
||||
imageUrls.push(content.image.url)
|
||||
}
|
||||
// Extract finish_reason
|
||||
if (content.finish_reason) {
|
||||
finishReason = content.finish_reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tool calls parsing for assistant messages
|
||||
if (item.role === 'assistant' && finishReason === 'tool_calls') {
|
||||
try {
|
||||
// Tool calls are embedded as JSON string in textContent
|
||||
const toolCallMatch = textContent.match(/\[.*\]/)
|
||||
if (toolCallMatch) {
|
||||
const toolCallsData = JSON.parse(toolCallMatch[0])
|
||||
toolCalls = toolCallsData.map((toolCall: any) => ({
|
||||
tool: {
|
||||
id: toolCall.id || 'unknown',
|
||||
function: {
|
||||
name: toolCall.function?.name || 'unknown',
|
||||
arguments: toolCall.function?.arguments || '{}'
|
||||
},
|
||||
type: toolCall.type || 'function'
|
||||
},
|
||||
response: {
|
||||
error: '',
|
||||
content: []
|
||||
},
|
||||
state: 'ready'
|
||||
}))
|
||||
// Remove tool calls JSON from text content, keep only reasoning
|
||||
textContent = ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse tool calls:', error)
|
||||
extractContentByType(content, {
|
||||
onText: (value) => {
|
||||
if (value) {
|
||||
textSegments.push(value)
|
||||
}
|
||||
},
|
||||
onReasoning: (value) => {
|
||||
if (value) {
|
||||
reasoningSegments.push(value)
|
||||
}
|
||||
},
|
||||
onImage: (url) => {
|
||||
if (url) {
|
||||
imageUrls.push(url)
|
||||
}
|
||||
},
|
||||
onToolCalls: (calls) => {
|
||||
toolCalls = calls.map((toolCall) => {
|
||||
const callId = toolCall.id || 'unknown'
|
||||
const rawArgs = toolCall.function?.arguments
|
||||
const normalizedArgs =
|
||||
typeof rawArgs === 'string'
|
||||
? rawArgs
|
||||
: JSON.stringify(rawArgs ?? {})
|
||||
return {
|
||||
id: callId,
|
||||
tool_call_id: callId,
|
||||
tool: {
|
||||
id: callId,
|
||||
function: {
|
||||
name: toolCall.function?.name || 'unknown',
|
||||
arguments: normalizedArgs,
|
||||
},
|
||||
type: toolCall.type || 'function',
|
||||
},
|
||||
response: {
|
||||
error: '',
|
||||
content: [],
|
||||
},
|
||||
state: 'pending',
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Format final content with reasoning if present
|
||||
let finalTextValue = ''
|
||||
if (reasoningContent) {
|
||||
finalTextValue = `<think>${reasoningContent}</think>`
|
||||
if (reasoningSegments.length > 0) {
|
||||
finalTextValue += `<think>${reasoningSegments.join('\n')}</think>`
|
||||
}
|
||||
if (textContent) {
|
||||
finalTextValue += textContent
|
||||
if (textSegments.length > 0) {
|
||||
if (finalTextValue) {
|
||||
finalTextValue += '\n'
|
||||
}
|
||||
finalTextValue += textSegments.join('\n')
|
||||
}
|
||||
|
||||
// Build content array for ThreadMessage
|
||||
@ -157,22 +154,26 @@ export class ObjectParser {
|
||||
}
|
||||
|
||||
// Build metadata
|
||||
const metadata: any = {}
|
||||
const metadata: any = { ...(item.metadata || {}) }
|
||||
if (toolCalls.length > 0) {
|
||||
metadata.tool_calls = toolCalls
|
||||
}
|
||||
|
||||
const createdAtMs = parseTimestamp(item.created_at)
|
||||
|
||||
// Map status from server format to frontend format
|
||||
const mappedStatus = item.status === 'completed' ? 'ready' : item.status || 'ready'
|
||||
|
||||
const role = item.role === 'user' || item.role === 'assistant' ? item.role : 'assistant'
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
id: item.id,
|
||||
object: 'thread.message',
|
||||
thread_id: threadId,
|
||||
role: item.role as 'user' | 'assistant',
|
||||
role,
|
||||
content: messageContent,
|
||||
created_at: item.created_at * 1000, // Convert to milliseconds
|
||||
created_at: createdAtMs,
|
||||
completed_at: 0,
|
||||
status: mappedStatus,
|
||||
metadata,
|
||||
@ -201,25 +202,46 @@ export const combineConversationItemsToMessages = (
|
||||
): ThreadMessage[] => {
|
||||
const messages: ThreadMessage[] = []
|
||||
const toolResponseMap = new Map<string, any>()
|
||||
const sortedItems = [...items].sort(
|
||||
(a, b) => parseTimestamp(a.created_at) - parseTimestamp(b.created_at)
|
||||
)
|
||||
|
||||
// First pass: collect tool responses
|
||||
for (const item of items) {
|
||||
for (const item of sortedItems) {
|
||||
if (item.role === 'tool') {
|
||||
const toolContent = item.content?.[0]?.text?.value || ''
|
||||
toolResponseMap.set(item.id, {
|
||||
error: '',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: toolContent
|
||||
}
|
||||
]
|
||||
})
|
||||
for (const content of item.content ?? []) {
|
||||
const toolCallId = content.tool_call_id || item.id
|
||||
const toolResultText =
|
||||
content.tool_result?.output_text?.text ||
|
||||
(Array.isArray(content.tool_result?.content)
|
||||
? content.tool_result?.content
|
||||
?.map((entry) => entry.text || entry.output_text?.text)
|
||||
.filter((text): text is string => Boolean(text))
|
||||
.join('\n')
|
||||
: undefined)
|
||||
const toolContent =
|
||||
content.text?.text ||
|
||||
content.text?.value ||
|
||||
content.output_text?.text ||
|
||||
content.input_text ||
|
||||
content.text_result ||
|
||||
toolResultText ||
|
||||
''
|
||||
toolResponseMap.set(toolCallId, {
|
||||
error: '',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: toolContent,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: build messages and merge tool responses
|
||||
for (const item of items) {
|
||||
for (const item of sortedItems) {
|
||||
// Skip tool messages as they will be merged into assistant messages
|
||||
if (item.role === 'tool') {
|
||||
continue
|
||||
@ -228,14 +250,35 @@ export const combineConversationItemsToMessages = (
|
||||
const message = ObjectParser.conversationItemToThreadMessage(item, threadId)
|
||||
|
||||
// If this is an assistant message with tool calls, merge tool responses
|
||||
if (message.role === 'assistant' && message.metadata?.tool_calls && Array.isArray(message.metadata.tool_calls)) {
|
||||
if (
|
||||
message.role === 'assistant' &&
|
||||
message.metadata?.tool_calls &&
|
||||
Array.isArray(message.metadata.tool_calls)
|
||||
) {
|
||||
const toolCalls = message.metadata.tool_calls as any[]
|
||||
let toolResponseIndex = 0
|
||||
|
||||
for (const [responseId, responseData] of toolResponseMap.entries()) {
|
||||
if (toolResponseIndex < toolCalls.length) {
|
||||
toolCalls[toolResponseIndex].response = responseData
|
||||
toolResponseIndex++
|
||||
for (const toolCall of toolCalls) {
|
||||
const callId = toolCall.tool_call_id || toolCall.id || toolCall.tool?.id
|
||||
let responseKey: string | undefined
|
||||
let response: any = null
|
||||
|
||||
if (callId && toolResponseMap.has(callId)) {
|
||||
responseKey = callId
|
||||
response = toolResponseMap.get(callId)
|
||||
} else {
|
||||
const iterator = toolResponseMap.entries().next()
|
||||
if (!iterator.done) {
|
||||
responseKey = iterator.value[0]
|
||||
response = iterator.value[1]
|
||||
}
|
||||
}
|
||||
|
||||
if (response) {
|
||||
toolCall.response = response
|
||||
toolCall.state = 'succeeded'
|
||||
if (responseKey) {
|
||||
toolResponseMap.delete(responseKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,3 +288,79 @@ export const combineConversationItemsToMessages = (
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
const parseTimestamp = (value: number | string | undefined): number => {
|
||||
if (typeof value === 'number') {
|
||||
// Distinguish between seconds and milliseconds
|
||||
return value > 1e12 ? value : value * 1000
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parsed = Date.parse(value)
|
||||
return Number.isNaN(parsed) ? Date.now() : parsed
|
||||
}
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
const extractContentByType = (
|
||||
content: ConversationItemContent,
|
||||
handlers: {
|
||||
onText: (value: string) => void
|
||||
onReasoning: (value: string) => void
|
||||
onImage: (url: string) => void
|
||||
onToolCalls: (calls: NonNullable<ConversationItemContent['tool_calls']>) => void
|
||||
}
|
||||
) => {
|
||||
const type = content.type || ''
|
||||
|
||||
switch (type) {
|
||||
case 'input_text':
|
||||
handlers.onText(content.input_text || '')
|
||||
break
|
||||
case 'text':
|
||||
handlers.onText(content.text?.text || content.text?.value || '')
|
||||
break
|
||||
case 'output_text':
|
||||
handlers.onText(content.output_text?.text || '')
|
||||
break
|
||||
case 'reasoning_content':
|
||||
handlers.onReasoning(content.reasoning_content || '')
|
||||
break
|
||||
case 'image':
|
||||
case 'image_url':
|
||||
if (content.image?.url) {
|
||||
handlers.onImage(content.image.url)
|
||||
}
|
||||
break
|
||||
case 'tool_calls':
|
||||
if (content.tool_calls && Array.isArray(content.tool_calls)) {
|
||||
handlers.onToolCalls(content.tool_calls)
|
||||
}
|
||||
break
|
||||
case 'tool_result':
|
||||
if (content.tool_result?.output_text?.text) {
|
||||
handlers.onText(content.tool_result.output_text.text)
|
||||
}
|
||||
break
|
||||
default:
|
||||
// Fallback for legacy fields without explicit type
|
||||
if (content.text?.value || content.text?.text) {
|
||||
handlers.onText(content.text.value || content.text.text || '')
|
||||
}
|
||||
if (content.text_result) {
|
||||
handlers.onText(content.text_result)
|
||||
}
|
||||
if (content.output_text?.text) {
|
||||
handlers.onText(content.output_text.text)
|
||||
}
|
||||
if (content.reasoning_content) {
|
||||
handlers.onReasoning(content.reasoning_content)
|
||||
}
|
||||
if (content.image?.url) {
|
||||
handlers.onImage(content.image.url)
|
||||
}
|
||||
if (content.tool_calls && Array.isArray(content.tool_calls)) {
|
||||
handlers.onToolCalls(content.tool_calls)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { ApiError } from '../shared/types/errors'
|
||||
import { JAN_API_ROUTES } from './const'
|
||||
import { JanModel, janProviderStore } from './store'
|
||||
|
||||
// JAN_API_BASE is defined in vite.config.ts
|
||||
// MENLO_PLATFORM_BASE_URL is defined in vite.config.ts
|
||||
|
||||
// Constants
|
||||
const TEMPORARY_CHAT_ID = 'temporary-chat'
|
||||
@ -20,7 +20,7 @@ const TEMPORARY_CHAT_ID = 'temporary-chat'
|
||||
*/
|
||||
function getChatCompletionConfig(request: JanChatCompletionRequest, stream: boolean = false) {
|
||||
const isTemporaryChat = request.conversation_id === TEMPORARY_CHAT_ID
|
||||
const endpoint = `${JAN_API_BASE}${JAN_API_ROUTES.CHAT_COMPLETIONS}`
|
||||
const endpoint = `${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.CHAT_COMPLETIONS}`
|
||||
|
||||
const payload = {
|
||||
...request,
|
||||
@ -162,7 +162,7 @@ export class JanApiClient {
|
||||
|
||||
this.modelsFetchPromise = (async () => {
|
||||
const response = await this.authService.makeAuthenticatedRequest<JanModelsResponse>(
|
||||
`${JAN_API_BASE}${JAN_API_ROUTES.MODELS}`
|
||||
`${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.MODELS}`
|
||||
)
|
||||
|
||||
const summaries = response.data || []
|
||||
@ -327,7 +327,7 @@ export class JanApiClient {
|
||||
|
||||
private async fetchSupportedParameters(modelId: string): Promise<string[]> {
|
||||
try {
|
||||
const endpoint = `${JAN_API_BASE}${JAN_API_ROUTES.MODEL_CATALOGS}/${this.encodeModelIdForCatalog(modelId)}`
|
||||
const endpoint = `${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.MODEL_CATALOGS}/${this.encodeModelIdForCatalog(modelId)}`
|
||||
const catalog = await this.authService.makeAuthenticatedRequest<JanModelCatalogResponse>(endpoint)
|
||||
return this.extractSupportedParameters(catalog)
|
||||
} catch (error) {
|
||||
|
||||
@ -12,8 +12,8 @@ import { JanMCPOAuthProvider } from './oauth-provider'
|
||||
import { WebSearchButton } from './components'
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
// JAN_API_BASE is defined in vite.config.ts (defaults to 'https://api-dev.jan.ai/jan/v1')
|
||||
declare const JAN_API_BASE: string
|
||||
// MENLO_PLATFORM_BASE_URL is defined in vite.config.ts (defaults to 'https://api-dev.menlo.ai/jan/v1')
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
|
||||
export default class MCPExtensionWeb extends MCPExtension {
|
||||
private mcpEndpoint = '/mcp'
|
||||
@ -77,7 +77,7 @@ export default class MCPExtensionWeb extends MCPExtension {
|
||||
|
||||
// Create transport with OAuth provider (handles token refresh automatically)
|
||||
const transport = new StreamableHTTPClientTransport(
|
||||
new URL(`${JAN_API_BASE}${this.mcpEndpoint}`),
|
||||
new URL(`${MENLO_PLATFORM_BASE_URL}${this.mcpEndpoint}`),
|
||||
{
|
||||
authProvider: this.oauthProvider
|
||||
// No sessionId needed - server will generate one automatically
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
import { AuthTokens } from './types'
|
||||
import { AUTH_ENDPOINTS } from './const'
|
||||
|
||||
declare const JAN_API_BASE: string
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
|
||||
/**
|
||||
* Logout user on server
|
||||
*/
|
||||
export async function logoutUser(): Promise<void> {
|
||||
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.LOGOUT}`, {
|
||||
const response = await fetch(`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.LOGOUT}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@ -29,7 +29,7 @@ export async function logoutUser(): Promise<void> {
|
||||
* Guest login
|
||||
*/
|
||||
export async function guestLogin(): Promise<AuthTokens> {
|
||||
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.GUEST_LOGIN}`, {
|
||||
const response = await fetch(`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.GUEST_LOGIN}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@ -51,7 +51,7 @@ export async function guestLogin(): Promise<AuthTokens> {
|
||||
*/
|
||||
export async function refreshToken(): Promise<AuthTokens> {
|
||||
const response = await fetch(
|
||||
`${JAN_API_BASE}${AUTH_ENDPOINTS.REFRESH_TOKEN}`,
|
||||
`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.REFRESH_TOKEN}`,
|
||||
{
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
|
||||
import { AuthTokens, LoginUrlResponse } from './types'
|
||||
|
||||
declare const JAN_API_BASE: string
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
|
||||
export async function getLoginUrl(endpoint: string): Promise<LoginUrlResponse> {
|
||||
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
|
||||
const response: Response = await fetch(`${MENLO_PLATFORM_BASE_URL}${endpoint}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@ -30,7 +30,7 @@ export async function handleOAuthCallback(
|
||||
code: string,
|
||||
state?: string
|
||||
): Promise<AuthTokens> {
|
||||
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
|
||||
const response: Response = await fetch(`${MENLO_PLATFORM_BASE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Handles authentication flows for any OAuth provider
|
||||
*/
|
||||
|
||||
declare const JAN_API_BASE: string
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
|
||||
import { User, AuthState, AuthBroadcastMessage, AuthTokens } from './types'
|
||||
import {
|
||||
@ -429,7 +429,7 @@ export class JanAuthService {
|
||||
private async fetchUserProfile(): Promise<User | null> {
|
||||
try {
|
||||
return await this.makeAuthenticatedRequest<User>(
|
||||
`${JAN_API_BASE}${AUTH_ENDPOINTS.ME}`
|
||||
`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.ME}`
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user profile:', error)
|
||||
|
||||
2
extensions-web/src/types/global.d.ts
vendored
2
extensions-web/src/types/global.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
declare const JAN_API_BASE: string
|
||||
declare const MENLO_PLATFORM_BASE_URL: string
|
||||
}
|
||||
|
||||
@ -14,6 +14,6 @@ export default defineConfig({
|
||||
emptyOutDir: false // Don't clean the output directory
|
||||
},
|
||||
define: {
|
||||
JAN_API_BASE: JSON.stringify(process.env.JAN_API_BASE || 'https://api-dev.jan.ai/v1'),
|
||||
MENLO_PLATFORM_BASE_URL: JSON.stringify(process.env.MENLO_PLATFORM_BASE_URL || 'https://api-dev.menlo.ai/v1'),
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user