1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-28 19:14:13 +08:00

feat: Support for terminal operation with the Ollama model (#7907)

This commit is contained in:
ssongliu 2025-02-19 14:07:05 +08:00 committed by GitHub
parent c3685f6b74
commit c329fb6599
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 421 additions and 51 deletions

View File

@ -18,7 +18,7 @@ import (
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加模型 [name]","formatEN":"add Ollama model [name]"}
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"}
func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@ -40,7 +40,7 @@ func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/recreate [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加模型重试 [name]","formatEN":"re-add Ollama model [name]"}
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"}
func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@ -54,6 +54,28 @@ func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Close Ollama model conn
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/close [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := AIToolService.Close(req.Name); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Sync Ollama model list
// @Success 200 {array} dto.OllamaModelDropList
@ -127,7 +149,7 @@ func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"ollama_models","output_column":"name","output_value":"name"}],"formatZH":"删除 ollama 模型 [name]","formatEN":"remove ollama model [name]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"}
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
var req dto.ForceDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {

View File

@ -10,6 +10,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@ -146,6 +147,70 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
}
}
func (b *BaseApi) OllamaWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
name := c.Query("name")
if cmd.CheckIllegal(name) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
}
container, err := service.LoadContainerName()
if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) {
return
}
commands := []string{"ollama", "run", name}
pidMap := loadMapFromDockerTop(container)
fmt.Println("pidMap")
for k, v := range pidMap {
fmt.Println(k, v)
}
itemCmds := append([]string{"exec", "-it", container}, commands...)
slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) {
return
}
defer killBash(container, strings.Join(commands, " "), pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
if wshandleError(wsConn, err) {
return
}
quitChan := make(chan bool, 3)
tty.Start(quitChan)
go slave.Wait(quitChan)
<-quitChan
global.LOG.Info("websocket finished")
if wshandleError(wsConn, err) {
return
}
}
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {

View File

@ -28,6 +28,7 @@ type AIToolService struct{}
type IAIToolService interface {
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
Create(name string) error
Close(name string) error
Recreate(name string) error
Delete(req dto.ForceDelete) error
Sync() ([]dto.OllamaModelDropList, error)
@ -69,7 +70,7 @@ func (u *AIToolService) LoadDetail(name string) (string, error) {
if cmd.CheckIllegal(name) {
return "", buserr.New(constant.ErrCmdIllegal)
}
containerName, err := loadContainerName()
containerName, err := LoadContainerName()
if err != nil {
return "", err
}
@ -88,7 +89,7 @@ func (u *AIToolService) Create(name string) error {
if modelInfo.ID != 0 {
return constant.ErrRecordExist
}
containerName, err := loadContainerName()
containerName, err := LoadContainerName()
if err != nil {
return err
}
@ -114,6 +115,21 @@ func (u *AIToolService) Create(name string) error {
return nil
}
func (u *AIToolService) Close(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New(constant.ErrCmdIllegal)
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
stdout, err := cmd.Execf("docker exec %s ollama stop %s", containerName, name)
if err != nil {
return fmt.Errorf("handle ollama stop %s failed, stdout: %s, err: %v", name, stdout, err)
}
return nil
}
func (u *AIToolService) Recreate(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New(constant.ErrCmdIllegal)
@ -122,7 +138,7 @@ func (u *AIToolService) Recreate(name string) error {
if modelInfo.ID == 0 {
return constant.ErrRecordNotFound
}
containerName, err := loadContainerName()
containerName, err := LoadContainerName()
if err != nil {
return err
}
@ -148,7 +164,7 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
if len(ollamaList) == 0 {
return constant.ErrRecordNotFound
}
containerName, err := loadContainerName()
containerName, err := LoadContainerName()
if err != nil && !req.ForceDelete {
return err
}
@ -167,7 +183,7 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
}
func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
containerName, err := loadContainerName()
containerName, err := LoadContainerName()
if err != nil {
return nil, err
}
@ -335,7 +351,7 @@ func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
return nil
}
func loadContainerName() (string, error) {
func LoadContainerName() (string, error) {
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
if err != nil {
return "", fmt.Errorf("ollama service is not found, err: %v", err)

View File

@ -15,6 +15,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh)
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)

View File

@ -156,7 +156,51 @@ const docTemplate = `{
"name"
],
"formatEN": "add Ollama model [name]",
"formatZH": "添加模型 [name]",
"formatZH": "添加 Ollama 模型 [name]",
"paramKeys": []
}
}
},
"/ai/ollama/model/close": {
"post": {
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"consumes": [
"application/json"
],
"tags": [
"AI"
],
"summary": "Close Ollama model conn",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OllamaModelName"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "close conn for Ollama model [name]",
"formatZH": "关闭 Ollama 模型连接 [name]",
"paramKeys": []
}
}
@ -199,17 +243,17 @@ const docTemplate = `{
{
"db": "ollama_models",
"input_column": "id",
"input_value": "id",
"isList": false,
"input_value": "ids",
"isList": true,
"output_column": "name",
"output_value": "name"
"output_value": "names"
}
],
"bodyKeys": [
"id"
"ids"
],
"formatEN": "remove ollama model [name]",
"formatZH": "删除 ollama 模型 [name]",
"formatEN": "remove Ollama model [names]",
"formatZH": "删除 Ollama 模型 [names]",
"paramKeys": []
}
}
@ -291,7 +335,7 @@ const docTemplate = `{
"name"
],
"formatEN": "re-add Ollama model [name]",
"formatZH": "添加模型重试 [name]",
"formatZH": "添加 Ollama 模型重试 [name]",
"paramKeys": []
}
}
@ -20633,18 +20677,15 @@ const docTemplate = `{
"domain"
],
"properties": {
"allowIPs": {
"type": "array",
"items": {
"type": "string"
}
},
"appInstallID": {
"type": "integer"
},
"domain": {
"type": "string"
},
"ipList": {
"type": "string"
},
"sslID": {
"type": "integer"
},
@ -20673,6 +20714,9 @@ const docTemplate = `{
"type": "string"
}
},
"connUrl": {
"type": "string"
},
"domain": {
"type": "string"
},

View File

@ -153,7 +153,51 @@
"name"
],
"formatEN": "add Ollama model [name]",
"formatZH": "添加模型 [name]",
"formatZH": "添加 Ollama 模型 [name]",
"paramKeys": []
}
}
},
"/ai/ollama/model/close": {
"post": {
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"consumes": [
"application/json"
],
"tags": [
"AI"
],
"summary": "Close Ollama model conn",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OllamaModelName"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "close conn for Ollama model [name]",
"formatZH": "关闭 Ollama 模型连接 [name]",
"paramKeys": []
}
}
@ -196,17 +240,17 @@
{
"db": "ollama_models",
"input_column": "id",
"input_value": "id",
"isList": false,
"input_value": "ids",
"isList": true,
"output_column": "name",
"output_value": "name"
"output_value": "names"
}
],
"bodyKeys": [
"id"
"ids"
],
"formatEN": "remove ollama model [name]",
"formatZH": "删除 ollama 模型 [name]",
"formatEN": "remove Ollama model [names]",
"formatZH": "删除 Ollama 模型 [names]",
"paramKeys": []
}
}
@ -288,7 +332,7 @@
"name"
],
"formatEN": "re-add Ollama model [name]",
"formatZH": "添加模型重试 [name]",
"formatZH": "添加 Ollama 模型重试 [name]",
"paramKeys": []
}
}
@ -20630,18 +20674,15 @@
"domain"
],
"properties": {
"allowIPs": {
"type": "array",
"items": {
"type": "string"
}
},
"appInstallID": {
"type": "integer"
},
"domain": {
"type": "string"
},
"ipList": {
"type": "string"
},
"sslID": {
"type": "integer"
},
@ -20670,6 +20711,9 @@
"type": "string"
}
},
"connUrl": {
"type": "string"
},
"domain": {
"type": "string"
},

View File

@ -2457,14 +2457,12 @@ definitions:
- ProxyCache
dto.OllamaBindDomain:
properties:
allowIPs:
items:
type: string
type: array
appInstallID:
type: integer
domain:
type: string
ipList:
type: string
sslID:
type: integer
websiteID:
@ -2486,6 +2484,8 @@ definitions:
items:
type: string
type: array
connUrl:
type: string
domain:
type: string
sslID:
@ -6583,7 +6583,34 @@ paths:
bodyKeys:
- name
formatEN: add Ollama model [name]
formatZH: 添加模型 [name]
formatZH: 添加 Ollama 模型 [name]
paramKeys: []
/ai/ollama/model/close:
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OllamaModelName'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
- Timestamp: []
summary: Close Ollama model conn
tags:
- AI
x-panel-log:
BeforeFunctions: []
bodyKeys:
- name
formatEN: close conn for Ollama model [name]
formatZH: 关闭 Ollama 模型连接 [name]
paramKeys: []
/ai/ollama/model/del:
post:
@ -6609,14 +6636,14 @@ paths:
BeforeFunctions:
- db: ollama_models
input_column: id
input_value: id
isList: false
input_value: ids
isList: true
output_column: name
output_value: name
output_value: names
bodyKeys:
- id
formatEN: remove ollama model [name]
formatZH: 删除 ollama 模型 [name]
- ids
formatEN: remove Ollama model [names]
formatZH: 删除 Ollama 模型 [names]
paramKeys: []
/ai/ollama/model/load:
post:
@ -6665,7 +6692,7 @@ paths:
bodyKeys:
- name
formatEN: re-add Ollama model [name]
formatZH: 添加模型重试 [name]
formatZH: 添加 Ollama 模型重试 [name]
paramKeys: []
/ai/ollama/model/search:
post:

View File

@ -20,6 +20,9 @@ export const loadOllamaModel = (name: string) => {
export const syncOllamaModel = () => {
return http.post<Array<AI.OllamaModelDropInfo>>(`/ai/ollama/model/sync`);
};
export const closeOllamaModel = (name: string) => {
return http.post(`/ai/ollama/close`, { name: name });
};
export const loadGPUInfo = () => {
return http.get<any>(`/ai/gpu/load`);

View File

@ -15,6 +15,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Run',
create: 'Create ',
add: 'Add ',
save: 'Save ',
@ -122,6 +123,8 @@ const message = {
},
msg: {
noneData: 'No data available',
disConn:
'If you want to disconnect the terminal, please click the disconnect button directly, do not enter exit commands like {0}.',
delete: `This operation delete can't be undone. Do you want to continue?`,
clean: `This operation clean can't be undone. Do you want to continue?`,
deleteTitle: 'Delete',
@ -1229,6 +1232,7 @@ const message = {
resource: 'Resource',
operate: 'Operate',
detail: {
ai: 'AI',
groups: 'Group',
hosts: 'Host',
apps: 'App',

View File

@ -14,6 +14,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: '実行',
create: '作成する',
add: '追加',
save: '保存',
@ -121,6 +122,8 @@ const message = {
},
msg: {
noneData: '利用可能なデータはありません',
disConn:
'端末を切断する場合は切断ボタンを直接クリックしてください{0} のような終了コマンドを入力しないでください',
delete: `この操作削除は元に戻すことはできません。続けたいですか?`,
clean: `この操作は取り消すことはできません。続けたいですか?`,
deleteTitle: '消去',
@ -1209,6 +1212,7 @@ const message = {
resource: 'リソース',
operate: '動作します',
detail: {
ai: 'AI',
groups: 'グループ',
hosts: 'ホスト',
apps: 'アプリ',

View File

@ -15,6 +15,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: '실행',
create: '생성',
add: '추가',
save: '저장',
@ -122,6 +123,7 @@ const message = {
},
msg: {
noneData: '데이터가 없습니다',
disConn: '터미널을 끊으려면 직접 끊기 버튼을 클릭하세요. {0} 같은 종료 명령을 입력하지 마세요.',
delete: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
clean: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
deleteTitle: '삭제',
@ -1196,6 +1198,7 @@ const message = {
resource: '자원',
operate: '작업',
detail: {
ai: 'AI',
groups: '그룹',
hosts: '호스트',
apps: '애플리케이션',

View File

@ -15,6 +15,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Jalankan',
create: 'Cipta',
add: 'Tambah',
save: 'Simpan',
@ -122,6 +123,8 @@ const message = {
},
msg: {
noneData: 'Tiada data tersedia',
disConn:
'Jika anda ingin memutuskan sambungan terminal, sila klik butang putus sambungan secara langsung, jangan masukkan arahan keluar seperti {0}.',
delete: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
clean: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
deleteTitle: 'Padam',
@ -1251,6 +1254,7 @@ const message = {
resource: 'Sumber',
operate: 'Operasi',
detail: {
ai: 'AI',
groups: 'Kumpulan',
hosts: 'Hos',
apps: 'Aplikasi',

View File

@ -15,6 +15,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Executar',
create: 'Criar',
add: 'Adicionar',
save: 'Salvar',
@ -122,6 +123,8 @@ const message = {
},
msg: {
noneData: 'Nenhum dado disponível',
disConn:
'Se você deseja desconectar o terminal, clique diretamente no botão de desconexão, não insira comandos de saída como {0}.',
delete: 'Esta operação de exclusão não pode ser desfeita. Deseja continuar?',
clean: 'Esta operação de limpeza não pode ser desfeita. Deseja continuar?',
deleteTitle: 'Excluir',
@ -1236,6 +1239,7 @@ const message = {
resource: 'Recurso',
operate: 'Operar',
detail: {
ai: 'AI',
groups: 'Grupo',
hosts: 'Host',
apps: 'Aplicativo',

View File

@ -15,6 +15,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Запуск',
create: 'Создать ',
add: 'Добавить ',
save: 'Сохранить ',
@ -123,6 +124,8 @@ const message = {
},
msg: {
noneData: 'Нет данных',
disConn:
'Если вы хотите отключить терминал, пожалуйста, нажмите кнопку отключения напрямую, не вводите команды выхода, такие как {0}.',
delete: 'Эта операция удаления не может быть отменена. Хотите продолжить?',
clean: 'Эта операция очистки не может быть отменена. Хотите продолжить?',
deleteTitle: 'Удалить',
@ -1242,6 +1245,7 @@ const message = {
resource: 'Ресурс',
operate: 'Операция',
detail: {
ai: 'AI',
groups: 'Группа',
hosts: 'Хост',
apps: 'Приложение',

View File

@ -14,6 +14,7 @@ const message = {
fit2cloud: '飛致雲',
lingxia: '凌霞',
button: {
run: '運行',
create: '建立',
add: '新增',
save: '儲存',
@ -122,6 +123,7 @@ const message = {
},
msg: {
noneData: '暫無資料',
disConn: '如果要斷開終端請直接點擊斷開按鈕不要輸入 {0} 等退出命令',
delete: '刪除 操作不可復原是否繼續',
clean: '清空 操作不可復原是否繼續',
deleteTitle: '刪除',
@ -1169,6 +1171,7 @@ const message = {
resource: '資源',
operate: '操作',
detail: {
ai: 'AI',
groups: '分組',
hosts: '主機',
apps: '應用',

View File

@ -14,6 +14,7 @@ const message = {
fit2cloud: '飞致云',
lingxia: '凌霞',
button: {
run: '运行',
create: '创建',
add: '添加',
save: '保存',
@ -120,6 +121,7 @@ const message = {
Rollbacking: '快照回滚中请稍候...',
},
msg: {
disConn: '如果要断开终端请直接点击断开按钮不要输入 {0} 等退出命令',
noneData: '暂无数据',
delete: '删除 操作不可回滚是否继续',
clean: '清空 操作不可回滚是否继续',
@ -1171,6 +1173,7 @@ const message = {
resource: '资源',
operate: '操作',
detail: {
ai: 'AI',
groups: '分组',
hosts: '主机',
apps: '应用',

View File

@ -172,6 +172,7 @@
<AddDialog ref="addRef" @search="search" @log="onLoadLog" />
<Log ref="logRef" @close="search" />
<Del ref="delRef" @search="search" />
<Terminal ref="terminalRef" />
<Conn ref="connRef" />
<CodemirrorDialog ref="detailRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
@ -183,6 +184,7 @@
import AppStatus from '@/components/app-status/index.vue';
import AddDialog from '@/views/ai/model/add/index.vue';
import Conn from '@/views/ai/model/conn/index.vue';
import Terminal from '@/views/ai/model/terminal/index.vue';
import Del from '@/views/ai/model/del/index.vue';
import Log from '@/components/log-dialog/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
@ -214,6 +216,7 @@ const logRef = ref();
const detailRef = ref();
const delRef = ref();
const connRef = ref();
const terminalRef = ref();
const openWebUIPort = ref();
const dashboardVisible = ref(false);
const dialogPortJumpRef = ref();
@ -403,6 +406,15 @@ const onLoadLog = (row: any) => {
};
const buttons = [
{
label: i18n.global.t('commons.button.run'),
click: (row: AI.OllamaModelInfo) => {
terminalRef.value.acceptParams({ name: row.name });
},
disabled: (row: any) => {
return row.status !== 'Success';
},
},
{
label: i18n.global.t('commons.button.retry'),
click: (row: AI.OllamaModelInfo) => {

View File

@ -0,0 +1,101 @@
<template>
<el-drawer
v-model="terminalVisible"
@close="handleClose"
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
:size="globalStore.isFullScreen ? '100%' : '50%'"
>
<template #header>
<DrawerHeader :header="$t('container.containerTerminal')" :resource="title" :back="handleClose">
<template #extra v-if="!mobile">
<el-tooltip :content="loadTooltip()" placement="top">
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen" plain></el-button>
</el-tooltip>
</template>
</DrawerHeader>
</template>
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['/bye exit']) }}</span>
</template>
</el-alert>
<Terminal class="mt-2" style="height: calc(100vh - 225px)" ref="terminalRef"></Terminal>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="handleClose">
{{ $t('commons.button.disconnect') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { computed, nextTick, ref } from 'vue';
import Terminal from '@/components/terminal/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { closeOllamaModel } from '@/api/modules/ai';
import { GlobalStore } from '@/store';
import i18n from '@/lang';
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
const title = ref();
const terminalVisible = ref(false);
const itemName = ref();
const terminalRef = ref();
interface DialogProps {
name: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
itemName.value = params.name;
terminalVisible.value = true;
initTerm();
};
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
};
const initTerm = () => {
nextTick(() => {
terminalRef.value.acceptParams({
endpoint: '/api/v1/ai/ollama/exec',
args: `name=${itemName.value}`,
error: '',
initCmd: '',
});
});
};
function toggleFullscreen() {
globalStore.isFullScreen = !globalStore.isFullScreen;
}
const onClose = async () => {
await closeOllamaModel(itemName.value)
.then(() => {
terminalRef.value?.onClose();
})
.catch(() => {
terminalRef.value?.onClose();
});
};
function handleClose() {
onClose();
globalStore.isFullScreen = false;
terminalVisible.value = false;
}
defineExpose({
acceptParams,
});
</script>

View File

@ -10,7 +10,12 @@
<template #header>
<DrawerHeader :header="$t('container.containerTerminal')" :resource="title" :back="handleClose" />
</template>
<el-form ref="formRef" :model="form" label-position="top">
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['exit']) }}</span>
</template>
</el-alert>
<el-form ref="formRef" class="mt-2" :model="form" label-position="top">
<el-form-item :label="$t('commons.table.user')" prop="user">
<el-input placeholder="root" clearable v-model="form.user" />
</el-form-item>
@ -46,7 +51,7 @@
</el-button>
<el-button v-else @click="onClose()">{{ $t('commons.button.disconnect') }}</el-button>
<Terminal
style="height: calc(100vh - 302px); margin-top: 18px"
style="height: calc(100vh - 355px); margin-top: 18px"
ref="terminalRef"
v-if="terminalOpen"
></Terminal>