NVIDIA NIM fixes (#4215)
* fix: udpate label to "NVIDIA NIM API Key" * test: update tag from ":latest" to ":1.8.0-rtx" * test: add image URL path "nvcr.io/nim/" * fix/nvidia-nim-2 (#4208) * fix: update nim-container-manager * feat: add "DeepSeek R1 Distill Llama 8B" * fix/nidia-nim-3 (#4209) * chore: add error message NVIDIA NIM is not installed. * chore: standardize NVIDIA NGC API Key * chore: capitalize Nvidia to NVIDIA * chore: generalize error message for chat models * fix/nvidia-nim-4-yau (#4212) * test: nimRelaxMemConstraints and hostPort * test: add logger for hostPort and nimRelaxMemConstraints * test: nim-container-manager version 1.0.9 * test: parseInt nimRelaxMemConstraints * test: update nim-container-manager version to 1.0.10 * chore: update nim-container-manager version to 1.0.11 * Update start container behaviour - show existing containers and give users the choice * Go back to previous step when clicking start new so user can change port number * Update condition for showing existing container dialog * Fix start new in different port not working * Update get container controller * Update again * fix: generalize error message for chat models * Update getContainer controller * Fix incorrect image check in getContainer controller * Update existing container dialog text * Fix styles in container exists dialog for nvidia nim --------- Co-authored-by: chungyau97 <chungyau97@gmail.com> Co-authored-by: Ong Chung Yau <33013947+chungyau97@users.noreply.github.com>
This commit is contained in:
parent
7867489727
commit
4fa2672c9d
|
|
@ -1,4 +1,4 @@
|
||||||
import { INodeParams, INodeCredential } from '../src/Interface'
|
import { INodeCredential, INodeParams } from '../src/Interface'
|
||||||
|
|
||||||
class NvidiaNIMApi implements INodeCredential {
|
class NvidiaNIMApi implements INodeCredential {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -8,12 +8,12 @@ class NvidiaNIMApi implements INodeCredential {
|
||||||
inputs: INodeParams[]
|
inputs: INodeParams[]
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Nvdia NIM API Key'
|
this.label = 'NVIDIA NGC API Key'
|
||||||
this.name = 'nvidiaNIMApi'
|
this.name = 'nvidiaNIMApi'
|
||||||
this.version = 1.0
|
this.version = 1.0
|
||||||
this.inputs = [
|
this.inputs = [
|
||||||
{
|
{
|
||||||
label: 'Nvidia NIM API Key',
|
label: 'NVIDIA NGC API Key',
|
||||||
name: 'nvidiaNIMApiKey',
|
name: 'nvidiaNIMApiKey',
|
||||||
type: 'password'
|
type: 'password'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
|
|
||||||
import { BaseCache } from '@langchain/core/caches'
|
import { BaseCache } from '@langchain/core/caches'
|
||||||
|
import { ChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
|
||||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
|
|
||||||
|
|
@ -16,13 +16,13 @@ class ChatNvdiaNIM_ChatModels implements INode {
|
||||||
inputs: INodeParams[]
|
inputs: INodeParams[]
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Chat Nvidia NIM'
|
this.label = 'Chat NVIDIA NIM'
|
||||||
this.name = 'chatNvidiaNIM'
|
this.name = 'Chat NVIDIA NIM'
|
||||||
this.version = 1.0
|
this.version = 1.1
|
||||||
this.type = 'ChatNvidiaNIM'
|
this.type = 'Chat NVIDIA NIM'
|
||||||
this.icon = 'nvdia.svg'
|
this.icon = 'nvdia.svg'
|
||||||
this.category = 'Chat Models'
|
this.category = 'Chat Models'
|
||||||
this.description = 'Wrapper around Nvdia NIM Inference API'
|
this.description = 'Wrapper around NVIDIA NIM Inference API'
|
||||||
this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
|
this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
|
||||||
this.credential = {
|
this.credential = {
|
||||||
label: 'Connect Credential',
|
label: 'Connect Credential',
|
||||||
|
|
@ -153,7 +153,7 @@ class ChatNvdiaNIM_ChatModels implements INode {
|
||||||
try {
|
try {
|
||||||
parsedBaseOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions)
|
parsedBaseOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions)
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
throw new Error("Invalid JSON in the ChatNvidiaNIM's baseOptions: " + exception)
|
throw new Error("Invalid JSON in the Chat NVIDIA NIM's baseOptions: " + exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"multer-s3": "^3.0.1",
|
"multer-s3": "^3.0.1",
|
||||||
"mysql2": "^3.11.3",
|
"mysql2": "^3.11.3",
|
||||||
"nim-container-manager": "^1.0.5",
|
"flowise-nim-container-manager": "^1.0.11",
|
||||||
"openai": "^4.82.0",
|
"openai": "^4.82.0",
|
||||||
"pg": "^8.11.1",
|
"pg": "^8.11.1",
|
||||||
"posthog-node": "^3.5.0",
|
"posthog-node": "^3.5.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Request, Response, NextFunction } from 'express'
|
import { NextFunction, Request, Response } from 'express'
|
||||||
|
|
||||||
const { NimContainerManager } = require('nim-container-manager')
|
const { NimContainerManager } = require('flowise-nim-container-manager')
|
||||||
|
|
||||||
const getToken = async (req: Request, res: Response, next: NextFunction) => {
|
const getToken = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -55,7 +55,13 @@ const startContainer = async (req: Request, res: Response, next: NextFunction) =
|
||||||
try {
|
try {
|
||||||
const imageTag = req.body.imageTag
|
const imageTag = req.body.imageTag
|
||||||
const apiKey = req.body.apiKey
|
const apiKey = req.body.apiKey
|
||||||
await NimContainerManager.startContainer(imageTag, apiKey)
|
const hostPort = req.body.hostPort
|
||||||
|
const nimRelaxMemConstraints = parseInt(req.body.nimRelaxMemConstraints)
|
||||||
|
// Validate nimRelaxMemConstraints
|
||||||
|
if (isNaN(nimRelaxMemConstraints) || (nimRelaxMemConstraints !== 0 && nimRelaxMemConstraints !== 1)) {
|
||||||
|
return res.status(400).send('nimRelaxMemConstraints must be 0 or 1')
|
||||||
|
}
|
||||||
|
await NimContainerManager.startContainer(imageTag, apiKey, hostPort, nimRelaxMemConstraints)
|
||||||
return res.send(`Starting container ${imageTag}`)
|
return res.send(`Starting container ${imageTag}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
|
|
@ -79,17 +85,51 @@ const getImage = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const getContainer = async (req: Request, res: Response, next: NextFunction) => {
|
const getContainer = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const imageTag = req.body.imageTag
|
const imageTag = req.body.imageTag
|
||||||
|
const port = req.body.port
|
||||||
|
|
||||||
|
// First check if the image exists
|
||||||
const images = await NimContainerManager.userImageLibrary()
|
const images = await NimContainerManager.userImageLibrary()
|
||||||
const image = images.find((img: any) => img.tag === imageTag)
|
const image = images.find((img: any) => img.tag === imageTag)
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).send(`Image ${imageTag} not found`)
|
return res.status(404).send(`Image ${imageTag} not found`)
|
||||||
}
|
}
|
||||||
if (!image.container) {
|
|
||||||
return res.status(404).send(`Container of ${imageTag} not found`)
|
const containers = await NimContainerManager.listRunningContainers()
|
||||||
|
const portInUse = containers.find((cont: any) => cont.port === port)
|
||||||
|
if (portInUse) {
|
||||||
|
const isModelContainer = portInUse.image === image.tag
|
||||||
|
if (isModelContainer) {
|
||||||
|
portInUse.image = image.name
|
||||||
|
return res.json(portInUse)
|
||||||
|
} else {
|
||||||
|
return res.status(409).send({
|
||||||
|
message: `Port ${port} is already in use by another container`,
|
||||||
|
container: portInUse
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const container = image.container
|
|
||||||
container.image = image.name
|
// If no container found with matching port, return 404
|
||||||
return res.json(container)
|
return res.status(404).send(`Container of ${imageTag} with port ${port} not found`)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listRunningContainers = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const containers = await NimContainerManager.listRunningContainers()
|
||||||
|
return res.json(containers)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopContainer = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const containerId = req.body.containerId
|
||||||
|
const containerInfo = await NimContainerManager.stopContainer(containerId)
|
||||||
|
return res.json(containerInfo)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -102,5 +142,7 @@ export default {
|
||||||
pullImage,
|
pullImage,
|
||||||
startContainer,
|
startContainer,
|
||||||
getImage,
|
getImage,
|
||||||
getContainer
|
getContainer,
|
||||||
|
listRunningContainers,
|
||||||
|
stopContainer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
// we need eslint because we have to pass next arg for the error middleware
|
// we need eslint because we have to pass next arg for the error middleware
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async function errorHandlerMiddleware(err: InternalFlowiseError, req: Request, res: Response, next: NextFunction) {
|
async function errorHandlerMiddleware(err: InternalFlowiseError, req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (err.message.includes('401 Incorrect API key provided'))
|
||||||
|
err.message = '401 Invalid model key or Incorrect local model configuration.'
|
||||||
let displayedError = {
|
let displayedError = {
|
||||||
statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
|
statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ const router = express.Router()
|
||||||
router.get('/preload', nimController.preload)
|
router.get('/preload', nimController.preload)
|
||||||
router.get('/get-token', nimController.getToken)
|
router.get('/get-token', nimController.getToken)
|
||||||
router.get('/download-installer', nimController.downloadInstaller)
|
router.get('/download-installer', nimController.downloadInstaller)
|
||||||
|
router.get('/list-running-containers', nimController.listRunningContainers)
|
||||||
router.post('/pull-image', nimController.pullImage)
|
router.post('/pull-image', nimController.pullImage)
|
||||||
router.post('/start-container', nimController.startContainer)
|
router.post('/start-container', nimController.startContainer)
|
||||||
|
router.post('/stop-container', nimController.stopContainer)
|
||||||
router.post('/get-image', nimController.getImage)
|
router.post('/get-image', nimController.getImage)
|
||||||
router.post('/get-container', nimController.getContainer)
|
router.post('/get-container', nimController.getContainer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ export class SSEStreamer implements IServerSideEventStreamer {
|
||||||
}
|
}
|
||||||
|
|
||||||
streamErrorEvent(chatId: string, msg: string) {
|
streamErrorEvent(chatId: string, msg: string) {
|
||||||
|
if (msg.includes('401 Incorrect API key provided')) msg = '401 Invalid model key or Incorrect local model configuration.'
|
||||||
const client = this.clients[chatId]
|
const client = this.clients[chatId]
|
||||||
if (client) {
|
if (client) {
|
||||||
const clientResponse = {
|
const clientResponse = {
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,39 @@
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
|
||||||
Button,
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Stepper,
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
Step,
|
Step,
|
||||||
StepLabel,
|
StepLabel,
|
||||||
Select,
|
Stepper,
|
||||||
MenuItem,
|
TextField
|
||||||
FormControl,
|
|
||||||
InputLabel
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
|
import axios from 'axios'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const modelOptions = {
|
const modelOptions = {
|
||||||
'nv-mistralai/mistral-nemo-12b-instruct:latest': {
|
'nvcr.io/nim/meta/llama-3.1-8b-instruct:1.8.0-RTX': {
|
||||||
label: 'Mistral Nemo 12B Instruct',
|
|
||||||
licenseUrl: 'https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nv-mistralai/containers/mistral-nemo-12b-instruct'
|
|
||||||
},
|
|
||||||
'meta/llama-3.1-8b-instruct-rtx:latest': {
|
|
||||||
label: 'Llama 3.1 8B Instruct',
|
label: 'Llama 3.1 8B Instruct',
|
||||||
licenseUrl: 'https://catalog.ngc.nvidia.com/orgs/nim/teams/meta/containers/llama-3.1-8b-instruct'
|
licenseUrl: 'https://catalog.ngc.nvidia.com/orgs/nim/teams/meta/containers/llama-3.1-8b-instruct'
|
||||||
|
},
|
||||||
|
'nvcr.io/nim/deepseek-ai/deepseek-r1-distill-llama-8b:1.8.0-RTX': {
|
||||||
|
label: 'DeepSeek R1 Distill Llama 8B',
|
||||||
|
licenseUrl: 'https://catalog.ngc.nvidia.com/orgs/nim/teams/deepseek-ai/containers/deepseek-r1-distill-llama-8b'
|
||||||
|
},
|
||||||
|
'nvcr.io/nim/nv-mistralai/mistral-nemo-12b-instruct:1.8.0-rtx': {
|
||||||
|
label: 'Mistral Nemo 12B Instruct',
|
||||||
|
licenseUrl: 'https://catalog.ngc.nvidia.com/orgs/nim/teams/nv-mistralai/containers/mistral-nemo-12b-instruct'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +41,10 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [imageTag, setImageTag] = useState('')
|
const [imageTag, setImageTag] = useState('')
|
||||||
const [pollInterval, setPollInterval] = useState(null)
|
const [pollInterval, setPollInterval] = useState(null)
|
||||||
|
const [nimRelaxMemConstraints, setNimRelaxMemConstraints] = useState('0')
|
||||||
|
const [hostPort, setHostPort] = useState('8080')
|
||||||
|
const [showContainerConfirm, setShowContainerConfirm] = useState(false)
|
||||||
|
const [existingContainer, setExistingContainer] = useState(null)
|
||||||
|
|
||||||
const steps = ['Download Installer', 'Pull Image', 'Start Container']
|
const steps = ['Download Installer', 'Pull Image', 'Start Container']
|
||||||
|
|
||||||
|
|
@ -137,34 +146,63 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const containerResponse = await axios.post('/api/v1/nvidia-nim/get-container', { imageTag })
|
const containerResponse = await axios.post('/api/v1/nvidia-nim/get-container', {
|
||||||
if (containerResponse.data && containerResponse.data && containerResponse.data.status === 'running') {
|
imageTag,
|
||||||
// wait additional 10 seconds for container to be ready
|
port: parseInt(hostPort)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10000))
|
})
|
||||||
|
if (containerResponse.data) {
|
||||||
|
setExistingContainer(containerResponse.data)
|
||||||
|
setShowContainerConfirm(true)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
onComplete(containerResponse.data)
|
|
||||||
onClose()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Handle port in use by non-model container
|
||||||
|
if (err.response?.status === 409) {
|
||||||
|
alert(`Port ${hostPort} is already in use by another container. Please choose a different port.`)
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Continue if container not found
|
// Continue if container not found
|
||||||
if (err.response?.status !== 404) {
|
if (err.response?.status !== 404) {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No container found with this port, proceed with starting new container
|
||||||
|
await startNewContainer()
|
||||||
|
} catch (err) {
|
||||||
|
let errorData = err.message
|
||||||
|
if (typeof err === 'string') {
|
||||||
|
errorData = err
|
||||||
|
} else if (err.response?.data) {
|
||||||
|
errorData = err.response.data.message
|
||||||
|
}
|
||||||
|
alert('Failed to check container status: ' + errorData)
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startNewContainer = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
const tokenResponse = await axios.get('/api/v1/nvidia-nim/get-token')
|
const tokenResponse = await axios.get('/api/v1/nvidia-nim/get-token')
|
||||||
const apiKey = tokenResponse.data.access_token
|
const apiKey = tokenResponse.data.access_token
|
||||||
|
|
||||||
await axios.post('/api/v1/nvidia-nim/start-container', {
|
await axios.post('/api/v1/nvidia-nim/start-container', {
|
||||||
imageTag,
|
imageTag,
|
||||||
apiKey
|
apiKey,
|
||||||
|
nimRelaxMemConstraints: parseInt(nimRelaxMemConstraints),
|
||||||
|
hostPort: parseInt(hostPort)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start polling for container status
|
// Start polling for container status
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const containerResponse = await axios.post('/api/v1/nvidia-nim/get-container', { imageTag })
|
const containerResponse = await axios.post('/api/v1/nvidia-nim/get-container', {
|
||||||
|
imageTag,
|
||||||
|
port: parseInt(hostPort)
|
||||||
|
})
|
||||||
if (containerResponse.data) {
|
if (containerResponse.data) {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
@ -194,12 +232,59 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUseExistingContainer = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
// Start polling for container status
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const containerResponse = await axios.post('/api/v1/nvidia-nim/get-container', {
|
||||||
|
imageTag,
|
||||||
|
port: parseInt(hostPort)
|
||||||
|
})
|
||||||
|
if (containerResponse.data) {
|
||||||
|
clearInterval(interval)
|
||||||
|
setLoading(false)
|
||||||
|
onComplete(containerResponse.data)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Continue polling if container not found
|
||||||
|
if (err.response?.status !== 404) {
|
||||||
|
clearInterval(interval)
|
||||||
|
alert('Failed to check container status: ' + err.message)
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
|
setPollInterval(interval)
|
||||||
|
} catch (err) {
|
||||||
|
let errorData = err.message
|
||||||
|
if (typeof err === 'string') {
|
||||||
|
errorData = err
|
||||||
|
} else if (err.response?.data) {
|
||||||
|
errorData = err.response.data.message
|
||||||
|
}
|
||||||
|
alert('Failed to check container status: ' + errorData)
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
if (activeStep === 1 && !imageTag) {
|
if (activeStep === 1 && !imageTag) {
|
||||||
alert('Please enter an image tag')
|
alert('Please enter an image tag')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeStep === 2) {
|
||||||
|
const port = parseInt(hostPort)
|
||||||
|
if (isNaN(port) || port < 1 || port > 65535) {
|
||||||
|
alert('Please enter a valid port number between 1 and 65535')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (activeStep) {
|
switch (activeStep) {
|
||||||
case 0:
|
case 0:
|
||||||
preload()
|
preload()
|
||||||
|
|
@ -234,86 +319,150 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const component = open ? (
|
const component = open ? (
|
||||||
<Dialog open={open}>
|
<>
|
||||||
<DialogTitle>NIM Setup</DialogTitle>
|
<Dialog open={open}>
|
||||||
<DialogContent>
|
<DialogTitle>NIM Setup</DialogTitle>
|
||||||
<Stepper activeStep={activeStep}>
|
<DialogContent>
|
||||||
{steps.map((label) => (
|
<Stepper activeStep={activeStep}>
|
||||||
<Step key={label}>
|
{steps.map((label) => (
|
||||||
<StepLabel>{label}</StepLabel>
|
<Step key={label}>
|
||||||
</Step>
|
<StepLabel>{label}</StepLabel>
|
||||||
))}
|
</Step>
|
||||||
</Stepper>
|
))}
|
||||||
|
</Stepper>
|
||||||
|
|
||||||
{activeStep === 0 && (
|
{activeStep === 0 && (
|
||||||
<div style={{ marginTop: 20 }}>
|
<div style={{ marginTop: 20 }}>
|
||||||
<p style={{ marginBottom: 20 }}>
|
<p style={{ marginBottom: 20 }}>
|
||||||
Would you like to download the NIM installer? Click Next if it has been installed
|
Would you like to download the NIM installer? Click Next if it has been installed
|
||||||
</p>
|
</p>
|
||||||
{loading && <CircularProgress />}
|
{loading && <CircularProgress />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeStep === 1 && (
|
{activeStep === 1 && (
|
||||||
<div>
|
<div>
|
||||||
<FormControl fullWidth sx={{ mt: 2 }}>
|
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||||
<InputLabel>Model</InputLabel>
|
<InputLabel>Model</InputLabel>
|
||||||
<Select label='Model' value={imageTag} onChange={(e) => setImageTag(e.target.value)}>
|
<Select label='Model' value={imageTag} onChange={(e) => setImageTag(e.target.value)}>
|
||||||
{Object.entries(modelOptions).map(([value, { label }]) => (
|
{Object.entries(modelOptions).map(([value, { label }]) => (
|
||||||
<MenuItem key={value} value={value}>
|
<MenuItem key={value} value={value}>
|
||||||
{label}
|
{label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{imageTag && (
|
{imageTag && (
|
||||||
<Button
|
<Button
|
||||||
variant='text'
|
variant='text'
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ mt: 1 }}
|
sx={{ mt: 1 }}
|
||||||
onClick={() => window.open(modelOptions[imageTag].licenseUrl, '_blank')}
|
onClick={() => window.open(modelOptions[imageTag].licenseUrl, '_blank')}
|
||||||
>
|
>
|
||||||
View License
|
View License
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 20 }} />
|
<div style={{ marginBottom: 20 }} />
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
<p>Pulling image...</p>
|
<p>Pulling image...</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeStep === 2 && (
|
{activeStep === 2 && (
|
||||||
<div>
|
<div>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: 20 }} />
|
<div style={{ marginBottom: 20 }} />
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
<p>Starting container...</p>
|
<p>Starting container...</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>Image is ready! Click Next to start the container.</p>
|
<>
|
||||||
)}
|
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||||
</div>
|
<InputLabel>Relax Memory Constraints</InputLabel>
|
||||||
)}
|
<Select
|
||||||
</DialogContent>
|
label='Relax Memory Constraints'
|
||||||
<DialogActions>
|
value={nimRelaxMemConstraints}
|
||||||
<Button onClick={onClose} variant='outline'>
|
onChange={(e) => setNimRelaxMemConstraints(e.target.value)}
|
||||||
Cancel
|
>
|
||||||
</Button>
|
<MenuItem value='1'>Yes</MenuItem>
|
||||||
{activeStep === 0 && (
|
<MenuItem value='0'>No</MenuItem>
|
||||||
<Button onClick={handleNext} variant='outline' color='secondary'>
|
</Select>
|
||||||
Next
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
type='number'
|
||||||
|
label='Host Port'
|
||||||
|
value={hostPort}
|
||||||
|
onChange={(e) => setHostPort(e.target.value)}
|
||||||
|
inputProps={{ min: 1, max: 65535 }}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
/>
|
||||||
|
<p style={{ marginTop: 20 }}>Click Next to start the container.</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} variant='outline'>
|
||||||
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
{activeStep === 0 && (
|
||||||
<Button onClick={activeStep === 0 ? handleDownloadInstaller : handleNext} disabled={loading}>
|
<Button onClick={handleNext} variant='outline' color='secondary'>
|
||||||
{activeStep === 0 ? 'Download' : 'Next'}
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
)}
|
||||||
</Dialog>
|
<Button
|
||||||
|
onClick={activeStep === 0 ? handleDownloadInstaller : handleNext}
|
||||||
|
disabled={loading || (activeStep === 2 && (!nimRelaxMemConstraints || !hostPort))}
|
||||||
|
>
|
||||||
|
{activeStep === 0 ? 'Download' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog open={showContainerConfirm} onClose={() => setShowContainerConfirm(false)}>
|
||||||
|
<DialogTitle>Container Already Exists</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<p>A container for this image already exists:</p>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Name:</strong> {existingContainer?.name || 'N/A'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Status:</strong> {existingContainer?.status || 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p>You can:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Use the existing container (recommended)</li>
|
||||||
|
<li>Change the port and try again</li>
|
||||||
|
</ul>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowContainerConfirm(false)
|
||||||
|
setExistingContainer(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowContainerConfirm(false)
|
||||||
|
handleUseExistingContainer()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use Existing
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
return createPortal(component, portalElement)
|
return createPortal(component, portalElement)
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,46 @@
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
|
import { useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { useEffect, useRef, useState, useContext } from 'react'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme, styled } from '@mui/material/styles'
|
|
||||||
import { Popper, Box, Typography, Tooltip, IconButton, Button, TextField } from '@mui/material'
|
|
||||||
import { useGridApiContext } from '@mui/x-data-grid'
|
|
||||||
import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh'
|
|
||||||
import { tooltipClasses } from '@mui/material/Tooltip'
|
|
||||||
import { IconArrowsMaximize, IconEdit, IconAlertTriangle, IconBulb, IconRefresh } from '@tabler/icons-react'
|
|
||||||
import { Tabs } from '@mui/base/Tabs'
|
import { Tabs } from '@mui/base/Tabs'
|
||||||
|
import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh'
|
||||||
|
import { Box, Button, IconButton, Popper, TextField, Tooltip, Typography } from '@mui/material'
|
||||||
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
|
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
|
||||||
|
import { styled, useTheme } from '@mui/material/styles'
|
||||||
|
import { tooltipClasses } from '@mui/material/Tooltip'
|
||||||
|
import { useGridApiContext } from '@mui/x-data-grid'
|
||||||
|
import { IconAlertTriangle, IconArrowsMaximize, IconBulb, IconEdit, IconRefresh } from '@tabler/icons-react'
|
||||||
|
|
||||||
// project import
|
// project import
|
||||||
|
import { flowContext } from '@/store/context/ReactFlowContext'
|
||||||
|
import ConditionDialog from '@/ui-component/dialog/ConditionDialog'
|
||||||
|
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
|
||||||
|
import FormatPromptValuesDialog from '@/ui-component/dialog/FormatPromptValuesDialog'
|
||||||
|
import InputHintDialog from '@/ui-component/dialog/InputHintDialog'
|
||||||
|
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
|
||||||
|
import NvidiaNIMDialog from '@/ui-component/dialog/NvidiaNIMDialog'
|
||||||
|
import PromptLangsmithHubDialog from '@/ui-component/dialog/PromptLangsmithHubDialog'
|
||||||
|
import { AsyncDropdown } from '@/ui-component/dropdown/AsyncDropdown'
|
||||||
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||||
import { MultiDropdown } from '@/ui-component/dropdown/MultiDropdown'
|
import { MultiDropdown } from '@/ui-component/dropdown/MultiDropdown'
|
||||||
import { AsyncDropdown } from '@/ui-component/dropdown/AsyncDropdown'
|
|
||||||
import { Input } from '@/ui-component/input/Input'
|
|
||||||
import { DataGrid } from '@/ui-component/grid/DataGrid'
|
|
||||||
import { File } from '@/ui-component/file/File'
|
|
||||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
|
||||||
import { flowContext } from '@/store/context/ReactFlowContext'
|
|
||||||
import { JsonEditorInput } from '@/ui-component/json/JsonEditor'
|
|
||||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
|
||||||
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
||||||
|
import { File } from '@/ui-component/file/File'
|
||||||
|
import { DataGrid } from '@/ui-component/grid/DataGrid'
|
||||||
|
import { Input } from '@/ui-component/input/Input'
|
||||||
|
import { JsonEditorInput } from '@/ui-component/json/JsonEditor'
|
||||||
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
import { Tab } from '@/ui-component/tabs/Tab'
|
||||||
import { TabPanel } from '@/ui-component/tabs/TabPanel'
|
import { TabPanel } from '@/ui-component/tabs/TabPanel'
|
||||||
import { TabsList } from '@/ui-component/tabs/TabsList'
|
import { TabsList } from '@/ui-component/tabs/TabsList'
|
||||||
import { Tab } from '@/ui-component/tabs/Tab'
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
import ToolDialog from '@/views/tools/ToolDialog'
|
|
||||||
import AssistantDialog from '@/views/assistants/openai/AssistantDialog'
|
import AssistantDialog from '@/views/assistants/openai/AssistantDialog'
|
||||||
import FormatPromptValuesDialog from '@/ui-component/dialog/FormatPromptValuesDialog'
|
import ToolDialog from '@/views/tools/ToolDialog'
|
||||||
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
|
|
||||||
import ConditionDialog from '@/ui-component/dialog/ConditionDialog'
|
|
||||||
import PromptLangsmithHubDialog from '@/ui-component/dialog/PromptLangsmithHubDialog'
|
|
||||||
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
|
|
||||||
import CredentialInputHandler from './CredentialInputHandler'
|
import CredentialInputHandler from './CredentialInputHandler'
|
||||||
import InputHintDialog from '@/ui-component/dialog/InputHintDialog'
|
|
||||||
import NvidiaNIMDialog from '@/ui-component/dialog/NvidiaNIMDialog'
|
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
import { getInputVariables, getCustomConditionOutputs, isValidConnection, getAvailableNodesForVariable } from '@/utils/genericHelper'
|
import { getAvailableNodesForVariable, getCustomConditionOutputs, getInputVariables, isValidConnection } from '@/utils/genericHelper'
|
||||||
|
|
||||||
// const
|
// const
|
||||||
import { FLOWISE_CREDENTIAL_ID } from '@/store/constant'
|
import { FLOWISE_CREDENTIAL_ID } from '@/store/constant'
|
||||||
|
|
@ -537,7 +537,7 @@ const NodeInputHandler = ({
|
||||||
></PromptLangsmithHubDialog>
|
></PromptLangsmithHubDialog>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{data.name === 'chatNvidiaNIM' && inputParam.name === 'modelName' && (
|
{data.name === 'Chat NVIDIA NIM' && inputParam.name === 'modelName' && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
222
pnpm-lock.yaml
222
pnpm-lock.yaml
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue