@@ -23,15 +23,18 @@ import type { Server } from 'http';
23
23
import path from 'node:path' ;
24
24
import http from 'node:http' ;
25
25
import { existsSync } from 'fs' ;
26
- import { getFreeRandomPort } from '../utils/ports' ;
27
26
import * as podmanDesktopApi from '@podman-desktop/api' ;
28
27
import { readFile } from 'fs/promises' ;
29
28
import type { ModelsManager } from './modelsManager' ;
30
29
import type { components } from '../../src-generated/openapi' ;
31
30
import type { ModelInfo } from '@shared/src/models/IModelInfo' ;
31
+ import type { ConfigurationRegistry } from '../registries/ConfigurationRegistry' ;
32
+ import { getFreeRandomPort } from '../utils/ports' ;
32
33
33
- const DEFAULT_PORT = 10434 ;
34
34
const SHOW_API_INFO_COMMAND = 'ai-lab.show-api-info' ;
35
+ const SHOW_API_ERROR_COMMAND = 'ai-lab.show-api-error' ;
36
+
37
+ export const PREFERENCE_RANDOM_PORT = 0 ;
35
38
36
39
type ListModelResponse = components [ 'schemas' ] [ 'ListModelResponse' ] ;
37
40
@@ -52,6 +55,7 @@ export class ApiServer implements Disposable {
52
55
constructor (
53
56
private extensionContext : podmanDesktopApi . ExtensionContext ,
54
57
private modelsManager : ModelsManager ,
58
+ private configurationRegistry : ConfigurationRegistry ,
55
59
) { }
56
60
57
61
protected getListener ( ) : Server | undefined {
@@ -72,22 +76,25 @@ export class ApiServer implements Disposable {
72
76
app . use ( '/spec' , this . getSpec . bind ( this ) ) ;
73
77
74
78
const server = http . createServer ( app ) ;
75
- let listeningOn = DEFAULT_PORT ;
79
+ let listeningOn = this . configurationRegistry . getExtensionConfiguration ( ) . apiPort ;
76
80
server . on ( 'listening' , ( ) => {
77
81
this . displayApiInfo ( listeningOn ) ;
78
82
} ) ;
79
83
server . on ( 'error' , ( ) => {
84
+ this . displayApiError ( listeningOn ) ;
85
+ } ) ;
86
+ if ( listeningOn === PREFERENCE_RANDOM_PORT ) {
80
87
getFreeRandomPort ( '0.0.0.0' )
81
88
. then ( ( randomPort : number ) => {
82
- console . warn ( `port ${ DEFAULT_PORT } in use, using ${ randomPort } for API server` ) ;
83
89
listeningOn = randomPort ;
84
- this . #listener = server . listen ( randomPort ) ;
90
+ this . #listener = server . listen ( listeningOn ) ;
85
91
} )
86
92
. catch ( ( e : unknown ) => {
87
93
console . error ( 'unable to get a free port for the api server' , e ) ;
88
94
} ) ;
89
- } ) ;
90
- this . #listener = server . listen ( DEFAULT_PORT ) ;
95
+ } else {
96
+ this . #listener = server . listen ( listeningOn ) ;
97
+ }
91
98
}
92
99
93
100
displayApiInfo ( port : number ) : void {
@@ -111,6 +118,23 @@ export class ApiServer implements Disposable {
111
118
apiStatusBarItem . show ( ) ;
112
119
}
113
120
121
+ displayApiError ( port : number ) : void {
122
+ const apiStatusBarItem = podmanDesktopApi . window . createStatusBarItem ( ) ;
123
+ apiStatusBarItem . text = `AI Lab API listening error` ;
124
+ apiStatusBarItem . command = SHOW_API_ERROR_COMMAND ;
125
+ this . extensionContext . subscriptions . push (
126
+ podmanDesktopApi . commands . registerCommand ( SHOW_API_ERROR_COMMAND , async ( ) => {
127
+ const address = `http://localhost:${ port } ` ;
128
+ await podmanDesktopApi . window . showErrorMessage (
129
+ `AI Lab API failed to listen on\n${ address } \nYou can change the port in the Preferences then restart the extension.` ,
130
+ 'OK' ,
131
+ ) ;
132
+ } ) ,
133
+ apiStatusBarItem ,
134
+ ) ;
135
+ apiStatusBarItem . show ( ) ;
136
+ }
137
+
114
138
private getFile ( filepath : string ) : string {
115
139
// when plugin is installed, the file is placed in the plugin directory (~/.local/share/containers/podman-desktop/plugins/<pluginname>/)
116
140
const prodFile = path . join ( __dirname , filepath ) ;
0 commit comments