dhti-cli 0.1.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/compose.js +19 -7
- package/dist/commands/conch.js +35 -16
- package/dist/commands/elixir.js +10 -5
- package/dist/commands/mimic.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/resources/docker-compose-master.yml +238 -0
- package/dist/resources/genai/Dockerfile +27 -0
- package/dist/resources/genai/README.md +1 -0
- package/dist/resources/genai/app/__init__.py +0 -0
- package/dist/resources/genai/app/bootstrap.py +43 -0
- package/dist/resources/genai/app/server.py +57 -0
- package/dist/resources/genai/pyproject.toml +84 -0
- package/dist/resources/genai/whl/placeholder.md +0 -0
- package/dist/resources/spa/Dockerfile +16 -0
- package/dist/resources/spa/def/importmap.json +39 -0
- package/dist/resources/spa/def/routes.registry.json +1599 -0
- package/dist/resources/spa/def/spa-assemble-config.json +40 -0
- package/dist/utils/card.d.ts +4 -4
- package/dist/utils/card.js +7 -0
- package/dist/utils/getCard.d.ts +2 -2
- package/dist/utils/getCard.js +2 -2
- package/dist/utils/index.d.ts +2 -4
- package/dist/utils/index.js +2 -4
- package/dist/utils/useDhti.d.ts +1 -1
- package/dist/utils/useDhti.js +1 -1
- package/oclif.manifest.json +6 -6
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -234,6 +234,8 @@ You can remove the services by: `dhti-cli docker -d`
|
|
|
234
234
|
## Give us a star ⭐️
|
|
235
235
|
If you find this project useful, give us a star. It helps others discover the project.
|
|
236
236
|
|
|
237
|
+
## [Details of CLI Commands](/notes/README.md)
|
|
238
|
+
|
|
237
239
|
## Contributors
|
|
238
240
|
|
|
239
241
|
* [Bell Eapen](https://nuchange.ca) | [](https://twitter.com/beapen)
|
package/dist/commands/compose.js
CHANGED
|
@@ -2,18 +2,26 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import yaml from 'js-yaml';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
5
7
|
export default class Compose extends Command {
|
|
6
8
|
static args = {
|
|
7
9
|
op: Args.string({ description: 'Operation to perform (add, delete, read or reset)' }),
|
|
8
10
|
};
|
|
9
11
|
static description = 'Generates a docker-compose.yml file from a list of modules';
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %>',
|
|
12
|
-
];
|
|
12
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
13
13
|
static flags = {
|
|
14
|
-
file: Flags.string({
|
|
14
|
+
file: Flags.string({
|
|
15
|
+
char: 'f',
|
|
16
|
+
default: `${os.homedir()}/dhti/docker-compose.yml`,
|
|
17
|
+
description: 'Full path to the docker compose file to read from. Creates if it does not exist',
|
|
18
|
+
}),
|
|
15
19
|
// flag with a value (-n, --name=VALUE)
|
|
16
|
-
module: Flags.string({
|
|
20
|
+
module: Flags.string({
|
|
21
|
+
char: 'm',
|
|
22
|
+
description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir)',
|
|
23
|
+
multiple: true,
|
|
24
|
+
}),
|
|
17
25
|
};
|
|
18
26
|
static init = () => {
|
|
19
27
|
// Create ${os.homedir()}/dhti if it does not exist
|
|
@@ -27,6 +35,10 @@ export default class Compose extends Command {
|
|
|
27
35
|
};
|
|
28
36
|
async run() {
|
|
29
37
|
const { args, flags } = await this.parse(Compose);
|
|
38
|
+
// Resolve resources directory for both dev (src) and packaged (dist)
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
+
const __dirname = path.dirname(__filename);
|
|
41
|
+
const RESOURCES_DIR = path.resolve(__dirname, '../resources');
|
|
30
42
|
// console.log('args', args) //args { op: 'add' }
|
|
31
43
|
// console.log('flags', flags) //flags { module: [ 'default', 'langserve', 'redis' ] }
|
|
32
44
|
const openmrs = ['gateway', 'frontend', 'backend', 'openmrs-db'];
|
|
@@ -51,10 +63,10 @@ export default class Compose extends Command {
|
|
|
51
63
|
ollama,
|
|
52
64
|
openmrs,
|
|
53
65
|
redis,
|
|
54
|
-
webui
|
|
66
|
+
webui,
|
|
55
67
|
};
|
|
56
68
|
try {
|
|
57
|
-
const masterData = yaml.load(fs.readFileSync('
|
|
69
|
+
const masterData = yaml.load(fs.readFileSync(path.join(RESOURCES_DIR, 'docker-compose-master.yml'), 'utf8'));
|
|
58
70
|
let existingData = { services: {}, version: '3.8' };
|
|
59
71
|
if (fs.existsSync(flags.file)) {
|
|
60
72
|
existingData = yaml.load(fs.readFileSync(flags.file, 'utf8'));
|
package/dist/commands/conch.js
CHANGED
|
@@ -2,28 +2,44 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import { exec } from 'node:child_process';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
5
7
|
export default class Conch extends Command {
|
|
6
8
|
static args = {
|
|
7
9
|
op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
|
|
8
10
|
};
|
|
9
11
|
static description = 'Install or uninstall conchs to create a Docker image';
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %>',
|
|
12
|
-
];
|
|
12
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
13
13
|
static flags = {
|
|
14
|
-
branch: Flags.string({ char: 'b', default:
|
|
15
|
-
container: Flags.string({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
branch: Flags.string({ char: 'b', default: 'develop', description: 'Branch to install from' }),
|
|
15
|
+
container: Flags.string({
|
|
16
|
+
char: 'c',
|
|
17
|
+
default: 'dhti-frontend-1',
|
|
18
|
+
description: 'Name of the container to copy the conch to while in dev mode',
|
|
19
|
+
}),
|
|
20
|
+
dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
|
|
21
|
+
git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
|
|
22
|
+
image: Flags.string({
|
|
23
|
+
char: 'i',
|
|
24
|
+
default: 'openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17',
|
|
25
|
+
description: 'Base image to use for the conch',
|
|
26
|
+
}),
|
|
19
27
|
name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
|
|
20
|
-
repoVersion: Flags.string({ char: 'v', default:
|
|
21
|
-
workdir: Flags.string({
|
|
28
|
+
repoVersion: Flags.string({ char: 'v', default: '1.0.0', description: 'Version of the conch' }),
|
|
29
|
+
workdir: Flags.string({
|
|
30
|
+
char: 'w',
|
|
31
|
+
default: `${os.homedir()}/dhti`,
|
|
32
|
+
description: 'Working directory to install the conch',
|
|
33
|
+
}),
|
|
22
34
|
};
|
|
23
35
|
async run() {
|
|
24
36
|
const { args, flags } = await this.parse(Conch);
|
|
37
|
+
// Resolve resources directory for both dev (src) and packaged (dist)
|
|
38
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
39
|
+
const __dirname = path.dirname(__filename);
|
|
40
|
+
const RESOURCES_DIR = path.resolve(__dirname, '../resources');
|
|
25
41
|
if (!flags.name) {
|
|
26
|
-
console.log(
|
|
42
|
+
console.log('Please provide a name for the conch');
|
|
27
43
|
this.exit(1);
|
|
28
44
|
}
|
|
29
45
|
// if arg is dev then copy to docker as below
|
|
@@ -50,14 +66,14 @@ export default class Conch extends Command {
|
|
|
50
66
|
});
|
|
51
67
|
}
|
|
52
68
|
catch (error) {
|
|
53
|
-
console.log(
|
|
69
|
+
console.log('Error copying conch to container', error);
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
72
|
// Create a directory to install the elixir
|
|
57
73
|
if (!fs.existsSync(`${flags.workdir}/conch`)) {
|
|
58
74
|
fs.mkdirSync(`${flags.workdir}/conch`);
|
|
59
75
|
}
|
|
60
|
-
fs.cpSync('
|
|
76
|
+
fs.cpSync(path.join(RESOURCES_DIR, 'spa'), `${flags.workdir}/conch`, { recursive: true });
|
|
61
77
|
// Rewrite files
|
|
62
78
|
const rewrite = () => {
|
|
63
79
|
flags.name = flags.name ?? 'openmrs-esm-genai';
|
|
@@ -77,16 +93,19 @@ export default class Conch extends Command {
|
|
|
77
93
|
fs.writeFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, JSON.stringify(spaAssembleConfig, null, 2));
|
|
78
94
|
// Read and process Dockerfile
|
|
79
95
|
let dockerfile = fs.readFileSync(`${flags.workdir}/conch/Dockerfile`, 'utf8');
|
|
80
|
-
dockerfile = dockerfile
|
|
96
|
+
dockerfile = dockerfile
|
|
97
|
+
.replaceAll('conch', flags.name)
|
|
98
|
+
.replaceAll('version', flags.repoVersion)
|
|
99
|
+
.replaceAll('server-image', flags.image);
|
|
81
100
|
fs.writeFileSync(`${flags.workdir}/conch/Dockerfile`, dockerfile);
|
|
82
101
|
// Read routes.json
|
|
83
102
|
const routes = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/${flags.name}/src/routes.json`, 'utf8'));
|
|
84
103
|
// Add to routes.registry.json
|
|
85
104
|
const registry = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/routes.registry.json`, 'utf8'));
|
|
86
105
|
if (args.op === 'install')
|
|
87
|
-
registry[
|
|
106
|
+
registry[flags.name.replace('openmrs-', '@openmrs/')] = routes;
|
|
88
107
|
if (args.op === 'uninstall')
|
|
89
|
-
delete registry[
|
|
108
|
+
delete registry[flags.name.replace('openmrs-', '@openmrs/')];
|
|
90
109
|
fs.writeFileSync(`${flags.workdir}/conch/def/routes.registry.json`, JSON.stringify(registry, null, 2));
|
|
91
110
|
};
|
|
92
111
|
if (flags.git !== 'none') {
|
package/dist/commands/elixir.js
CHANGED
|
@@ -3,6 +3,7 @@ import { exec } from 'node:child_process';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
export default class Elixir extends Command {
|
|
7
8
|
static args = {
|
|
8
9
|
op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
|
|
@@ -34,6 +35,10 @@ export default class Elixir extends Command {
|
|
|
34
35
|
};
|
|
35
36
|
async run() {
|
|
36
37
|
const { args, flags } = await this.parse(Elixir);
|
|
38
|
+
// Resolve resources directory for both dev (src) and packaged (dist)
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
+
const __dirname = path.dirname(__filename);
|
|
41
|
+
const RESOURCES_DIR = path.resolve(__dirname, '../resources');
|
|
37
42
|
if (!flags.name) {
|
|
38
43
|
console.log('Please provide a name for the elixir');
|
|
39
44
|
this.exit(1);
|
|
@@ -69,7 +74,7 @@ export default class Elixir extends Command {
|
|
|
69
74
|
if (!fs.existsSync(`${flags.workdir}/elixir`)) {
|
|
70
75
|
fs.mkdirSync(`${flags.workdir}/elixir`);
|
|
71
76
|
}
|
|
72
|
-
fs.cpSync('
|
|
77
|
+
fs.cpSync(path.join(RESOURCES_DIR, 'genai'), `${flags.workdir}/elixir`, { recursive: true });
|
|
73
78
|
// if whl is not none, copy the whl file to thee whl directory
|
|
74
79
|
if (flags.whl !== 'none') {
|
|
75
80
|
if (!fs.existsSync(`${flags.workdir}/elixir/whl/`)) {
|
|
@@ -105,13 +110,13 @@ mcp_server.add_tool(${expoName}_mcp_tool) # type: ignore
|
|
|
105
110
|
`;
|
|
106
111
|
const newCliImport = fs
|
|
107
112
|
.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
|
|
108
|
-
.replace('#DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
|
|
113
|
+
.replace('# DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
|
|
109
114
|
const langfuseRoute = `add_routes(app, ${expoName}_chain.with_config(config), path="/langserve/${expoName}")`;
|
|
110
|
-
const newLangfuseRoute = newCliImport.replace('#DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
|
|
115
|
+
const newLangfuseRoute = newCliImport.replace('# DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
|
|
111
116
|
const normalRoute = `add_routes(app, ${expoName}_chain, path="/langserve/${expoName}")`;
|
|
112
|
-
const newNormalRoute = newLangfuseRoute.replace('#DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
|
|
117
|
+
const newNormalRoute = newLangfuseRoute.replace('# DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
|
|
113
118
|
const commonRoutes = `\nadd_invokes(app, path="/langserve/${expoName}")\nadd_services(app, path="/langserve/${expoName}")`;
|
|
114
|
-
const finalRoute = newNormalRoute.replace('#DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
|
|
119
|
+
const finalRoute = newNormalRoute.replace('# DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
|
|
115
120
|
// if args.op === install, add the line to the pyproject.toml file
|
|
116
121
|
if (args.op === 'install') {
|
|
117
122
|
fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
|
package/dist/commands/mimic.js
CHANGED
|
@@ -145,12 +145,13 @@ export default class Mimic extends Command {
|
|
|
145
145
|
body: mimic_request,
|
|
146
146
|
headers: {
|
|
147
147
|
'Content-Type': 'application/fhir+json',
|
|
148
|
-
|
|
148
|
+
Prefer: 'respond-async',
|
|
149
149
|
},
|
|
150
150
|
method: 'POST',
|
|
151
151
|
});
|
|
152
152
|
if (!response.ok) {
|
|
153
153
|
console.error(`Error: ${response.status} ${response.statusText}`);
|
|
154
|
+
return;
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { Cards, handleBundle } from './utils/index.js';
|
|
2
2
|
export { run } from '@oclif/core';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { Cards, handleBundle } from './utils/index.js';
|
|
2
2
|
export { run } from '@oclif/core';
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
version: "3.7"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
gateway:
|
|
5
|
+
image: beapen/dhti-gateway:latest
|
|
6
|
+
restart: "unless-stopped"
|
|
7
|
+
pull_policy: always
|
|
8
|
+
depends_on:
|
|
9
|
+
- frontend
|
|
10
|
+
- backend
|
|
11
|
+
ports:
|
|
12
|
+
- "80:80"
|
|
13
|
+
- "9000:80"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
frontend:
|
|
17
|
+
image: openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17
|
|
18
|
+
# image: openmrs/openmrs-reference-application-3-frontend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
|
|
19
|
+
ports:
|
|
20
|
+
- "8003:80"
|
|
21
|
+
restart: "unless-stopped"
|
|
22
|
+
pull_policy: always
|
|
23
|
+
environment:
|
|
24
|
+
SPA_PATH: /openmrs/spa
|
|
25
|
+
API_URL: /openmrs
|
|
26
|
+
SPA_CONFIG_URLS: /openmrs/spa/config-core_demo.json
|
|
27
|
+
SPA_DEFAULT_LOCALE:
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "curl", "-f", "http://localhost/"]
|
|
30
|
+
timeout: 5s
|
|
31
|
+
depends_on:
|
|
32
|
+
- backend
|
|
33
|
+
# volumes:
|
|
34
|
+
# - ./spa:/usr/share/nginx/html
|
|
35
|
+
|
|
36
|
+
backend:
|
|
37
|
+
image: openmrs/openmrs-reference-application-3-backend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
|
|
38
|
+
ports:
|
|
39
|
+
- "8002:8080"
|
|
40
|
+
restart: "unless-stopped"
|
|
41
|
+
depends_on:
|
|
42
|
+
- openmrs-db
|
|
43
|
+
environment:
|
|
44
|
+
OMRS_CONFIG_MODULE_WEB_ADMIN: "true"
|
|
45
|
+
OMRS_CONFIG_AUTO_UPDATE_DATABASE: "true"
|
|
46
|
+
OMRS_CONFIG_CREATE_TABLES: "true"
|
|
47
|
+
OMRS_CONFIG_CONNECTION_SERVER: openmrs-db
|
|
48
|
+
OMRS_CONFIG_CONNECTION_DATABASE: openmrs
|
|
49
|
+
OMRS_CONFIG_CONNECTION_USERNAME: ${OPENMRS_DB_USER:-openmrs}
|
|
50
|
+
OMRS_CONFIG_CONNECTION_PASSWORD: ${OPENMRS_DB_PASSWORD:-openmrs}
|
|
51
|
+
healthcheck:
|
|
52
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/openmrs"]
|
|
53
|
+
timeout: 5s
|
|
54
|
+
volumes:
|
|
55
|
+
- openmrs-data:/openmrs/data
|
|
56
|
+
|
|
57
|
+
openmrs-db:
|
|
58
|
+
image: mariadb:10.11.7
|
|
59
|
+
restart: "unless-stopped"
|
|
60
|
+
command: "mysqld --character-set-server=utf8 --collation-server=utf8_general_ci"
|
|
61
|
+
healthcheck:
|
|
62
|
+
test: "mysql --user=${OMRS_DB_USER:-openmrs} --password=${OMRS_DB_PASSWORD:-openmrs} --execute \"SHOW DATABASES;\""
|
|
63
|
+
interval: 3s
|
|
64
|
+
timeout: 1s
|
|
65
|
+
retries: 5
|
|
66
|
+
environment:
|
|
67
|
+
MYSQL_DATABASE: openmrs
|
|
68
|
+
MYSQL_USER: ${OMRS_DB_USER:-openmrs}
|
|
69
|
+
MYSQL_PASSWORD: ${OMRS_DB_PASSWORD:-openmrs}
|
|
70
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-openmrs}
|
|
71
|
+
volumes:
|
|
72
|
+
- openmrs-db:/var/lib/mysql
|
|
73
|
+
|
|
74
|
+
langserve:
|
|
75
|
+
image: beapen/genai:latest
|
|
76
|
+
pull_policy: always
|
|
77
|
+
ports:
|
|
78
|
+
- "8001:8001"
|
|
79
|
+
restart: "unless-stopped"
|
|
80
|
+
environment:
|
|
81
|
+
- OLLAMA_SERVER_URL==http://ollama:11434
|
|
82
|
+
- OLLAMA_WEBUI=http://ollama-webui:8080
|
|
83
|
+
- LANGFUSE_HOST=http://langfuse:3000
|
|
84
|
+
- LANGFUSE_PUBLIC_KEY=pk-lf-abcd
|
|
85
|
+
- LANGFUSE_SECRET_KEY=sk-lf-abcd
|
|
86
|
+
|
|
87
|
+
ollama:
|
|
88
|
+
image: ollama/ollama:latest
|
|
89
|
+
ports:
|
|
90
|
+
- 11434:11434
|
|
91
|
+
volumes:
|
|
92
|
+
- ollama-code:/code
|
|
93
|
+
- ollama-root:/root/.ollama
|
|
94
|
+
tty: true
|
|
95
|
+
restart: "unless-stopped"
|
|
96
|
+
environment:
|
|
97
|
+
- OLLAMA_ORIGINS=*
|
|
98
|
+
|
|
99
|
+
ollama-webui:
|
|
100
|
+
image: ghcr.io/open-webui/open-webui:main
|
|
101
|
+
volumes:
|
|
102
|
+
- ollama-webui:/app/backend/data
|
|
103
|
+
depends_on:
|
|
104
|
+
- ollama
|
|
105
|
+
ports:
|
|
106
|
+
- 8080:8080
|
|
107
|
+
environment:
|
|
108
|
+
- '/ollama/api=http://ollama:11434/api'
|
|
109
|
+
extra_hosts:
|
|
110
|
+
- host.docker.internal:host-gateway
|
|
111
|
+
restart: unless-stopped
|
|
112
|
+
|
|
113
|
+
fhir:
|
|
114
|
+
image: alphora/cqf-ruler:0.14.0 # includes cql
|
|
115
|
+
ports:
|
|
116
|
+
- 8005:8080
|
|
117
|
+
restart: "unless-stopped"
|
|
118
|
+
depends_on:
|
|
119
|
+
- postgres-db
|
|
120
|
+
environment:
|
|
121
|
+
- "spring.datasource.url=jdbc:postgresql://postgres-db:5432/postgres"
|
|
122
|
+
- "spring.datasource.username=postgres"
|
|
123
|
+
- "spring.datasource.password=postgres"
|
|
124
|
+
- "spring.datasource.driverClassName=org.postgresql.Driver"
|
|
125
|
+
- "spring.jpa.properties.hibernate.dialect=ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect"
|
|
126
|
+
- "hapi.fhir.fhir_version=R4"
|
|
127
|
+
- "hapi.fhir.cors.allowed-origins=*"
|
|
128
|
+
- "hapi.fhir.cors.allowCredentials=true"
|
|
129
|
+
- "hapi.fhir.bulkdata.enabled=true"
|
|
130
|
+
- "hapi.fhir.bulk_export_enabled=true"
|
|
131
|
+
- "hapi.fhir.bulk_import_enabled=true"
|
|
132
|
+
- "hapi.fhir.enforce_referential_integrity_on_write=false"
|
|
133
|
+
- "hapi.fhir.enforce_referential_integrity_on_delete=false"
|
|
134
|
+
|
|
135
|
+
mcp-fhir:
|
|
136
|
+
image: beapen/fhir-mcp-server:1.0
|
|
137
|
+
ports:
|
|
138
|
+
- 8006:8000
|
|
139
|
+
restart: "unless-stopped"
|
|
140
|
+
depends_on:
|
|
141
|
+
- fhir
|
|
142
|
+
environment:
|
|
143
|
+
- FHIR_SERVER_BASE_URL="http://fhir:8005/baseR4"
|
|
144
|
+
- FHIR_SERVER_SCOPES="*"
|
|
145
|
+
- FHIR_SERVER_ACCESS_TOKEN="none"
|
|
146
|
+
|
|
147
|
+
cql-elm:
|
|
148
|
+
image: cqframework/cql-translation-service:latest
|
|
149
|
+
ports:
|
|
150
|
+
- 8091:8080
|
|
151
|
+
restart: "unless-stopped"
|
|
152
|
+
|
|
153
|
+
cql-web:
|
|
154
|
+
image: beapen/cql_runner:latest
|
|
155
|
+
ports:
|
|
156
|
+
- 8092:80
|
|
157
|
+
restart: "unless-stopped"
|
|
158
|
+
depends_on:
|
|
159
|
+
- fhir
|
|
160
|
+
|
|
161
|
+
langfuse:
|
|
162
|
+
image: ghcr.io/langfuse/langfuse:latest
|
|
163
|
+
depends_on:
|
|
164
|
+
- postgres-db
|
|
165
|
+
ports:
|
|
166
|
+
- "3000:3000"
|
|
167
|
+
environment:
|
|
168
|
+
- DATABASE_URL=postgresql://postgres:postgres@postgres-db:5432/postgres
|
|
169
|
+
- NEXTAUTH_SECRET=mysecret
|
|
170
|
+
- SALT=mysalt
|
|
171
|
+
- NEXTAUTH_URL=http://langfuse:3000
|
|
172
|
+
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false}
|
|
173
|
+
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
|
|
174
|
+
|
|
175
|
+
postgres-db:
|
|
176
|
+
image: postgres
|
|
177
|
+
restart: "unless-stopped"
|
|
178
|
+
environment:
|
|
179
|
+
- POSTGRES_USER=postgres
|
|
180
|
+
- POSTGRES_PASSWORD=postgres
|
|
181
|
+
- POSTGRES_DB=postgres
|
|
182
|
+
ports:
|
|
183
|
+
- 5432:5432
|
|
184
|
+
volumes:
|
|
185
|
+
- postgres-db:/var/lib/postgresql/data
|
|
186
|
+
|
|
187
|
+
redis:
|
|
188
|
+
image: redislabs/redisearch:2.8.8
|
|
189
|
+
ports:
|
|
190
|
+
- 6379:6379
|
|
191
|
+
restart: "unless-stopped"
|
|
192
|
+
volumes:
|
|
193
|
+
- redis-db:/data
|
|
194
|
+
|
|
195
|
+
redis-commander:
|
|
196
|
+
image: rediscommander/redis-commander:latest
|
|
197
|
+
restart: "unless-stopped"
|
|
198
|
+
environment:
|
|
199
|
+
- REDIS_HOSTS=local:redis:6379
|
|
200
|
+
ports:
|
|
201
|
+
- "8081:8081"
|
|
202
|
+
|
|
203
|
+
neo4j:
|
|
204
|
+
image: neo4j:5.1-enterprise
|
|
205
|
+
ports:
|
|
206
|
+
- 7474:7474
|
|
207
|
+
- 7687:7687
|
|
208
|
+
environment:
|
|
209
|
+
- NEO4J_AUTH=neo4j/password
|
|
210
|
+
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
|
|
211
|
+
- NEO4J_PLUGINS=["apoc", "graph-data-science", "n10s"]
|
|
212
|
+
- NEO4J_dbms_security_procedures_unrestricted=apoc.*,gds.*,n10s.*
|
|
213
|
+
- NEO4J_dbms_security_procedures_whitelist=apoc.*,gds.*,n10s.*
|
|
214
|
+
restart: "unless-stopped"
|
|
215
|
+
volumes:
|
|
216
|
+
- neo4j-db:/data
|
|
217
|
+
|
|
218
|
+
fhirg:
|
|
219
|
+
image: beapen/fhirg:latest
|
|
220
|
+
ports:
|
|
221
|
+
- 8004:8080
|
|
222
|
+
restart: "unless-stopped"
|
|
223
|
+
environment:
|
|
224
|
+
- spring.neo4j.uri=bolt://neo4j:7687
|
|
225
|
+
- spring.neo4j.authentication.username=neo4j
|
|
226
|
+
- spring.neo4j.authentication.password=password
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
volumes:
|
|
230
|
+
openmrs-data: ~
|
|
231
|
+
openmrs-db: ~
|
|
232
|
+
fhir-db: ~
|
|
233
|
+
postgres-db: ~
|
|
234
|
+
redis-db: ~
|
|
235
|
+
neo4j-db: ~
|
|
236
|
+
ollama-code: ~
|
|
237
|
+
ollama-root: ~
|
|
238
|
+
ollama-webui: ~
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Use Python 3.12 slim as base image
|
|
2
|
+
FROM python:3.12-slim AS base
|
|
3
|
+
|
|
4
|
+
# Update package lists and install Git
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
apt-get install -y --no-install-recommends git && \
|
|
7
|
+
rm -rf /var/lib/apt/lists/*
|
|
8
|
+
|
|
9
|
+
# Install uv
|
|
10
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
11
|
+
|
|
12
|
+
# Set working directory
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
# Copy pyproject.toml into the image
|
|
16
|
+
COPY pyproject.toml /app/pyproject.toml
|
|
17
|
+
COPY README.md /app/README.md
|
|
18
|
+
|
|
19
|
+
# Install dependencies and generate uv.lock
|
|
20
|
+
RUN uv sync --no-dev
|
|
21
|
+
|
|
22
|
+
# Copy the project into the image
|
|
23
|
+
COPY . /app
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Run the server
|
|
27
|
+
CMD ["uv", "run", "python", "app/server.py"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# README
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from kink import di
|
|
2
|
+
from os import getenv
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
from langchain_core.prompts import PromptTemplate
|
|
5
|
+
from langchain_core.language_models.fake import FakeListLLM
|
|
6
|
+
|
|
7
|
+
## Override the default configuration of elixirs here if needed
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def bootstrap():
|
|
11
|
+
load_dotenv()
|
|
12
|
+
fake_llm = FakeListLLM(responses=["Paris", "I don't know"])
|
|
13
|
+
di["main_prompt"] = PromptTemplate.from_template(
|
|
14
|
+
"Summarize the following in 100 words: {input}"
|
|
15
|
+
)
|
|
16
|
+
di["main_llm"] = fake_llm
|
|
17
|
+
di["cds_hook_discovery"] = {
|
|
18
|
+
"services": [
|
|
19
|
+
{
|
|
20
|
+
"id": "dhti-service",
|
|
21
|
+
"hook": "order-select",
|
|
22
|
+
"title": "MyOrg Order Assistant",
|
|
23
|
+
"description": "Provides suggestions and actions for selected draft orders, including handling CommunicationRequest resources.",
|
|
24
|
+
"prefetch": {
|
|
25
|
+
"patient": "Patient/{{context.patientId}}",
|
|
26
|
+
"draftOrders": "Bundle?patient={{context.patientId}}&status=draft",
|
|
27
|
+
},
|
|
28
|
+
"scopes": [
|
|
29
|
+
"launch",
|
|
30
|
+
"patient/Patient.read",
|
|
31
|
+
"user/Practitioner.read",
|
|
32
|
+
"patient/CommunicationRequest.read",
|
|
33
|
+
],
|
|
34
|
+
"metadata": {
|
|
35
|
+
"author": "MyOrg CDS Team",
|
|
36
|
+
"version": "1.0.0",
|
|
37
|
+
"supportedResources": [
|
|
38
|
+
"CommunicationRequest",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from langserve import add_routes
|
|
3
|
+
from langchain_core.runnables.config import RunnableConfig
|
|
4
|
+
from dhti_elixir_base.cds_hook.routes import add_services, add_invokes
|
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
mcp_server = FastMCP(name="dhti-mcp-server")
|
|
8
|
+
|
|
9
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
10
|
+
# DHTI_CLI_IMPORT
|
|
11
|
+
import uvicorn
|
|
12
|
+
|
|
13
|
+
# Comes after elixir bootstraps, so can override elixir configurations
|
|
14
|
+
from bootstrap import bootstrap
|
|
15
|
+
bootstrap()
|
|
16
|
+
|
|
17
|
+
app = FastAPI(title="dhti-elixir-server")
|
|
18
|
+
# Mount the MCP server's ASGI application at a specific path (Exposes /messages and /sse endpoints)
|
|
19
|
+
app.mount("/langserve/mcp", mcp_server.sse_app())
|
|
20
|
+
|
|
21
|
+
origins = [
|
|
22
|
+
"*",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
app.add_middleware(
|
|
26
|
+
CORSMiddleware,
|
|
27
|
+
allow_origins=origins,
|
|
28
|
+
allow_credentials=True,
|
|
29
|
+
allow_methods=["*"],
|
|
30
|
+
allow_headers=["*"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Define a root endpoint
|
|
34
|
+
@app.get("/langserve")
|
|
35
|
+
async def read_root():
|
|
36
|
+
return {"message": "Hello from DHTI!"}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from langfuse import Langfuse
|
|
40
|
+
from langfuse.callback import CallbackHandler
|
|
41
|
+
langfuse_handler = CallbackHandler()
|
|
42
|
+
langfuse_handler.auth_check()
|
|
43
|
+
config = RunnableConfig(callbacks=[langfuse_handler])
|
|
44
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
45
|
+
# DHTI_LANGFUSE_ROUTE
|
|
46
|
+
|
|
47
|
+
except:
|
|
48
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
49
|
+
# DHTI_NORMAL_ROUTE
|
|
50
|
+
x = True
|
|
51
|
+
|
|
52
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
53
|
+
# DHTI_COMMON_ROUTE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
uvicorn.run(app, host="0.0.0.0", port=8001)
|