Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions extensions-web/src/jan-provider-web/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Handles API requests to Jan backend for models and chat completions
*/

import { JanAuthService } from '../shared/auth'
import { getSharedAuthService, JanAuthService } from '../shared'
import { JanModel, janProviderStore } from './store'

Check warning on line 7 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

6-7 lines are not covered with tests

// JAN_API_BASE is defined in vite.config.ts

Expand Down Expand Up @@ -72,64 +72,64 @@
}>
}

export class JanApiClient {

Check warning on line 75 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

75 line is not covered with tests
private static instance: JanApiClient
private authService: JanAuthService

private constructor() {
this.authService = JanAuthService.getInstance()
this.authService = getSharedAuthService()
}

Check warning on line 81 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

79-81 lines are not covered with tests

static getInstance(): JanApiClient {
if (!JanApiClient.instance) {
JanApiClient.instance = new JanApiClient()
}
return JanApiClient.instance
}

Check warning on line 88 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

83-88 lines are not covered with tests

async getModels(): Promise<JanModel[]> {
try {
janProviderStore.setLoadingModels(true)
janProviderStore.clearError()

Check warning on line 93 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

90-93 lines are not covered with tests

const response = await this.authService.makeAuthenticatedRequest<JanModelsResponse>(
`${JAN_API_BASE}/models`
)

Check warning on line 97 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

95-97 lines are not covered with tests

const models = response.data || []
janProviderStore.setModels(models)

Check warning on line 100 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

99-100 lines are not covered with tests

return models
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch models'
janProviderStore.setError(errorMessage)
janProviderStore.setLoadingModels(false)
throw error
}
}

Check warning on line 109 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

102-109 lines are not covered with tests

async createChatCompletion(
request: JanChatCompletionRequest
): Promise<JanChatCompletionResponse> {
try {
janProviderStore.clearError()

Check warning on line 115 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

111-115 lines are not covered with tests

return await this.authService.makeAuthenticatedRequest<JanChatCompletionResponse>(
`${JAN_API_BASE}/chat/completions`,
{
method: 'POST',
body: JSON.stringify({
...request,
stream: false,
}),
}
)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to create chat completion'
janProviderStore.setError(errorMessage)
throw error
}
}

Check warning on line 132 in extensions-web/src/jan-provider-web/api.ts

View workflow job for this annotation

GitHub Actions / coverage-check

117-132 lines are not covered with tests

async createStreamingChatCompletion(
request: JanChatCompletionRequest,
Expand Down Expand Up @@ -216,12 +216,9 @@

async initialize(): Promise<void> {
try {
await this.authService.initialize()
janProviderStore.setAuthenticated(true)

// Fetch initial models
await this.getModels()

console.log('Jan API client initialized successfully')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize API client'
Expand Down
6 changes: 2 additions & 4 deletions extensions-web/src/mcp-web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { MCPExtension, MCPTool, MCPToolCallResult } from '@janhq/core'
import { JanAuthService } from '../shared/auth'
import { getSharedAuthService, JanAuthService } from '../shared'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { JanMCPOAuthProvider } from './oauth-provider'
Expand All @@ -30,14 +30,12 @@ export default class MCPExtensionWeb extends MCPExtension {
version?: string
) {
super(url, name, productName, active, description, version)
this.authService = JanAuthService.getInstance()
this.authService = getSharedAuthService()
this.oauthProvider = new JanMCPOAuthProvider(this.authService)
}

async onLoad(): Promise<void> {
try {
// Initialize authentication first
await this.authService.initialize()
// Initialize MCP client with OAuth
await this.initializeMCPClient()
// Then fetch tools
Expand Down
37 changes: 18 additions & 19 deletions extensions-web/src/shared/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,13 @@ const AUTH_STORAGE_KEY = 'jan_auth_tokens'
const TOKEN_EXPIRY_BUFFER = 60 * 1000 // 1 minute buffer before actual expiry

export class JanAuthService {
private static instance: JanAuthService
private tokens: AuthTokens | null = null
private tokenExpiryTime: number = 0

private constructor() {
constructor() {
this.loadTokensFromStorage()
}

static getInstance(): JanAuthService {
if (!JanAuthService.instance) {
JanAuthService.instance = new JanAuthService()
}
return JanAuthService.instance
}

private loadTokensFromStorage(): void {
try {
const storedTokens = localStorage.getItem(AUTH_STORAGE_KEY)
Expand Down Expand Up @@ -169,16 +161,6 @@ export class JanAuthService {
return this.tokens.access_token
}

async initialize(): Promise<void> {
try {
await this.getValidAccessToken()
console.log('Jan auth service initialized successfully')
} catch (error) {
console.error('Failed to initialize Jan auth service:', error)
throw error
}
}

async getAuthHeader(): Promise<{ Authorization: string }> {
const token = await this.getValidAccessToken()
return {
Expand Down Expand Up @@ -217,4 +199,21 @@ export class JanAuthService {
logout(): void {
this.clearTokens()
}
}

declare global {
interface Window {
janAuthService?: JanAuthService
}
}

/**
* Gets or creates the shared JanAuthService instance on the window object
* This ensures all extensions use the same auth service instance
*/
export function getSharedAuthService(): JanAuthService {
if (!window.janAuthService) {
window.janAuthService = new JanAuthService()
}
return window.janAuthService
}
2 changes: 1 addition & 1 deletion extensions-web/src/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { getSharedDB } from './db'
export { JanAuthService } from './auth'
export { JanAuthService, getSharedAuthService } from './auth'
export type { AuthTokens, AuthResponse } from './auth'
8 changes: 5 additions & 3 deletions web-app/src/lib/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const sendCompletion = async (

if (
thread.model.id &&
models[providerName]?.models !== true && // Skip if provider accepts any model (models: true)
!Object.values(models[providerName]).flat().includes(thread.model.id) &&
!tokenJS.extendedModelExist(providerName as any, thread.model.id) &&
provider.provider !== 'llamacpp'
Expand Down Expand Up @@ -396,9 +397,12 @@ export const postMessageProcessing = async (
let toolParameters = {}
if (toolCall.function.arguments.length) {
try {
console.log('Raw tool arguments:', toolCall.function.arguments)
toolParameters = JSON.parse(toolCall.function.arguments)
console.log('Parsed tool parameters:', toolParameters)
} catch (error) {
console.error('Failed to parse tool arguments:', error)
console.error('Raw arguments that failed:', toolCall.function.arguments)
}
}
const approved =
Expand All @@ -414,9 +418,7 @@ export const postMessageProcessing = async (

const { promise, cancel } = getServiceHub().mcp().callToolWithCancellation({
toolName: toolCall.function.name,
arguments: toolCall.function.arguments.length
? JSON.parse(toolCall.function.arguments)
: {},
arguments: toolCall.function.arguments.length ? toolParameters : {},
})

useAppState.getState().setCancelToolCall(cancel)
Expand Down
2 changes: 1 addition & 1 deletion web-app/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const AppLayout = () => {
{/* Fake absolute panel top to enable window drag */}
<div className="absolute w-full h-10 z-10" data-tauri-drag-region />
<DialogAppUpdater />
<BackendUpdater />
{PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && <BackendUpdater />}

{/* Use ResizablePanelGroup only on larger screens */}
{!isSmallScreen && isLeftPanelOpen ? (
Expand Down
Loading