Compare commits
5 Commits
dev
...
refactor/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea231676bf | ||
|
|
1f4977c1d1 | ||
|
|
7b6e4cd172 | ||
|
|
8b15fe4ef2 | ||
|
|
0c5fbc102c |
108
.github/workflows/jan-tauri-build-nightly.yaml
vendored
108
.github/workflows/jan-tauri-build-nightly.yaml
vendored
@ -168,62 +168,62 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
||||||
AWS_EC2_METADATA_DISABLED: 'true'
|
AWS_EC2_METADATA_DISABLED: 'true'
|
||||||
|
|
||||||
# noti-discord-nightly-and-update-url-readme:
|
noti-discord-nightly-and-update-url-readme:
|
||||||
# needs:
|
needs:
|
||||||
# [
|
[
|
||||||
# build-macos,
|
build-macos,
|
||||||
# build-windows-x64,
|
build-windows-x64,
|
||||||
# build-linux-x64,
|
build-linux-x64,
|
||||||
# get-update-version,
|
get-update-version,
|
||||||
# set-public-provider,
|
set-public-provider,
|
||||||
# sync-temp-to-latest,
|
sync-temp-to-latest,
|
||||||
# ]
|
]
|
||||||
# secrets: inherit
|
secrets: inherit
|
||||||
# if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
# with:
|
with:
|
||||||
# ref: refs/heads/dev
|
ref: refs/heads/dev
|
||||||
# build_reason: Nightly
|
build_reason: Nightly
|
||||||
# push_to_branch: dev
|
push_to_branch: dev
|
||||||
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
# noti-discord-pre-release-and-update-url-readme:
|
noti-discord-pre-release-and-update-url-readme:
|
||||||
# needs:
|
needs:
|
||||||
# [
|
[
|
||||||
# build-macos,
|
build-macos,
|
||||||
# build-windows-x64,
|
build-windows-x64,
|
||||||
# build-linux-x64,
|
build-linux-x64,
|
||||||
# get-update-version,
|
get-update-version,
|
||||||
# set-public-provider,
|
set-public-provider,
|
||||||
# sync-temp-to-latest,
|
sync-temp-to-latest,
|
||||||
# ]
|
]
|
||||||
# secrets: inherit
|
secrets: inherit
|
||||||
# if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
# with:
|
with:
|
||||||
# ref: refs/heads/dev
|
ref: refs/heads/dev
|
||||||
# build_reason: Pre-release
|
build_reason: Pre-release
|
||||||
# push_to_branch: dev
|
push_to_branch: dev
|
||||||
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
# noti-discord-manual-and-update-url-readme:
|
noti-discord-manual-and-update-url-readme:
|
||||||
# needs:
|
needs:
|
||||||
# [
|
[
|
||||||
# build-macos,
|
build-macos,
|
||||||
# build-windows-x64,
|
build-windows-x64,
|
||||||
# build-linux-x64,
|
build-linux-x64,
|
||||||
# get-update-version,
|
get-update-version,
|
||||||
# set-public-provider,
|
set-public-provider,
|
||||||
# sync-temp-to-latest,
|
sync-temp-to-latest,
|
||||||
# ]
|
]
|
||||||
# secrets: inherit
|
secrets: inherit
|
||||||
# if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
|
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
|
||||||
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
# with:
|
with:
|
||||||
# ref: refs/heads/dev
|
ref: refs/heads/dev
|
||||||
# build_reason: Manual
|
build_reason: Manual
|
||||||
# push_to_branch: dev
|
push_to_branch: dev
|
||||||
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
comment-pr-build-url:
|
comment-pr-build-url:
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
@ -149,9 +149,14 @@
|
|||||||
"key": "flash_attn",
|
"key": "flash_attn",
|
||||||
"title": "Flash Attention",
|
"title": "Flash Attention",
|
||||||
"description": "Enable Flash Attention for optimized performance.",
|
"description": "Enable Flash Attention for optimized performance.",
|
||||||
"controllerType": "checkbox",
|
"controllerType": "dropdown",
|
||||||
"controllerProps": {
|
"controllerProps": {
|
||||||
"value": false
|
"value": "auto",
|
||||||
|
"options": [
|
||||||
|
{ "value": "auto", "name": "Auto" },
|
||||||
|
{ "value": "on", "name": "ON" },
|
||||||
|
{ "value": "off", "name": "OFF" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -102,50 +102,27 @@ export async function listSupportedBackends(): Promise<
|
|||||||
// TODO: fetch versions from the server?
|
// TODO: fetch versions from the server?
|
||||||
// TODO: select CUDA version based on driver version
|
// TODO: select CUDA version based on driver version
|
||||||
if (sysType == 'windows-x86_64') {
|
if (sysType == 'windows-x86_64') {
|
||||||
// NOTE: if a machine supports AVX2, should we include noavx and avx?
|
supportedBackends.push('win-common_cpus-x64')
|
||||||
supportedBackends.push('win-noavx-x64')
|
|
||||||
if (features.avx) supportedBackends.push('win-avx-x64')
|
|
||||||
if (features.avx2) supportedBackends.push('win-avx2-x64')
|
|
||||||
if (features.avx512) supportedBackends.push('win-avx512-x64')
|
|
||||||
if (features.cuda11) {
|
if (features.cuda11) {
|
||||||
if (features.avx512) supportedBackends.push('win-avx512-cuda-cu11.7-x64')
|
supportedBackends.push('win-cuda-11-common_cpus-x64')
|
||||||
else if (features.avx2) supportedBackends.push('win-avx2-cuda-cu11.7-x64')
|
|
||||||
else if (features.avx) supportedBackends.push('win-avx-cuda-cu11.7-x64')
|
|
||||||
else supportedBackends.push('win-noavx-cuda-cu11.7-x64')
|
|
||||||
}
|
}
|
||||||
if (features.cuda12) {
|
if (features.cuda12) {
|
||||||
if (features.avx512) supportedBackends.push('win-avx512-cuda-cu12.0-x64')
|
supportedBackends.push('win-cuda-12-common_cpus-x64')
|
||||||
else if (features.avx2) supportedBackends.push('win-avx2-cuda-cu12.0-x64')
|
|
||||||
else if (features.avx) supportedBackends.push('win-avx-cuda-cu12.0-x64')
|
|
||||||
else supportedBackends.push('win-noavx-cuda-cu12.0-x64')
|
|
||||||
}
|
}
|
||||||
if (features.vulkan) supportedBackends.push('win-vulkan-x64')
|
if (features.vulkan) supportedBackends.push('win-vulkan-common_cpus-x64')
|
||||||
}
|
}
|
||||||
// not available yet, placeholder for future
|
// not available yet, placeholder for future
|
||||||
else if (sysType === 'windows-aarch64' || sysType === 'windows-arm64') {
|
else if (sysType === 'windows-aarch64' || sysType === 'windows-arm64') {
|
||||||
supportedBackends.push('win-arm64')
|
supportedBackends.push('win-arm64')
|
||||||
} else if (sysType === 'linux-x86_64' || sysType === 'linux-x86') {
|
} else if (sysType === 'linux-x86_64' || sysType === 'linux-x86') {
|
||||||
supportedBackends.push('linux-noavx-x64')
|
supportedBackends.push('linux-common_cpus-x64')
|
||||||
if (features.avx) supportedBackends.push('linux-avx-x64')
|
|
||||||
if (features.avx2) supportedBackends.push('linux-avx2-x64')
|
|
||||||
if (features.avx512) supportedBackends.push('linux-avx512-x64')
|
|
||||||
if (features.cuda11) {
|
if (features.cuda11) {
|
||||||
if (features.avx512)
|
supportedBackends.push('linux-cuda-11-common_cpus-x64')
|
||||||
supportedBackends.push('linux-avx512-cuda-cu11.7-x64')
|
|
||||||
else if (features.avx2)
|
|
||||||
supportedBackends.push('linux-avx2-cuda-cu11.7-x64')
|
|
||||||
else if (features.avx) supportedBackends.push('linux-avx-cuda-cu11.7-x64')
|
|
||||||
else supportedBackends.push('linux-noavx-cuda-cu11.7-x64')
|
|
||||||
}
|
}
|
||||||
if (features.cuda12) {
|
if (features.cuda12) {
|
||||||
if (features.avx512)
|
supportedBackends.push('linux-cuda-12-common_cpus-x64')
|
||||||
supportedBackends.push('linux-avx512-cuda-cu12.0-x64')
|
|
||||||
else if (features.avx2)
|
|
||||||
supportedBackends.push('linux-avx2-cuda-cu12.0-x64')
|
|
||||||
else if (features.avx) supportedBackends.push('linux-avx-cuda-cu12.0-x64')
|
|
||||||
else supportedBackends.push('linux-noavx-cuda-cu12.0-x64')
|
|
||||||
}
|
}
|
||||||
if (features.vulkan) supportedBackends.push('linux-vulkan-x64')
|
if (features.vulkan) supportedBackends.push('linux-vulkan-common_cpus-x64')
|
||||||
}
|
}
|
||||||
// not available yet, placeholder for future
|
// not available yet, placeholder for future
|
||||||
else if (sysType === 'linux-aarch64' || sysType === 'linux-arm64') {
|
else if (sysType === 'linux-aarch64' || sysType === 'linux-arm64') {
|
||||||
@ -230,10 +207,7 @@ export async function downloadBackend(
|
|||||||
version: string,
|
version: string,
|
||||||
source: 'github' | 'cdn' = 'github'
|
source: 'github' | 'cdn' = 'github'
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const janDataFolderPath = await getJanDataFolderPath()
|
|
||||||
const llamacppPath = await joinPath([janDataFolderPath, 'llamacpp'])
|
|
||||||
const backendDir = await getBackendDir(backend, version)
|
const backendDir = await getBackendDir(backend, version)
|
||||||
const libDir = await joinPath([llamacppPath, 'lib'])
|
|
||||||
|
|
||||||
const downloadManager = window.core.extensionManager.getByName(
|
const downloadManager = window.core.extensionManager.getByName(
|
||||||
'@janhq/download-extension'
|
'@janhq/download-extension'
|
||||||
@ -265,7 +239,7 @@ export async function downloadBackend(
|
|||||||
source === 'github'
|
source === 'github'
|
||||||
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`
|
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`
|
||||||
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`,
|
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`,
|
||||||
save_path: await joinPath([libDir, 'cuda11.tar.gz']),
|
save_path: await joinPath([backendDir, 'build', 'bin', 'cuda11.tar.gz']),
|
||||||
proxy: proxyConfig,
|
proxy: proxyConfig,
|
||||||
})
|
})
|
||||||
} else if (backend.includes('cu12.0') && !(await _isCudaInstalled('12.0'))) {
|
} else if (backend.includes('cu12.0') && !(await _isCudaInstalled('12.0'))) {
|
||||||
@ -274,7 +248,7 @@ export async function downloadBackend(
|
|||||||
source === 'github'
|
source === 'github'
|
||||||
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`
|
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`
|
||||||
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`,
|
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`,
|
||||||
save_path: await joinPath([libDir, 'cuda12.tar.gz']),
|
save_path: await joinPath([backendDir, 'build', 'bin', 'cuda12.tar.gz']),
|
||||||
proxy: proxyConfig,
|
proxy: proxyConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -344,8 +318,8 @@ async function _getSupportedFeatures() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.nvidia.com/deploy/cuda-compatibility/#cuda-11-and-later-defaults-to-minor-version-compatibility
|
// https://docs.nvidia.com/deploy/cuda-compatibility/#cuda-11-and-later-defaults-to-minor-version-compatibility
|
||||||
let minCuda11DriverVersion
|
let minCuda11DriverVersion: string
|
||||||
let minCuda12DriverVersion
|
let minCuda12DriverVersion: string
|
||||||
if (sysInfo.os_type === 'linux') {
|
if (sysInfo.os_type === 'linux') {
|
||||||
minCuda11DriverVersion = '450.80.02'
|
minCuda11DriverVersion = '450.80.02'
|
||||||
minCuda12DriverVersion = '525.60.13'
|
minCuda12DriverVersion = '525.60.13'
|
||||||
|
|||||||
@ -38,10 +38,12 @@ import { invoke } from '@tauri-apps/api/core'
|
|||||||
import { getProxyConfig } from './util'
|
import { getProxyConfig } from './util'
|
||||||
import { basename } from '@tauri-apps/api/path'
|
import { basename } from '@tauri-apps/api/path'
|
||||||
import {
|
import {
|
||||||
|
loadLlamaModel,
|
||||||
readGgufMetadata,
|
readGgufMetadata,
|
||||||
getModelSize,
|
getModelSize,
|
||||||
isModelSupported,
|
isModelSupported,
|
||||||
planModelLoadInternal,
|
planModelLoadInternal,
|
||||||
|
unloadLlamaModel,
|
||||||
} from '@janhq/tauri-plugin-llamacpp-api'
|
} from '@janhq/tauri-plugin-llamacpp-api'
|
||||||
import { getSystemUsage, getSystemInfo } from '@janhq/tauri-plugin-hardware-api'
|
import { getSystemUsage, getSystemInfo } from '@janhq/tauri-plugin-hardware-api'
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ type LlamacppConfig = {
|
|||||||
device: string
|
device: string
|
||||||
split_mode: string
|
split_mode: string
|
||||||
main_gpu: number
|
main_gpu: number
|
||||||
flash_attn: boolean
|
flash_attn: string
|
||||||
cont_batching: boolean
|
cont_batching: boolean
|
||||||
no_mmap: boolean
|
no_mmap: boolean
|
||||||
mlock: boolean
|
mlock: boolean
|
||||||
@ -549,9 +551,9 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
|
|
||||||
// Helper to map backend string to a priority category
|
// Helper to map backend string to a priority category
|
||||||
const getBackendCategory = (backendString: string): string | undefined => {
|
const getBackendCategory = (backendString: string): string | undefined => {
|
||||||
if (backendString.includes('cu12.0')) return 'cuda-cu12.0'
|
if (backendString.includes('cuda-12-common_cpus')) return 'cuda-cu12.0'
|
||||||
if (backendString.includes('cu11.7')) return 'cuda-cu11.7'
|
if (backendString.includes('cuda-11-common_cpus')) return 'cuda-cu11.7'
|
||||||
if (backendString.includes('vulkan')) return 'vulkan'
|
if (backendString.includes('vulkan-common_cpus')) return 'vulkan'
|
||||||
if (backendString.includes('avx512')) return 'avx512'
|
if (backendString.includes('avx512')) return 'avx512'
|
||||||
if (backendString.includes('avx2')) return 'avx2'
|
if (backendString.includes('avx2')) return 'avx2'
|
||||||
if (
|
if (
|
||||||
@ -1644,18 +1646,20 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
if (cfg.device.length > 0) args.push('--device', cfg.device)
|
if (cfg.device.length > 0) args.push('--device', cfg.device)
|
||||||
if (cfg.split_mode.length > 0 && cfg.split_mode != 'layer')
|
if (cfg.split_mode.length > 0 && cfg.split_mode != 'layer')
|
||||||
args.push('--split-mode', cfg.split_mode)
|
args.push('--split-mode', cfg.split_mode)
|
||||||
if (cfg.main_gpu !== undefined && cfg.main_gpu != 0)
|
if (cfg.main_gpu !== undefined && cfg.main_gpu !== 0)
|
||||||
args.push('--main-gpu', String(cfg.main_gpu))
|
args.push('--main-gpu', String(cfg.main_gpu))
|
||||||
|
// Note: Older llama.cpp versions are no longer supported
|
||||||
|
if (
|
||||||
|
cfg.flash_attn !== undefined ||
|
||||||
|
!cfg.flash_attn ||
|
||||||
|
cfg.flash_attn !== ''
|
||||||
|
)
|
||||||
|
args.push('--flash-attn', String(cfg.flash_attn)) //default: auto = ON when supported
|
||||||
|
|
||||||
// Boolean flags
|
// Boolean flags
|
||||||
if (cfg.ctx_shift) args.push('--context-shift')
|
if (cfg.ctx_shift) args.push('--context-shift')
|
||||||
if (Number(version.replace(/^b/, '')) >= 6325) {
|
|
||||||
if (!cfg.flash_attn) args.push('--flash-attn', 'off') //default: auto = ON when supported
|
|
||||||
} else {
|
|
||||||
if (cfg.flash_attn) args.push('--flash-attn')
|
|
||||||
}
|
|
||||||
if (cfg.cont_batching) args.push('--cont-batching')
|
if (cfg.cont_batching) args.push('--cont-batching')
|
||||||
args.push('--no-mmap')
|
if (cfg.no_mmap) args.push('--no-mmap')
|
||||||
if (cfg.mlock) args.push('--mlock')
|
if (cfg.mlock) args.push('--mlock')
|
||||||
if (cfg.no_kv_offload) args.push('--no-kv-offload')
|
if (cfg.no_kv_offload) args.push('--no-kv-offload')
|
||||||
if (isEmbedding) {
|
if (isEmbedding) {
|
||||||
@ -1667,7 +1671,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
if (cfg.cache_type_k && cfg.cache_type_k != 'f16')
|
if (cfg.cache_type_k && cfg.cache_type_k != 'f16')
|
||||||
args.push('--cache-type-k', cfg.cache_type_k)
|
args.push('--cache-type-k', cfg.cache_type_k)
|
||||||
if (
|
if (
|
||||||
cfg.flash_attn &&
|
cfg.flash_attn !== 'on' &&
|
||||||
cfg.cache_type_v != 'f16' &&
|
cfg.cache_type_v != 'f16' &&
|
||||||
cfg.cache_type_v != 'f32'
|
cfg.cache_type_v != 'f32'
|
||||||
) {
|
) {
|
||||||
@ -1688,20 +1692,9 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
|
|
||||||
logger.info('Calling Tauri command llama_load with args:', args)
|
logger.info('Calling Tauri command llama_load with args:', args)
|
||||||
const backendPath = await getBackendExePath(backend, version)
|
const backendPath = await getBackendExePath(backend, version)
|
||||||
const libraryPath = await joinPath([await this.getProviderPath(), 'lib'])
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: add LIBRARY_PATH
|
const sInfo = await loadLlamaModel(backendPath, args, envs, isEmbedding)
|
||||||
const sInfo = await invoke<SessionInfo>(
|
|
||||||
'plugin:llamacpp|load_llama_model',
|
|
||||||
{
|
|
||||||
backendPath,
|
|
||||||
libraryPath,
|
|
||||||
args,
|
|
||||||
envs,
|
|
||||||
isEmbedding,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return sInfo
|
return sInfo
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in load command:\n', error)
|
logger.error('Error in load command:\n', error)
|
||||||
@ -1717,12 +1710,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
const pid = sInfo.pid
|
const pid = sInfo.pid
|
||||||
try {
|
try {
|
||||||
// Pass the PID as the session_id
|
// Pass the PID as the session_id
|
||||||
const result = await invoke<UnloadResult>(
|
const result = await unloadLlamaModel(pid)
|
||||||
'plugin:llamacpp|unload_llama_model',
|
|
||||||
{
|
|
||||||
pid: pid,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// If successful, remove from active sessions
|
// If successful, remove from active sessions
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@ -2042,7 +2030,10 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
if (sysInfo?.os_type === 'linux' && Array.isArray(sysInfo.gpus)) {
|
if (sysInfo?.os_type === 'linux' && Array.isArray(sysInfo.gpus)) {
|
||||||
const usage = await getSystemUsage()
|
const usage = await getSystemUsage()
|
||||||
if (usage && Array.isArray(usage.gpus)) {
|
if (usage && Array.isArray(usage.gpus)) {
|
||||||
const uuidToUsage: Record<string, { total_memory: number; used_memory: number }> = {}
|
const uuidToUsage: Record<
|
||||||
|
string,
|
||||||
|
{ total_memory: number; used_memory: number }
|
||||||
|
> = {}
|
||||||
for (const u of usage.gpus as any[]) {
|
for (const u of usage.gpus as any[]) {
|
||||||
if (u && typeof u.uuid === 'string') {
|
if (u && typeof u.uuid === 'string') {
|
||||||
uuidToUsage[u.uuid] = u
|
uuidToUsage[u.uuid] = u
|
||||||
@ -2082,7 +2073,10 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
typeof u.used_memory === 'number'
|
typeof u.used_memory === 'number'
|
||||||
) {
|
) {
|
||||||
const total = Math.max(0, Math.floor(u.total_memory))
|
const total = Math.max(0, Math.floor(u.total_memory))
|
||||||
const free = Math.max(0, Math.floor(u.total_memory - u.used_memory))
|
const free = Math.max(
|
||||||
|
0,
|
||||||
|
Math.floor(u.total_memory - u.used_memory)
|
||||||
|
)
|
||||||
return { ...dev, mem: total, free }
|
return { ...dev, mem: total, free }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,18 @@ import { invoke } from '@tauri-apps/api/core'
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
export interface SessionInfo {
|
export interface SessionInfo {
|
||||||
pid: number
|
pid: number;
|
||||||
port: number
|
port: number;
|
||||||
model_id: string
|
model_id: string;
|
||||||
model_path: string
|
model_path: string;
|
||||||
api_key: string
|
is_embedding: boolean
|
||||||
|
api_key: string;
|
||||||
|
mmproj_path?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnloadResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceInfo {
|
export interface DeviceInfo {
|
||||||
@ -29,19 +36,19 @@ export async function cleanupLlamaProcesses(): Promise<void> {
|
|||||||
// LlamaCpp server commands
|
// LlamaCpp server commands
|
||||||
export async function loadLlamaModel(
|
export async function loadLlamaModel(
|
||||||
backendPath: string,
|
backendPath: string,
|
||||||
libraryPath?: string,
|
args: string[],
|
||||||
args: string[] = [],
|
envs: Record<string, string>,
|
||||||
isEmbedding: boolean = false
|
isEmbedding: boolean
|
||||||
): Promise<SessionInfo> {
|
): Promise<SessionInfo> {
|
||||||
return await invoke('plugin:llamacpp|load_llama_model', {
|
return await invoke('plugin:llamacpp|load_llama_model', {
|
||||||
backendPath,
|
backendPath,
|
||||||
libraryPath,
|
|
||||||
args,
|
args,
|
||||||
isEmbedding,
|
envs,
|
||||||
|
isEmbedding
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unloadLlamaModel(pid: number): Promise<void> {
|
export async function unloadLlamaModel(pid: number): Promise<UnloadResult> {
|
||||||
return await invoke('plugin:llamacpp|unload_llama_model', { pid })
|
return await invoke('plugin:llamacpp|unload_llama_model', { pid })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,6 @@ pub struct UnloadResult {
|
|||||||
pub async fn load_llama_model<R: Runtime>(
|
pub async fn load_llama_model<R: Runtime>(
|
||||||
app_handle: tauri::AppHandle<R>,
|
app_handle: tauri::AppHandle<R>,
|
||||||
backend_path: &str,
|
backend_path: &str,
|
||||||
library_path: Option<&str>,
|
|
||||||
mut args: Vec<String>,
|
mut args: Vec<String>,
|
||||||
envs: HashMap<String, String>,
|
envs: HashMap<String, String>,
|
||||||
is_embedding: bool,
|
is_embedding: bool,
|
||||||
@ -52,7 +51,7 @@ pub async fn load_llama_model<R: Runtime>(
|
|||||||
log::info!("Attempting to launch server at path: {:?}", backend_path);
|
log::info!("Attempting to launch server at path: {:?}", backend_path);
|
||||||
log::info!("Using arguments: {:?}", args);
|
log::info!("Using arguments: {:?}", args);
|
||||||
|
|
||||||
validate_binary_path(backend_path)?;
|
let bin_path = validate_binary_path(backend_path)?;
|
||||||
|
|
||||||
let port = parse_port_from_args(&args);
|
let port = parse_port_from_args(&args);
|
||||||
let model_path_pb = validate_model_path(&mut args)?;
|
let model_path_pb = validate_model_path(&mut args)?;
|
||||||
@ -83,11 +82,11 @@ pub async fn load_llama_model<R: Runtime>(
|
|||||||
let model_id = extract_arg_value(&args, "-a");
|
let model_id = extract_arg_value(&args, "-a");
|
||||||
|
|
||||||
// Configure the command to run the server
|
// Configure the command to run the server
|
||||||
let mut command = Command::new(backend_path);
|
let mut command = Command::new(&bin_path);
|
||||||
command.args(args);
|
command.args(args);
|
||||||
command.envs(envs);
|
command.envs(envs);
|
||||||
|
|
||||||
setup_library_path(library_path, &mut command);
|
setup_library_path(bin_path.parent().and_then(|p| p.to_str()), &mut command);
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
command.stderr(Stdio::piped());
|
command.stderr(Stdio::piped());
|
||||||
setup_windows_process_flags(&mut command);
|
setup_windows_process_flags(&mut command);
|
||||||
@ -280,10 +279,9 @@ pub async fn unload_llama_model<R: Runtime>(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_devices(
|
pub async fn get_devices(
|
||||||
backend_path: &str,
|
backend_path: &str,
|
||||||
library_path: Option<&str>,
|
|
||||||
envs: HashMap<String, String>,
|
envs: HashMap<String, String>,
|
||||||
) -> ServerResult<Vec<DeviceInfo>> {
|
) -> ServerResult<Vec<DeviceInfo>> {
|
||||||
get_devices_from_backend(backend_path, library_path, envs).await
|
get_devices_from_backend(backend_path, envs).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate API key using HMAC-SHA256
|
/// Generate API key using HMAC-SHA256
|
||||||
|
|||||||
@ -19,20 +19,19 @@ pub struct DeviceInfo {
|
|||||||
|
|
||||||
pub async fn get_devices_from_backend(
|
pub async fn get_devices_from_backend(
|
||||||
backend_path: &str,
|
backend_path: &str,
|
||||||
library_path: Option<&str>,
|
|
||||||
envs: HashMap<String, String>,
|
envs: HashMap<String, String>,
|
||||||
) -> ServerResult<Vec<DeviceInfo>> {
|
) -> ServerResult<Vec<DeviceInfo>> {
|
||||||
log::info!("Getting devices from server at path: {:?}", backend_path);
|
log::info!("Getting devices from server at path: {:?}", backend_path);
|
||||||
|
|
||||||
validate_binary_path(backend_path)?;
|
let bin_path = validate_binary_path(backend_path)?;
|
||||||
|
|
||||||
// Configure the command to run the server with --list-devices
|
// Configure the command to run the server with --list-devices
|
||||||
let mut command = Command::new(backend_path);
|
let mut command = Command::new(&bin_path);
|
||||||
command.arg("--list-devices");
|
command.arg("--list-devices");
|
||||||
command.envs(envs);
|
command.envs(envs);
|
||||||
|
|
||||||
// Set up library path
|
// Set up library path
|
||||||
setup_library_path(library_path, &mut command);
|
setup_library_path(bin_path.parent().and_then(|p| p.to_str()), &mut command);
|
||||||
|
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
command.stderr(Stdio::piped());
|
command.stderr(Stdio::piped());
|
||||||
@ -410,4 +409,4 @@ AnotherInvalid
|
|||||||
assert_eq!(result[0].id, "Vulkan0");
|
assert_eq!(result[0].id, "Vulkan0");
|
||||||
assert_eq!(result[1].id, "CUDA0");
|
assert_eq!(result[1].id, "CUDA0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,6 @@ pub async fn estimate_kv_cache_internal(
|
|||||||
ctx_size: Option<u64>,
|
ctx_size: Option<u64>,
|
||||||
) -> Result<KVCacheEstimate, KVCacheError> {
|
) -> Result<KVCacheEstimate, KVCacheError> {
|
||||||
log::info!("Received ctx_size parameter: {:?}", ctx_size);
|
log::info!("Received ctx_size parameter: {:?}", ctx_size);
|
||||||
log::info!("Received model metadata:\n{:?}", &meta);
|
|
||||||
let arch = meta
|
let arch = meta
|
||||||
.get("general.architecture")
|
.get("general.architecture")
|
||||||
.ok_or(KVCacheError::ArchitectureNotFound)?;
|
.ok_or(KVCacheError::ArchitectureNotFound)?;
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
IconAtom,
|
IconAtom,
|
||||||
IconWorld,
|
IconWorld,
|
||||||
IconCodeCircle2,
|
IconCodeCircle2,
|
||||||
IconSparkles,
|
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { Fragment } from 'react/jsx-runtime'
|
import { Fragment } from 'react/jsx-runtime'
|
||||||
|
|
||||||
@ -30,8 +29,6 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
|
|||||||
icon = <IconEye className="size-4" />
|
icon = <IconEye className="size-4" />
|
||||||
} else if (capability === 'tools') {
|
} else if (capability === 'tools') {
|
||||||
icon = <IconTool className="size-3.5" />
|
icon = <IconTool className="size-3.5" />
|
||||||
} else if (capability === 'proactive') {
|
|
||||||
icon = <IconSparkles className="size-3.5" />
|
|
||||||
} else if (capability === 'reasoning') {
|
} else if (capability === 'reasoning') {
|
||||||
icon = <IconAtom className="size-3.5" />
|
icon = <IconAtom className="size-3.5" />
|
||||||
} else if (capability === 'embeddings') {
|
} else if (capability === 'embeddings') {
|
||||||
@ -57,11 +54,7 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>
|
||||||
{capability === 'web_search'
|
{capability === 'web_search' ? 'Web Search' : capability}
|
||||||
? 'Web Search'
|
|
||||||
: capability === 'proactive'
|
|
||||||
? 'Proactive'
|
|
||||||
: capability}
|
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -16,7 +16,6 @@ const LANGUAGES = [
|
|||||||
{ value: 'zh-CN', label: '简体中文' },
|
{ value: 'zh-CN', label: '简体中文' },
|
||||||
{ value: 'zh-TW', label: '繁體中文' },
|
{ value: 'zh-TW', label: '繁體中文' },
|
||||||
{ value: 'de-DE', label: 'Deutsch' },
|
{ value: 'de-DE', label: 'Deutsch' },
|
||||||
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
|
||||||
{ value: 'ja', label: '日本語' },
|
{ value: 'ja', label: '日本語' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -152,19 +152,12 @@ export const ModelInfoHoverCard = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Features Section */}
|
{/* Features Section */}
|
||||||
{(model.num_mmproj > 0 || model.tools || (model.num_mmproj > 0 && model.tools)) && (
|
{(model.num_mmproj > 0 || model.tools) && (
|
||||||
<div className="border-t border-main-view-fg/10 pt-3">
|
<div className="border-t border-main-view-fg/10 pt-3">
|
||||||
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
|
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
|
||||||
Features
|
Features
|
||||||
</h5>
|
</h5>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{model.tools && (
|
|
||||||
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
|
||||||
<span className="text-xs text-main-view-fg font-medium">
|
|
||||||
Tools
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{model.num_mmproj > 0 && (
|
{model.num_mmproj > 0 && (
|
||||||
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
||||||
<span className="text-xs text-main-view-fg font-medium">
|
<span className="text-xs text-main-view-fg font-medium">
|
||||||
@ -172,10 +165,10 @@ export const ModelInfoHoverCard = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{model.num_mmproj > 0 && model.tools && (
|
{model.tools && (
|
||||||
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
||||||
<span className="text-xs text-main-view-fg font-medium">
|
<span className="text-xs text-main-view-fg font-medium">
|
||||||
Proactive
|
Tools
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,124 +0,0 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
|
||||||
import Capabilities from '../Capabilities'
|
|
||||||
|
|
||||||
// Mock Tooltip components
|
|
||||||
vi.mock('@/components/ui/tooltip', () => ({
|
|
||||||
Tooltip: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
||||||
TooltipContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
||||||
TooltipProvider: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
||||||
TooltipTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock Tabler icons
|
|
||||||
vi.mock('@tabler/icons-react', () => ({
|
|
||||||
IconEye: () => <div data-testid="icon-eye">Eye Icon</div>,
|
|
||||||
IconTool: () => <div data-testid="icon-tool">Tool Icon</div>,
|
|
||||||
IconSparkles: () => <div data-testid="icon-sparkles">Sparkles Icon</div>,
|
|
||||||
IconAtom: () => <div data-testid="icon-atom">Atom Icon</div>,
|
|
||||||
IconWorld: () => <div data-testid="icon-world">World Icon</div>,
|
|
||||||
IconCodeCircle2: () => <div data-testid="icon-code">Code Icon</div>,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('Capabilities', () => {
|
|
||||||
it('should render vision capability with eye icon', () => {
|
|
||||||
render(<Capabilities capabilities={['vision']} />)
|
|
||||||
|
|
||||||
const eyeIcon = screen.getByTestId('icon-eye')
|
|
||||||
expect(eyeIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render tools capability with tool icon', () => {
|
|
||||||
render(<Capabilities capabilities={['tools']} />)
|
|
||||||
|
|
||||||
const toolIcon = screen.getByTestId('icon-tool')
|
|
||||||
expect(toolIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render proactive capability with sparkles icon', () => {
|
|
||||||
render(<Capabilities capabilities={['proactive']} />)
|
|
||||||
|
|
||||||
const sparklesIcon = screen.getByTestId('icon-sparkles')
|
|
||||||
expect(sparklesIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render reasoning capability with atom icon', () => {
|
|
||||||
render(<Capabilities capabilities={['reasoning']} />)
|
|
||||||
|
|
||||||
const atomIcon = screen.getByTestId('icon-atom')
|
|
||||||
expect(atomIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render web_search capability with world icon', () => {
|
|
||||||
render(<Capabilities capabilities={['web_search']} />)
|
|
||||||
|
|
||||||
const worldIcon = screen.getByTestId('icon-world')
|
|
||||||
expect(worldIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render embeddings capability with code icon', () => {
|
|
||||||
render(<Capabilities capabilities={['embeddings']} />)
|
|
||||||
|
|
||||||
const codeIcon = screen.getByTestId('icon-code')
|
|
||||||
expect(codeIcon).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render multiple capabilities', () => {
|
|
||||||
render(<Capabilities capabilities={['tools', 'vision', 'proactive']} />)
|
|
||||||
|
|
||||||
expect(screen.getByTestId('icon-tool')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-eye')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-sparkles')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render all capabilities in correct order', () => {
|
|
||||||
render(<Capabilities capabilities={['tools', 'vision', 'proactive', 'reasoning', 'web_search', 'embeddings']} />)
|
|
||||||
|
|
||||||
expect(screen.getByTestId('icon-tool')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-eye')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-sparkles')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-atom')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-world')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-code')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle empty capabilities array', () => {
|
|
||||||
const { container } = render(<Capabilities capabilities={[]} />)
|
|
||||||
|
|
||||||
expect(container.querySelector('[data-testid^="icon-"]')).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle unknown capabilities gracefully', () => {
|
|
||||||
const { container } = render(<Capabilities capabilities={['unknown_capability']} />)
|
|
||||||
|
|
||||||
expect(container).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should display proactive tooltip with correct text', () => {
|
|
||||||
render(<Capabilities capabilities={['proactive']} />)
|
|
||||||
|
|
||||||
// The tooltip content should be 'Proactive'
|
|
||||||
expect(screen.getByTestId('icon-sparkles')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render proactive icon between tools/vision and reasoning', () => {
|
|
||||||
const { container } = render(<Capabilities capabilities={['tools', 'vision', 'proactive', 'reasoning']} />)
|
|
||||||
|
|
||||||
// All icons should be rendered
|
|
||||||
expect(screen.getByTestId('icon-tool')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-eye')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-sparkles')).toBeInTheDocument()
|
|
||||||
expect(screen.getByTestId('icon-atom')).toBeInTheDocument()
|
|
||||||
|
|
||||||
expect(container.querySelector('[data-testid="icon-sparkles"]')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply correct CSS classes to proactive icon', () => {
|
|
||||||
render(<Capabilities capabilities={['proactive']} />)
|
|
||||||
|
|
||||||
const sparklesIcon = screen.getByTestId('icon-sparkles')
|
|
||||||
expect(sparklesIcon).toBeInTheDocument()
|
|
||||||
// Icon should have size-3.5 class (same as tools, reasoning, etc.)
|
|
||||||
expect(sparklesIcon.parentElement).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -437,31 +437,4 @@ describe('ChatInput', () => {
|
|||||||
expect(() => renderWithRouter()).not.toThrow()
|
expect(() => renderWithRouter()).not.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Proactive Mode', () => {
|
|
||||||
it('should render ChatInput with proactive capable model', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
renderWithRouter()
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(screen.getByTestId('chat-input')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle proactive capability detection', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
renderWithRouter()
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(screen.getByTestId('chat-input')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should work with models that have multiple capabilities', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
renderWithRouter()
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(screen.getByTestId('chat-input')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -82,7 +82,6 @@ vi.mock('@tabler/icons-react', () => ({
|
|||||||
IconEye: () => <div data-testid="eye-icon" />,
|
IconEye: () => <div data-testid="eye-icon" />,
|
||||||
IconTool: () => <div data-testid="tool-icon" />,
|
IconTool: () => <div data-testid="tool-icon" />,
|
||||||
IconLoader2: () => <div data-testid="loader-icon" />,
|
IconLoader2: () => <div data-testid="loader-icon" />,
|
||||||
IconSparkles: () => <div data-testid="sparkles-icon" />,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('DialogEditModel - Basic Component Tests', () => {
|
describe('DialogEditModel - Basic Component Tests', () => {
|
||||||
@ -190,7 +189,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
{
|
{
|
||||||
id: 'test-model.gguf',
|
id: 'test-model.gguf',
|
||||||
displayName: 'Test Model',
|
displayName: 'Test Model',
|
||||||
capabilities: ['vision', 'tools', 'proactive'],
|
capabilities: ['vision', 'tools'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: [],
|
settings: [],
|
||||||
@ -227,7 +226,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
{
|
{
|
||||||
id: 'test-model.gguf',
|
id: 'test-model.gguf',
|
||||||
displayName: 'Test Model',
|
displayName: 'Test Model',
|
||||||
capabilities: ['vision', 'tools', 'proactive', 'completion', 'embeddings', 'web_search', 'reasoning'],
|
capabilities: ['vision', 'tools', 'completion', 'embeddings', 'web_search', 'reasoning'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: [],
|
settings: [],
|
||||||
@ -241,7 +240,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Component should render without errors even with extra capabilities
|
// Component should render without errors even with extra capabilities
|
||||||
// The capabilities helper should only extract vision, tools, and proactive
|
// The capabilities helper should only extract vision and tools
|
||||||
expect(container).toBeInTheDocument()
|
expect(container).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
IconTool,
|
IconTool,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconLoader2,
|
IconLoader2,
|
||||||
IconSparkles,
|
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||||
@ -46,7 +45,6 @@ export const DialogEditModel = ({
|
|||||||
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
|
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
|
||||||
vision: false,
|
vision: false,
|
||||||
tools: false,
|
tools: false,
|
||||||
proactive: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize with the provided model ID or the first model if available
|
// Initialize with the provided model ID or the first model if available
|
||||||
@ -69,7 +67,6 @@ export const DialogEditModel = ({
|
|||||||
const capabilitiesToObject = (capabilitiesList: string[]) => ({
|
const capabilitiesToObject = (capabilitiesList: string[]) => ({
|
||||||
vision: capabilitiesList.includes('vision'),
|
vision: capabilitiesList.includes('vision'),
|
||||||
tools: capabilitiesList.includes('tools'),
|
tools: capabilitiesList.includes('tools'),
|
||||||
proactive: capabilitiesList.includes('proactive'),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize capabilities and display name from selected model
|
// Initialize capabilities and display name from selected model
|
||||||
@ -271,23 +268,6 @@ export const DialogEditModel = ({
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<IconSparkles className="size-4 text-main-view-fg/70" />
|
|
||||||
<span className="text-sm">
|
|
||||||
{t('providers:editModel.proactive')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id="proactive-capability"
|
|
||||||
checked={capabilities.proactive}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleCapabilityChange('proactive', checked)
|
|
||||||
}
|
|
||||||
disabled={isLoading || !(capabilities.tools && capabilities.vision)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -170,7 +170,6 @@ vi.mock('@/lib/completion', () => ({
|
|||||||
sendCompletion: vi.fn(),
|
sendCompletion: vi.fn(),
|
||||||
postMessageProcessing: vi.fn(),
|
postMessageProcessing: vi.fn(),
|
||||||
isCompletionResponse: vi.fn(),
|
isCompletionResponse: vi.fn(),
|
||||||
captureProactiveScreenshots: vi.fn(() => Promise.resolve([])),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/lib/messages', () => ({
|
vi.mock('@/lib/messages', () => ({
|
||||||
@ -226,26 +225,4 @@ describe('useChat', () => {
|
|||||||
|
|
||||||
expect(result.current).toBeDefined()
|
expect(result.current).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Proactive Mode', () => {
|
|
||||||
it('should detect proactive mode when model has proactive capability', () => {
|
|
||||||
const { result } = renderHook(() => useChat())
|
|
||||||
|
|
||||||
expect(result.current).toBeDefined()
|
|
||||||
expect(typeof result.current).toBe('function')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle model with tools, vision, and proactive capabilities', () => {
|
|
||||||
const { result } = renderHook(() => useChat())
|
|
||||||
|
|
||||||
expect(result.current).toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should work with models that have proactive capability', () => {
|
|
||||||
const { result } = renderHook(() => useChat())
|
|
||||||
|
|
||||||
expect(result.current).toBeDefined()
|
|
||||||
expect(typeof result.current).toBe('function')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
newUserThreadContent,
|
newUserThreadContent,
|
||||||
postMessageProcessing,
|
postMessageProcessing,
|
||||||
sendCompletion,
|
sendCompletion,
|
||||||
captureProactiveScreenshots,
|
|
||||||
} from '@/lib/completion'
|
} from '@/lib/completion'
|
||||||
import { CompletionMessagesBuilder } from '@/lib/messages'
|
import { CompletionMessagesBuilder } from '@/lib/messages'
|
||||||
import { renderInstructions } from '@/lib/instructionTemplate'
|
import { renderInstructions } from '@/lib/instructionTemplate'
|
||||||
@ -420,27 +419,6 @@ export const useChat = () => {
|
|||||||
})
|
})
|
||||||
: []
|
: []
|
||||||
|
|
||||||
// Check if proactive mode is enabled
|
|
||||||
const isProactiveMode = selectedModel?.capabilities?.includes('proactive') ?? false
|
|
||||||
|
|
||||||
// Proactive mode: Capture initial screenshot/snapshot before first LLM call
|
|
||||||
if (isProactiveMode && availableTools.length > 0 && !abortController.signal.aborted) {
|
|
||||||
console.log('Proactive mode: Capturing initial screenshots before LLM call')
|
|
||||||
try {
|
|
||||||
const initialScreenshots = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
// Add initial screenshots to builder
|
|
||||||
for (const screenshot of initialScreenshots) {
|
|
||||||
// Generate unique tool call ID for initial screenshot
|
|
||||||
const proactiveToolCallId = `proactive_initial_${Date.now()}_${Math.random()}`
|
|
||||||
builder.addToolMessage(screenshot, proactiveToolCallId)
|
|
||||||
console.log('Initial proactive screenshot added to context')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Failed to capture initial proactive screenshots:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let assistantLoopSteps = 0
|
let assistantLoopSteps = 0
|
||||||
|
|
||||||
while (
|
while (
|
||||||
@ -716,10 +694,6 @@ export const useChat = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder.addAssistantMessage(accumulatedText, undefined, toolCalls)
|
builder.addAssistantMessage(accumulatedText, undefined, toolCalls)
|
||||||
|
|
||||||
// Check if proactive mode is enabled for this model
|
|
||||||
const isProactiveMode = selectedModel?.capabilities?.includes('proactive') ?? false
|
|
||||||
|
|
||||||
const updatedMessage = await postMessageProcessing(
|
const updatedMessage = await postMessageProcessing(
|
||||||
toolCalls,
|
toolCalls,
|
||||||
builder,
|
builder,
|
||||||
@ -727,8 +701,7 @@ export const useChat = () => {
|
|||||||
abortController,
|
abortController,
|
||||||
useToolApproval.getState().approvedTools,
|
useToolApproval.getState().approvedTools,
|
||||||
allowAllMCPPermissions ? undefined : showApprovalModal,
|
allowAllMCPPermissions ? undefined : showApprovalModal,
|
||||||
allowAllMCPPermissions,
|
allowAllMCPPermissions
|
||||||
isProactiveMode
|
|
||||||
)
|
)
|
||||||
addMessage(updatedMessage ?? finalContent)
|
addMessage(updatedMessage ?? finalContent)
|
||||||
updateStreamingContent(emptyThreadContent)
|
updateStreamingContent(emptyThreadContent)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
import {
|
import {
|
||||||
newUserThreadContent,
|
newUserThreadContent,
|
||||||
newAssistantThreadContent,
|
newAssistantThreadContent,
|
||||||
emptyThreadContent,
|
emptyThreadContent,
|
||||||
@ -8,8 +8,7 @@ import {
|
|||||||
stopModel,
|
stopModel,
|
||||||
normalizeTools,
|
normalizeTools,
|
||||||
extractToolCall,
|
extractToolCall,
|
||||||
postMessageProcessing,
|
postMessageProcessing
|
||||||
captureProactiveScreenshots
|
|
||||||
} from '../completion'
|
} from '../completion'
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
@ -73,54 +72,6 @@ vi.mock('../extension', () => ({
|
|||||||
ExtensionManager: {},
|
ExtensionManager: {},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/hooks/useServiceHub', () => ({
|
|
||||||
getServiceHub: vi.fn(() => ({
|
|
||||||
mcp: vi.fn(() => ({
|
|
||||||
getTools: vi.fn(() => Promise.resolve([])),
|
|
||||||
callToolWithCancellation: vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({
|
|
||||||
content: [{ type: 'text', text: 'mock result' }],
|
|
||||||
error: '',
|
|
||||||
}),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
rag: vi.fn(() => ({
|
|
||||||
getToolNames: vi.fn(() => Promise.resolve([])),
|
|
||||||
callTool: vi.fn(() => Promise.resolve({
|
|
||||||
content: [{ type: 'text', text: 'mock rag result' }],
|
|
||||||
error: '',
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/hooks/useAttachments', () => ({
|
|
||||||
useAttachments: {
|
|
||||||
getState: vi.fn(() => ({ enabled: true })),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/hooks/useAppState', () => ({
|
|
||||||
useAppState: {
|
|
||||||
getState: vi.fn(() => ({
|
|
||||||
setCancelToolCall: vi.fn(),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/lib/platform/const', () => ({
|
|
||||||
PlatformFeatures: {
|
|
||||||
ATTACHMENTS: true,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/lib/platform/types', () => ({
|
|
||||||
PlatformFeature: {
|
|
||||||
ATTACHMENTS: 'ATTACHMENTS',
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('completion.ts', () => {
|
describe('completion.ts', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@ -236,448 +187,4 @@ describe('completion.ts', () => {
|
|||||||
expect(result.length).toBe(0)
|
expect(result.length).toBe(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Proactive Mode - Browser MCP Tool Detection', () => {
|
|
||||||
// We need to access the private function, so we'll test it through postMessageProcessing
|
|
||||||
it('should detect browser tool names with "browser" prefix', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([]))
|
|
||||||
const mockMcp = {
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({ content: [{ type: 'text', text: 'result' }], error: '' }),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => mockMcp,
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'browserbase_navigate', arguments: '{"url": "test.com"}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(
|
|
||||||
calls,
|
|
||||||
builder,
|
|
||||||
message,
|
|
||||||
abortController,
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
true // isProactiveMode = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// Verify tool was executed
|
|
||||||
expect(mockMcp.callToolWithCancellation).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should detect browserbase tools', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({ content: [{ type: 'text', text: 'result' }], error: '' }),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: () => Promise.resolve([]),
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'browserbase_screenshot', arguments: '{}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(calls, builder, message, abortController, {}, undefined, false, true)
|
|
||||||
|
|
||||||
expect(mockCallTool).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should detect multi_browserbase tools', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({ content: [{ type: 'text', text: 'result' }], error: '' }),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: () => Promise.resolve([]),
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'multi_browserbase_stagehand_navigate', arguments: '{}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(calls, builder, message, abortController, {}, undefined, false, true)
|
|
||||||
|
|
||||||
expect(mockCallTool).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not treat non-browser tools as browser tools', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([]))
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({ content: [{ type: 'text', text: 'result' }], error: '' }),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'fetch_url', arguments: '{"url": "test.com"}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(calls, builder, message, abortController, {}, undefined, false, true)
|
|
||||||
|
|
||||||
// Proactive screenshots should not be called for non-browser tools
|
|
||||||
expect(mockGetTools).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Proactive Mode - Screenshot Capture', () => {
|
|
||||||
it('should capture screenshot and snapshot when available', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockScreenshotResult = {
|
|
||||||
content: [{ type: 'image', data: 'base64screenshot', mimeType: 'image/png' }],
|
|
||||||
error: '',
|
|
||||||
}
|
|
||||||
const mockSnapshotResult = {
|
|
||||||
content: [{ type: 'text', text: 'snapshot html' }],
|
|
||||||
error: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'browserbase_screenshot', inputSchema: {} },
|
|
||||||
{ name: 'browserbase_snapshot', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
const mockCallTool = vi.fn()
|
|
||||||
.mockReturnValueOnce({
|
|
||||||
promise: Promise.resolve(mockScreenshotResult),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
})
|
|
||||||
.mockReturnValueOnce({
|
|
||||||
promise: Promise.resolve(mockSnapshotResult),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
})
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
})
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const abortController = new AbortController()
|
|
||||||
const results = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
expect(results).toHaveLength(2)
|
|
||||||
expect(results[0]).toEqual(mockScreenshotResult)
|
|
||||||
expect(results[1]).toEqual(mockSnapshotResult)
|
|
||||||
expect(mockCallTool).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle missing screenshot tool gracefully', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'some_other_tool', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: vi.fn()
|
|
||||||
})
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const abortController = new AbortController()
|
|
||||||
const results = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
expect(results).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle screenshot capture errors gracefully', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'browserbase_screenshot', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: Promise.reject(new Error('Screenshot failed')),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
})
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const abortController = new AbortController()
|
|
||||||
const results = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
// Should return empty array on error, not throw
|
|
||||||
expect(results).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respect abort controller', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'browserbase_screenshot', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: new Promise((resolve) => setTimeout(() => resolve({
|
|
||||||
content: [{ type: 'image', data: 'base64', mimeType: 'image/png' }],
|
|
||||||
error: '',
|
|
||||||
}), 100)),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
})
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const abortController = new AbortController()
|
|
||||||
abortController.abort()
|
|
||||||
|
|
||||||
const results = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
// Should not attempt to capture if already aborted
|
|
||||||
expect(results).toHaveLength(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Proactive Mode - Screenshot Filtering', () => {
|
|
||||||
it('should filter out old image_url content from tool messages', () => {
|
|
||||||
const builder = {
|
|
||||||
messages: [
|
|
||||||
{ role: 'user', content: 'Hello' },
|
|
||||||
{
|
|
||||||
role: 'tool',
|
|
||||||
content: [
|
|
||||||
{ type: 'text', text: 'Tool result' },
|
|
||||||
{ type: 'image_url', image_url: { url: 'data:image/png;base64,old' } }
|
|
||||||
],
|
|
||||||
tool_call_id: 'old_call'
|
|
||||||
},
|
|
||||||
{ role: 'assistant', content: 'Response' },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(builder.messages).toHaveLength(3)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Proactive Mode - Integration', () => {
|
|
||||||
it('should trigger proactive screenshots after browser tool execution', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
|
|
||||||
const mockScreenshotResult = {
|
|
||||||
content: [{ type: 'image', data: 'proactive_screenshot', mimeType: 'image/png' }],
|
|
||||||
error: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'browserbase_screenshot', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
|
|
||||||
let callCount = 0
|
|
||||||
const mockCallTool = vi.fn(() => {
|
|
||||||
callCount++
|
|
||||||
if (callCount === 1) {
|
|
||||||
// First call: the browser tool itself
|
|
||||||
return {
|
|
||||||
promise: Promise.resolve({
|
|
||||||
content: [{ type: 'text', text: 'navigated to page' }],
|
|
||||||
error: '',
|
|
||||||
}),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Second call: proactive screenshot
|
|
||||||
return {
|
|
||||||
promise: Promise.resolve(mockScreenshotResult),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'browserbase_navigate', arguments: '{"url": "test.com"}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(
|
|
||||||
calls,
|
|
||||||
builder,
|
|
||||||
message,
|
|
||||||
abortController,
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
// Should have called: 1) browser tool, 2) getTools, 3) proactive screenshot
|
|
||||||
expect(mockCallTool).toHaveBeenCalledTimes(2)
|
|
||||||
expect(mockGetTools).toHaveBeenCalled()
|
|
||||||
expect(builder.addToolMessage).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not trigger proactive screenshots when mode is disabled', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([
|
|
||||||
{ name: 'browserbase_screenshot', inputSchema: {} }
|
|
||||||
]))
|
|
||||||
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({
|
|
||||||
content: [{ type: 'text', text: 'navigated' }],
|
|
||||||
error: '',
|
|
||||||
}),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'browserbase_navigate', arguments: '{}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(
|
|
||||||
calls,
|
|
||||||
builder,
|
|
||||||
message,
|
|
||||||
abortController,
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(mockCallTool).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockGetTools).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not trigger proactive screenshots for non-browser tools', async () => {
|
|
||||||
const { getServiceHub } = await import('@/hooks/useServiceHub')
|
|
||||||
|
|
||||||
const mockGetTools = vi.fn(() => Promise.resolve([]))
|
|
||||||
const mockCallTool = vi.fn(() => ({
|
|
||||||
promise: Promise.resolve({
|
|
||||||
content: [{ type: 'text', text: 'fetched data' }],
|
|
||||||
error: '',
|
|
||||||
}),
|
|
||||||
cancel: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mocked(getServiceHub).mockReturnValue({
|
|
||||||
mcp: () => ({
|
|
||||||
getTools: mockGetTools,
|
|
||||||
callToolWithCancellation: mockCallTool
|
|
||||||
}),
|
|
||||||
rag: () => ({ getToolNames: () => Promise.resolve([]) })
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const calls = [{
|
|
||||||
id: 'call_1',
|
|
||||||
type: 'function' as const,
|
|
||||||
function: { name: 'fetch_url', arguments: '{"url": "test.com"}' }
|
|
||||||
}]
|
|
||||||
const builder = {
|
|
||||||
addToolMessage: vi.fn(),
|
|
||||||
getMessages: vi.fn(() => [])
|
|
||||||
} as any
|
|
||||||
const message = { thread_id: 'test-thread', metadata: {} } as any
|
|
||||||
const abortController = new AbortController()
|
|
||||||
|
|
||||||
await postMessageProcessing(
|
|
||||||
calls,
|
|
||||||
builder,
|
|
||||||
message,
|
|
||||||
abortController,
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(mockCallTool).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockGetTools).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -396,120 +396,6 @@ export const extractToolCall = (
|
|||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to check if a tool call is a browser MCP tool
|
|
||||||
* @param toolName - The name of the tool
|
|
||||||
* @returns true if the tool is a browser-related MCP tool
|
|
||||||
*/
|
|
||||||
const isBrowserMCPTool = (toolName: string): boolean => {
|
|
||||||
const browserToolPrefixes = [
|
|
||||||
'browser',
|
|
||||||
'browserbase',
|
|
||||||
'browsermcp',
|
|
||||||
'multi_browserbase',
|
|
||||||
]
|
|
||||||
return browserToolPrefixes.some((prefix) =>
|
|
||||||
toolName.toLowerCase().startsWith(prefix)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to capture screenshot and snapshot proactively
|
|
||||||
* @param abortController - The abort controller for cancellation
|
|
||||||
* @returns Promise with screenshot and snapshot results
|
|
||||||
*/
|
|
||||||
export const captureProactiveScreenshots = async (
|
|
||||||
abortController: AbortController
|
|
||||||
): Promise<ToolResult[]> => {
|
|
||||||
const results: ToolResult[] = []
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get available tools
|
|
||||||
const allTools = await getServiceHub().mcp().getTools()
|
|
||||||
|
|
||||||
// Find screenshot and snapshot tools
|
|
||||||
const screenshotTool = allTools.find((t) =>
|
|
||||||
t.name.toLowerCase().includes('screenshot')
|
|
||||||
)
|
|
||||||
const snapshotTool = allTools.find((t) =>
|
|
||||||
t.name.toLowerCase().includes('snapshot')
|
|
||||||
)
|
|
||||||
|
|
||||||
// Capture screenshot if available
|
|
||||||
if (screenshotTool && !abortController.signal.aborted) {
|
|
||||||
try {
|
|
||||||
const { promise } = getServiceHub().mcp().callToolWithCancellation({
|
|
||||||
toolName: screenshotTool.name,
|
|
||||||
arguments: {},
|
|
||||||
})
|
|
||||||
const screenshotResult = await promise
|
|
||||||
if (screenshotResult && typeof screenshotResult !== 'string') {
|
|
||||||
results.push(screenshotResult as ToolResult)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Failed to capture proactive screenshot:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture snapshot if available
|
|
||||||
if (snapshotTool && !abortController.signal.aborted) {
|
|
||||||
try {
|
|
||||||
const { promise } = getServiceHub().mcp().callToolWithCancellation({
|
|
||||||
toolName: snapshotTool.name,
|
|
||||||
arguments: {},
|
|
||||||
})
|
|
||||||
const snapshotResult = await promise
|
|
||||||
if (snapshotResult && typeof snapshotResult !== 'string') {
|
|
||||||
results.push(snapshotResult as ToolResult)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Failed to capture proactive snapshot:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to get MCP tools for proactive capture:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to filter out old screenshot/snapshot images from builder messages
|
|
||||||
* Keeps only the latest proactive screenshots
|
|
||||||
* @param builder - The completion messages builder
|
|
||||||
*/
|
|
||||||
const filterOldProactiveScreenshots = (builder: CompletionMessagesBuilder) => {
|
|
||||||
const messages = builder.getMessages()
|
|
||||||
const filteredMessages: any[] = []
|
|
||||||
|
|
||||||
for (const msg of messages) {
|
|
||||||
if (msg.role === 'tool') {
|
|
||||||
// If it's a tool message with array content (multimodal)
|
|
||||||
if (Array.isArray(msg.content)) {
|
|
||||||
// Filter out images, keep text only for old tool messages
|
|
||||||
const textOnly = msg.content.filter(
|
|
||||||
(part: any) => part.type !== 'image_url'
|
|
||||||
)
|
|
||||||
if (textOnly.length > 0) {
|
|
||||||
filteredMessages.push({ ...msg, content: textOnly })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Keep string content as-is
|
|
||||||
filteredMessages.push(msg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Keep all non-tool messages
|
|
||||||
filteredMessages.push(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconstruct builder with filtered messages
|
|
||||||
// Note: This is a workaround since CompletionMessagesBuilder doesn't have a setter
|
|
||||||
// We'll need to access the private messages array
|
|
||||||
// eslint-disable-next-line no-extra-semi
|
|
||||||
;(builder as any).messages = filteredMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview Helper function to process the completion response.
|
* @fileoverview Helper function to process the completion response.
|
||||||
* @param calls
|
* @param calls
|
||||||
@ -519,7 +405,6 @@ const filterOldProactiveScreenshots = (builder: CompletionMessagesBuilder) => {
|
|||||||
* @param approvedTools
|
* @param approvedTools
|
||||||
* @param showModal
|
* @param showModal
|
||||||
* @param allowAllMCPPermissions
|
* @param allowAllMCPPermissions
|
||||||
* @param isProactiveMode
|
|
||||||
*/
|
*/
|
||||||
export const postMessageProcessing = async (
|
export const postMessageProcessing = async (
|
||||||
calls: ChatCompletionMessageToolCall[],
|
calls: ChatCompletionMessageToolCall[],
|
||||||
@ -532,8 +417,7 @@ export const postMessageProcessing = async (
|
|||||||
threadId: string,
|
threadId: string,
|
||||||
toolParameters?: object
|
toolParameters?: object
|
||||||
) => Promise<boolean>,
|
) => Promise<boolean>,
|
||||||
allowAllMCPPermissions: boolean = false,
|
allowAllMCPPermissions: boolean = false
|
||||||
isProactiveMode: boolean = false
|
|
||||||
) => {
|
) => {
|
||||||
// Handle completed tool calls
|
// Handle completed tool calls
|
||||||
if (calls.length) {
|
if (calls.length) {
|
||||||
@ -589,7 +473,6 @@ export const postMessageProcessing = async (
|
|||||||
const toolName = toolCall.function.name
|
const toolName = toolCall.function.name
|
||||||
const toolArgs = toolCall.function.arguments.length ? toolParameters : {}
|
const toolArgs = toolCall.function.arguments.length ? toolParameters : {}
|
||||||
const isRagTool = ragToolNames.has(toolName)
|
const isRagTool = ragToolNames.has(toolName)
|
||||||
const isBrowserTool = isBrowserMCPTool(toolName)
|
|
||||||
|
|
||||||
// Auto-approve RAG tools (local/safe operations), require permission for MCP tools
|
// Auto-approve RAG tools (local/safe operations), require permission for MCP tools
|
||||||
const approved = isRagTool
|
const approved = isRagTool
|
||||||
@ -679,27 +562,6 @@ export const postMessageProcessing = async (
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
builder.addToolMessage(result as ToolResult, toolCall.id)
|
builder.addToolMessage(result as ToolResult, toolCall.id)
|
||||||
|
|
||||||
// Proactive mode: Capture screenshot/snapshot after browser tool execution
|
|
||||||
if (isProactiveMode && isBrowserTool && !abortController.signal.aborted) {
|
|
||||||
console.log('Proactive mode: Capturing screenshots after browser tool call')
|
|
||||||
|
|
||||||
// Filter out old screenshots before adding new ones
|
|
||||||
filterOldProactiveScreenshots(builder)
|
|
||||||
|
|
||||||
// Capture new screenshots
|
|
||||||
const proactiveScreenshots = await captureProactiveScreenshots(abortController)
|
|
||||||
|
|
||||||
// Add proactive screenshots to builder
|
|
||||||
for (const screenshot of proactiveScreenshots) {
|
|
||||||
// Generate a unique tool call ID for the proactive screenshot
|
|
||||||
const proactiveToolCallId = ulid()
|
|
||||||
builder.addToolMessage(screenshot, proactiveToolCallId)
|
|
||||||
|
|
||||||
console.log('Proactive screenshot captured and added to context')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update message metadata
|
// update message metadata
|
||||||
}
|
}
|
||||||
return message
|
return message
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "Werkzeuge",
|
"tools": "Werkzeuge",
|
||||||
"webSearch": "Web Suche",
|
"webSearch": "Web Suche",
|
||||||
"reasoning": "Argumentation",
|
"reasoning": "Argumentation",
|
||||||
"proactive": "Proaktiv",
|
|
||||||
"selectAModel": "Wähle ein Modell",
|
"selectAModel": "Wähle ein Modell",
|
||||||
"noToolsAvailable": "Keine Werkzeuge verfügbar",
|
"noToolsAvailable": "Keine Werkzeuge verfügbar",
|
||||||
"noModelsFoundFor": "Keine Modelle gefunden zu \"{{searchValue}}\"",
|
"noModelsFoundFor": "Keine Modelle gefunden zu \"{{searchValue}}\"",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "Fähigkeiten",
|
"capabilities": "Fähigkeiten",
|
||||||
"tools": "Werkzeuge",
|
"tools": "Werkzeuge",
|
||||||
"vision": "Vision",
|
"vision": "Vision",
|
||||||
"proactive": "Proaktiv (Experimentell)",
|
|
||||||
"embeddings": "Einbettungen",
|
"embeddings": "Einbettungen",
|
||||||
"notAvailable": "Noch nicht verfügbar",
|
"notAvailable": "Noch nicht verfügbar",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -81,7 +81,6 @@
|
|||||||
"tools": "Tools",
|
"tools": "Tools",
|
||||||
"webSearch": "Web Search",
|
"webSearch": "Web Search",
|
||||||
"reasoning": "Reasoning",
|
"reasoning": "Reasoning",
|
||||||
"proactive": "Proactive",
|
|
||||||
"selectAModel": "Select a model",
|
"selectAModel": "Select a model",
|
||||||
"noToolsAvailable": "No tools available",
|
"noToolsAvailable": "No tools available",
|
||||||
"noModelsFoundFor": "No models found for \"{{searchValue}}\"",
|
"noModelsFoundFor": "No models found for \"{{searchValue}}\"",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "Capabilities",
|
"capabilities": "Capabilities",
|
||||||
"tools": "Tools",
|
"tools": "Tools",
|
||||||
"vision": "Vision",
|
"vision": "Vision",
|
||||||
"proactive": "Proactive (Experimental)",
|
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
"notAvailable": "Not available yet",
|
"notAvailable": "Not available yet",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "Alat",
|
"tools": "Alat",
|
||||||
"webSearch": "Pencarian Web",
|
"webSearch": "Pencarian Web",
|
||||||
"reasoning": "Penalaran",
|
"reasoning": "Penalaran",
|
||||||
"proactive": "Proaktif",
|
|
||||||
"selectAModel": "Pilih model",
|
"selectAModel": "Pilih model",
|
||||||
"noToolsAvailable": "Tidak ada alat yang tersedia",
|
"noToolsAvailable": "Tidak ada alat yang tersedia",
|
||||||
"noModelsFoundFor": "Tidak ada model yang ditemukan untuk \"{{searchValue}}\"",
|
"noModelsFoundFor": "Tidak ada model yang ditemukan untuk \"{{searchValue}}\"",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "Kemampuan",
|
"capabilities": "Kemampuan",
|
||||||
"tools": "Alat",
|
"tools": "Alat",
|
||||||
"vision": "Visi",
|
"vision": "Visi",
|
||||||
"proactive": "Proaktif (Eksperimental)",
|
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
"notAvailable": "Belum tersedia",
|
"notAvailable": "Belum tersedia",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "Narzędzia",
|
"tools": "Narzędzia",
|
||||||
"webSearch": "Szukanie w Sieci",
|
"webSearch": "Szukanie w Sieci",
|
||||||
"reasoning": "Rozumowanie",
|
"reasoning": "Rozumowanie",
|
||||||
"proactive": "Proaktywny",
|
|
||||||
"selectAModel": "Wybierz Model",
|
"selectAModel": "Wybierz Model",
|
||||||
"noToolsAvailable": "Brak narzędzi",
|
"noToolsAvailable": "Brak narzędzi",
|
||||||
"noModelsFoundFor": "Brak modeli dla \"{{searchValue}}\"",
|
"noModelsFoundFor": "Brak modeli dla \"{{searchValue}}\"",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "Możliwości",
|
"capabilities": "Możliwości",
|
||||||
"tools": "Narzędzia",
|
"tools": "Narzędzia",
|
||||||
"vision": "Wizja",
|
"vision": "Wizja",
|
||||||
"proactive": "Proaktywny (Eksperymentalny)",
|
|
||||||
"embeddings": "Osadzenia",
|
"embeddings": "Osadzenia",
|
||||||
"notAvailable": "Jeszcze niedostępne",
|
"notAvailable": "Jeszcze niedostępne",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Assistentes",
|
|
||||||
"editAssistant": "Editar Assistente",
|
|
||||||
"deleteAssistant": "Remover Assistente",
|
|
||||||
"deleteConfirmation": "Remover Assistente",
|
|
||||||
"deleteConfirmationDesc": "Tem certeza de que deseja remover este assistente? Esta ação não pode ser desfeita.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Remover",
|
|
||||||
"addAssistant": "Adicionar Assistente",
|
|
||||||
"emoji": "Emoji",
|
|
||||||
"name": "Nome",
|
|
||||||
"enterName": "Digite o nome",
|
|
||||||
"nameRequired": "O nome é obrigatório",
|
|
||||||
"description": "Descrição (opcional)",
|
|
||||||
"enterDescription": "Digite a descrição",
|
|
||||||
"instructions": "Instruções",
|
|
||||||
"enterInstructions": "Digite as instruções",
|
|
||||||
"predefinedParameters": "Parâmetros Predefinidos",
|
|
||||||
"parameters": "Parâmetros",
|
|
||||||
"key": "Chave",
|
|
||||||
"value": "Valor",
|
|
||||||
"stringValue": "Texto",
|
|
||||||
"numberValue": "Número",
|
|
||||||
"booleanValue": "Booleano",
|
|
||||||
"jsonValue": "JSON",
|
|
||||||
"trueValue": "Verdadeiro",
|
|
||||||
"falseValue": "Falso",
|
|
||||||
"jsonValuePlaceholder": "JSON",
|
|
||||||
"save": "Salvar",
|
|
||||||
"createNew": "Criar Novo Assistente",
|
|
||||||
"personality": "Personalidade",
|
|
||||||
"capabilities": "Capacidades",
|
|
||||||
"instructionsDateHint": "Dica: Use {{current_date}} para inserir a data de hoje.",
|
|
||||||
"maxToolSteps": "Máximo de passos das ferramentas (tools)"
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Olá, como você está?",
|
|
||||||
"description": "Como posso ajudá-lo hoje?",
|
|
||||||
"temporaryChat": "Chat Temporário",
|
|
||||||
"temporaryChatDescription": "Inicie uma conversa temporária que não será salva no seu histórico de chat.",
|
|
||||||
"status": {
|
|
||||||
"empty": "Nenhum Chat Encontrado"
|
|
||||||
},
|
|
||||||
"sendMessage": "Enviar Mensagem",
|
|
||||||
"newConversation": "Nova Conversa",
|
|
||||||
"clearHistory": "Limpar Histórico"
|
|
||||||
}
|
|
||||||
@ -1,375 +0,0 @@
|
|||||||
{
|
|
||||||
"assistants": "Assistentes",
|
|
||||||
"hardware": "Hardware",
|
|
||||||
"mcp-servers": "Servidores MCP",
|
|
||||||
"local_api_server": "Servidor de API Local",
|
|
||||||
"https_proxy": "Proxy HTTPS",
|
|
||||||
"extensions": "Extensões",
|
|
||||||
"attachments": "Anexos",
|
|
||||||
"general": "Geral",
|
|
||||||
"settings": "Configurações",
|
|
||||||
"modelProviders": "Provedores de Modelo",
|
|
||||||
"appearance": "Aparência",
|
|
||||||
"privacy": "Privacidade",
|
|
||||||
"keyboardShortcuts": "Atalhos",
|
|
||||||
"newChat": "Novo Chat",
|
|
||||||
"favorites": "Favoritos",
|
|
||||||
"recents": "Recentes",
|
|
||||||
"hub": "Hub",
|
|
||||||
"helpSupport": "Ajuda e Suporte",
|
|
||||||
"helpUsImproveJan": "Ajude-nos a Melhorar o Jan",
|
|
||||||
"unstarAll": "Remover todos os favoritos",
|
|
||||||
"unstar": "Remover Favorito",
|
|
||||||
"deleteAll": "Apagar Tudo",
|
|
||||||
"star": "Favoritar",
|
|
||||||
"rename": "Renomear",
|
|
||||||
"delete": "Apagar",
|
|
||||||
"copied": "Copiado!",
|
|
||||||
"dataFolder": "Pasta de Dados",
|
|
||||||
"others": "Outros",
|
|
||||||
"language": "Idioma",
|
|
||||||
"login": "Entrar",
|
|
||||||
"loginWith": "Entrar com {{provider}}",
|
|
||||||
"loginFailed": "Falha ao Entrar",
|
|
||||||
"logout": "Sair",
|
|
||||||
"loggingOut": "Saindo...",
|
|
||||||
"loggedOut": "Saída Realizada com Sucesso",
|
|
||||||
"logoutFailed": "Falha ao Sair",
|
|
||||||
"profile": "Perfil",
|
|
||||||
"reset": "Redefinir",
|
|
||||||
"search": "Buscar",
|
|
||||||
"name": "Nome",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"create": "Criar",
|
|
||||||
"save": "Salvar",
|
|
||||||
"edit": "Editar",
|
|
||||||
"copy": "Copiar",
|
|
||||||
"back": "Voltar",
|
|
||||||
"close": "Fechar",
|
|
||||||
"next": "Próximo",
|
|
||||||
"finish": "Finalizar",
|
|
||||||
"skip": "Pular",
|
|
||||||
"allow": "Permitir",
|
|
||||||
"deny": "Negar",
|
|
||||||
"start": "Iniciar",
|
|
||||||
"stop": "Parar",
|
|
||||||
"preview": "Visualizar",
|
|
||||||
"compactWidth": "Largura Compacta",
|
|
||||||
"fullWidth": "Largura Completa",
|
|
||||||
"dark": "Escuro",
|
|
||||||
"light": "Claro",
|
|
||||||
"system": "Sistema",
|
|
||||||
"auto": "Automático",
|
|
||||||
"english": "Inglês",
|
|
||||||
"medium": "Médio",
|
|
||||||
"newThread": "Nova Conversa",
|
|
||||||
"noResultsFound": "Nenhum resultado encontrado",
|
|
||||||
"noThreadsYet": "Nenhuma conversa ainda",
|
|
||||||
"noThreadsYetDesc": "Inicie uma nova conversa para ver seu histórico de conversas aqui.",
|
|
||||||
"downloads": "Downloads",
|
|
||||||
"downloading": "Baixando",
|
|
||||||
"cancelDownload": "Cancelar download",
|
|
||||||
"downloadCancelled": "Download Cancelado",
|
|
||||||
"downloadComplete": "Download Concluído",
|
|
||||||
"thinking": "Pensando...",
|
|
||||||
"thought": "Pensamento",
|
|
||||||
"callingTool": "Chamando ferramenta",
|
|
||||||
"completed": "Concluído",
|
|
||||||
"image": "Imagem",
|
|
||||||
"vision": "Visão",
|
|
||||||
"embeddings": "Embeddings",
|
|
||||||
"tools": "Ferramentas",
|
|
||||||
"webSearch": "Busca na Web",
|
|
||||||
"reasoning": "Raciocínio",
|
|
||||||
"selectAModel": "Selecione um modelo",
|
|
||||||
"noToolsAvailable": "Nenhuma ferramenta disponível",
|
|
||||||
"noModelsFoundFor": "Nenhum modelo encontrado para \"{{searchValue}}\"",
|
|
||||||
"failedToLoadModels": "Falha ao carregar modelos",
|
|
||||||
"noModels": "Nenhum modelo encontrado",
|
|
||||||
"customAvatar": "Avatar personalizado",
|
|
||||||
"editAssistant": "Editar Assistente",
|
|
||||||
"jan": "Jan",
|
|
||||||
"metadata": "Metadados",
|
|
||||||
"regenerate": "Regenerar",
|
|
||||||
"threadImage": "Imagem da conversa",
|
|
||||||
"editMessage": "Editar Mensagem",
|
|
||||||
"deleteMessage": "Apagar Mensagem",
|
|
||||||
"deleteThread": "Apagar Conversa",
|
|
||||||
"renameThread": "Renomear Conversa",
|
|
||||||
"threadTitle": "Título da Conversa",
|
|
||||||
"deleteAllThreads": "Apagar Todas as Conversas",
|
|
||||||
"allThreadsUnfavorited": "Todas as Conversas Removidas dos Favoritos",
|
|
||||||
"deleteAllThreadsConfirm": "Tem certeza de que deseja deletar todas as conversas? Esta ação não pode ser desfeita.",
|
|
||||||
"addProvider": "Adicionar Provedor",
|
|
||||||
"addOpenAIProvider": "Adicionar Provedor OpenAI",
|
|
||||||
"enterNameForProvider": "Digite um nome para seu provedor",
|
|
||||||
"providerAlreadyExists": "Provedor com nome \"{{name}}\" já existe. Por favor, escolha um nome diferente.",
|
|
||||||
"adjustFontSize": "Ajustar Tamanho da Fonte",
|
|
||||||
"changeLanguage": "Alterar Idioma",
|
|
||||||
"editTheme": "Editar Tema",
|
|
||||||
"editCodeBlockStyle": "Editar Estilo do Bloco de Código",
|
|
||||||
"editServerHost": "Editar Host do Servidor",
|
|
||||||
"pickColorWindowBackground": "Escolher Cor do Fundo da Janela",
|
|
||||||
"pickColorAppMainView": "Escolher Cor da Visualização Principal do App",
|
|
||||||
"pickColorAppPrimary": "Escolher Cor Primária do App",
|
|
||||||
"pickColorAppAccent": "Escolher Cor de Destaque do App",
|
|
||||||
"pickColorAppDestructive": "Escolher Cor Destrutiva do App",
|
|
||||||
"apiKeyRequired": "Chave API é obrigatória",
|
|
||||||
"enterTrustedHosts": "Digite os hosts confiáveis",
|
|
||||||
"placeholder": {
|
|
||||||
"chatInput": "Pergunte-me qualquer coisa..."
|
|
||||||
},
|
|
||||||
"confirm": "Confirmar",
|
|
||||||
"continue": "Continuar",
|
|
||||||
"loading": "Carregando...",
|
|
||||||
"error": "Erro",
|
|
||||||
"success": "Sucesso",
|
|
||||||
"warning": "Aviso",
|
|
||||||
"conversationNotAvailable": "Conversa não disponível",
|
|
||||||
"conversationNotAvailableDescription": "A conversa que você está tentando acessar não está disponível ou foi removida.",
|
|
||||||
"temporaryChat": "Chat Temporário",
|
|
||||||
"temporaryChatTooltip": "Chat temporário não aparecerá no seu histórico",
|
|
||||||
"noResultsFoundDesc": "Não conseguimos encontrar chats correspondentes à sua busca. Tente uma palavra-chave diferente.",
|
|
||||||
"searchModels": "Buscar modelos...",
|
|
||||||
"searchStyles": "Buscar estilos...",
|
|
||||||
"createAssistant": "Criar Assistente",
|
|
||||||
"enterApiKey": "Digite a Chave API",
|
|
||||||
"scrollToBottom": "Rolar até embaixo",
|
|
||||||
"generateAiResponse": "Gerar Resposta da IA",
|
|
||||||
"addModel": {
|
|
||||||
"title": "Adicionar Modelo",
|
|
||||||
"modelId": "ID do Modelo",
|
|
||||||
"enterModelId": "Digite o ID do Modelo",
|
|
||||||
"addModel": "Adicionar Modelo",
|
|
||||||
"description": "Adicionar um novo modelo ao provedor",
|
|
||||||
"exploreModels": "Ver lista de modelos do provedor"
|
|
||||||
},
|
|
||||||
"mcpServers": {
|
|
||||||
"editServer": "Editar Servidor",
|
|
||||||
"addServer": "Adicionar Servidor",
|
|
||||||
"serverName": "Nome do Servidor",
|
|
||||||
"enterServerName": "Digite o nome do servidor",
|
|
||||||
"command": "Comando",
|
|
||||||
"enterCommand": "Digite o comando",
|
|
||||||
"arguments": "Argumentos",
|
|
||||||
"argument": "Argumento {{index}}",
|
|
||||||
"envVars": "Variáveis de Ambiente",
|
|
||||||
"key": "Chave",
|
|
||||||
"value": "Valor",
|
|
||||||
"save": "Salvar"
|
|
||||||
},
|
|
||||||
"deleteServer": {
|
|
||||||
"title": "Remover Servidor",
|
|
||||||
"delete": "Remover"
|
|
||||||
},
|
|
||||||
"editJson": {
|
|
||||||
"errorParse": "Falha ao analisar JSON",
|
|
||||||
"errorPaste": "Falha ao colar JSON",
|
|
||||||
"errorFormat": "Formato JSON inválido",
|
|
||||||
"titleAll": "Editar Configuração de Todos os Servidores",
|
|
||||||
"placeholder": "Entre com a configuração do JSON...",
|
|
||||||
"save": "Salvar"
|
|
||||||
},
|
|
||||||
"editModel": {
|
|
||||||
"title": "Editar Modelo: {{modelId}}",
|
|
||||||
"description": "Configure as capacidades do modelo alterando as opções abaixo.",
|
|
||||||
"capabilities": "Capacidades",
|
|
||||||
"tools": "Ferramentas",
|
|
||||||
"vision": "Visão",
|
|
||||||
"embeddings": "Embeddings",
|
|
||||||
"notAvailable": "Ainda não disponível"
|
|
||||||
},
|
|
||||||
"outOfContextError": {
|
|
||||||
"truncateInput": "Truncar Entrada",
|
|
||||||
"title": "Erro de contexto esgotado",
|
|
||||||
"description": "Este chat está atingindo o limite de memória da IA, como um quadro branco ficando cheio. Podemos expandir a janela de memória (chamada tamanho do contexto) para que ela lembre mais, mas pode usar mais da memória do seu computador. Também podemos truncar a entrada, o que significa que ela esquecerá parte do histórico do chat para dar espaço a novas mensagens.",
|
|
||||||
"increaseContextSizeDescription": "Você quer aumentar o tamanho do contexto?",
|
|
||||||
"increaseContextSize": "Aumentar Tamanho do Contexto"
|
|
||||||
},
|
|
||||||
"toolApproval": {
|
|
||||||
"title": "Solicitação de Permissão para Ferramenta",
|
|
||||||
"description": "O assistente quer usar <strong>{{toolName}}</strong>",
|
|
||||||
"securityNotice": "Permita apenas ferramentas em que você confia. Ferramentas podem acessar seu sistema e dados.",
|
|
||||||
"deny": "Negar",
|
|
||||||
"allowOnce": "Permitir Uma Vez",
|
|
||||||
"alwaysAllow": "Sempre Permitir"
|
|
||||||
},
|
|
||||||
"deleteModel": {
|
|
||||||
"title": "Remover Modelo: {{modelId}}",
|
|
||||||
"description": "Tem certeza de que deseja remover este modelo? Esta ação não pode ser desfeita.",
|
|
||||||
"success": "Modelo {{modelId}} foi removido permanentemente.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Remover"
|
|
||||||
},
|
|
||||||
"deleteProvider": {
|
|
||||||
"title": "Remover Provedor",
|
|
||||||
"description": "Remover este provedor e todos os seus modelos. Esta ação não pode ser desfeita.",
|
|
||||||
"success": "Provedor {{provider}} foi removido permanentemente.",
|
|
||||||
"confirmTitle": "Remover Provedor: {{provider}}",
|
|
||||||
"confirmDescription": "Tem certeza de que deseja remover este provedor? Esta ação não pode ser desfeita.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Remover"
|
|
||||||
},
|
|
||||||
"modelSettings": {
|
|
||||||
"title": "Configurações do Modelo - {{modelId}}",
|
|
||||||
"description": "Configure as configurações do modelo para otimizar desempenho e comportamento."
|
|
||||||
},
|
|
||||||
"dialogs": {
|
|
||||||
"changeDataFolder": {
|
|
||||||
"title": "Alterar Localização da Pasta de Dados",
|
|
||||||
"description": "Tem certeza de que deseja alterar a localização da pasta de dados? Isso moverá todos os seus dados para a nova localização e reiniciará a aplicação.",
|
|
||||||
"currentLocation": "Localização Atual:",
|
|
||||||
"newLocation": "Nova Localização:",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"changeLocation": "Alterar Localização"
|
|
||||||
},
|
|
||||||
"deleteAllThreads": {
|
|
||||||
"title": "Remover Todas as Conversas",
|
|
||||||
"description": "Todas as conversas serão removidas. Esta ação não pode ser desfeita."
|
|
||||||
},
|
|
||||||
"deleteThread": {
|
|
||||||
"description": "Tem certeza de que deseja remover esta conversa? Esta ação não pode ser desfeita."
|
|
||||||
},
|
|
||||||
"editMessage": {
|
|
||||||
"title": "Editar Mensagem"
|
|
||||||
},
|
|
||||||
"messageMetadata": {
|
|
||||||
"title": "Metadados da Mensagem"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"projects": {
|
|
||||||
"title": "Projetos",
|
|
||||||
"addProject": "Adicionar Projeto",
|
|
||||||
"addToProject": "Adicionar ao projeto",
|
|
||||||
"removeFromProject": "Remover do projeto",
|
|
||||||
"createNewProject": "Criar Novo Projeto",
|
|
||||||
"editProject": "Editar Projeto",
|
|
||||||
"deleteProject": "Remover Projeto",
|
|
||||||
"projectName": "Nome do Projeto",
|
|
||||||
"enterProjectName": "Digite o nome do projeto...",
|
|
||||||
"noProjectsAvailable": "Nenhum projeto disponível",
|
|
||||||
"noProjectsYet": "Nenhum Projeto Ainda",
|
|
||||||
"noProjectsYetDesc": "Inicie um novo projeto clicando no botão Adicionar Projeto.",
|
|
||||||
"projectNotFound": "Projeto Não Encontrado",
|
|
||||||
"projectNotFoundDesc": "O projeto que você está procurando não existe ou foi removido.",
|
|
||||||
"deleteProjectDialog": {
|
|
||||||
"title": "Remover Projeto",
|
|
||||||
"permanentDelete": "ThiIsso removerá permanentemente todas as conversas.",
|
|
||||||
"permanentDeleteWarning": "Esta ação removerá permanentemente TODAS as conversas dentro do projeto!",
|
|
||||||
"deleteEmptyProject": "Esta ação removerá o projeto \"{{projectName}}\".",
|
|
||||||
"saveThreadsAdvice": "Para salvar conversas, mova-as para sua lista de conversas ou outro projeto antes de remover.",
|
|
||||||
"starredWarning": "Você ainda tem conversas favoritadas dentro do projeto.",
|
|
||||||
"deleteButton": "Remover",
|
|
||||||
"successWithName": "Projeto \"{{projectName}}\" removido com sucesso",
|
|
||||||
"successWithoutName": "Projeto removido com sucesso",
|
|
||||||
"error": "Falha ao remover projeto. Tente novamente.",
|
|
||||||
"ariaLabel": "Remover {{projectName}}"
|
|
||||||
},
|
|
||||||
"addProjectDialog": {
|
|
||||||
"createTitle": "Criar Novo Projeto",
|
|
||||||
"editTitle": "Editar Projeto",
|
|
||||||
"nameLabel": "Nome do Projeto",
|
|
||||||
"namePlaceholder": "Digite o nome do projeto...",
|
|
||||||
"createButton": "Criar",
|
|
||||||
"updateButton": "Atualizar",
|
|
||||||
"alreadyExists": "Projeto \"{{projectName}}\" já existe",
|
|
||||||
"createSuccess": "Projeto \"{{projectName}}\" criado com sucesso",
|
|
||||||
"renameSuccess": "Projeto renomeado de \"{{oldName}}\" para \"{{newName}}\""
|
|
||||||
},
|
|
||||||
"noConversationsIn": "Nenhuma Conversa em {{projectName}}",
|
|
||||||
"startNewConversation": "Inicie uma nova conversa com {{projectName}} abaixo",
|
|
||||||
"conversationsIn": "Conversas em {{projectName}}",
|
|
||||||
"conversationsDescription": "Clique em qualquer conversa para continuar o chat, ou inicie uma nova abaixo.",
|
|
||||||
"thread": "conversa",
|
|
||||||
"threads": "conversas",
|
|
||||||
"updated": "Atualizado:",
|
|
||||||
"collapseProject": "Recolher projeto",
|
|
||||||
"expandProject": "Expandir projeto",
|
|
||||||
"update": "Atualizar",
|
|
||||||
"searchProjects": "Buscar projetos...",
|
|
||||||
"noProjectsFound": "Nenhum projeto encontrado",
|
|
||||||
"tryDifferentSearch": "Tente um termo de busca diferente"
|
|
||||||
},
|
|
||||||
"toast": {
|
|
||||||
"allThreadsUnfavorited": {
|
|
||||||
"title": "Todas as Conversas Removidas dos Favoritos",
|
|
||||||
"description": "Todas as conversas foram removidas dos seus favoritos."
|
|
||||||
},
|
|
||||||
"deleteAllThreads": {
|
|
||||||
"title": "Remover Todas as Conversas",
|
|
||||||
"description": "Todas as conversas foram removidas permanentemente."
|
|
||||||
},
|
|
||||||
"renameThread": {
|
|
||||||
"title": "Renomear Conversa",
|
|
||||||
"description": "Título da conversa foi renomeado para '{{title}}'"
|
|
||||||
},
|
|
||||||
"deleteThread": {
|
|
||||||
"title": "Remover Conversa",
|
|
||||||
"description": "Esta conversa foi removida permanentemente."
|
|
||||||
},
|
|
||||||
"editMessage": {
|
|
||||||
"title": "Editar Mensagem",
|
|
||||||
"description": "Mensagem editada com sucesso. Aguarde a resposta do modelo."
|
|
||||||
},
|
|
||||||
"appUpdateDownloaded": {
|
|
||||||
"title": "Atualização do App Baixada",
|
|
||||||
"description": "A atualização do app foi baixada com sucesso."
|
|
||||||
},
|
|
||||||
"appUpdateDownloadFailed": {
|
|
||||||
"title": "Falha no Download da Atualização do App",
|
|
||||||
"description": "Falha ao baixar a atualização do app. Tente novamente."
|
|
||||||
},
|
|
||||||
"downloadComplete": {
|
|
||||||
"title": "Download Concluído",
|
|
||||||
"description": " {{item}} foi baixado"
|
|
||||||
},
|
|
||||||
"downloadCancelled": {
|
|
||||||
"title": "Download Cancelado",
|
|
||||||
"description": "O processo de download foi cancelado"
|
|
||||||
},
|
|
||||||
"downloadFailed": {
|
|
||||||
"title": "Falha no Download",
|
|
||||||
"description": "Falha no download de {{item}}"
|
|
||||||
},
|
|
||||||
"modelValidationStarted": {
|
|
||||||
"title": "Validando Modelo",
|
|
||||||
"description": "Modelo \"{{modelId}}\" baixado com sucesso. Verificando integridade..."
|
|
||||||
},
|
|
||||||
"modelValidationFailed": {
|
|
||||||
"title": "Falha na Validação do Modelo",
|
|
||||||
"description": "O modelo baixado \"{{modelId}}\" falhou na verificação de integridade e foi removido. O arquivo pode estar corrompido ou adulterado."
|
|
||||||
},
|
|
||||||
"downloadAndVerificationComplete": {
|
|
||||||
"title": "Download Concluído",
|
|
||||||
"description": "Modelo \"{{item}}\" baixado e verificado com sucesso"
|
|
||||||
},
|
|
||||||
"projectCreated": {
|
|
||||||
"title": "Projeto Criado",
|
|
||||||
"description": "Projeto \"{{projectName}}\" criado com sucesso"
|
|
||||||
},
|
|
||||||
"projectRenamed": {
|
|
||||||
"title": "Projeto Renomeado",
|
|
||||||
"description": "Projeto renomeado de \"{{oldName}}\" para \"{{newName}}\""
|
|
||||||
},
|
|
||||||
"projectDeleted": {
|
|
||||||
"title": "Projeto Removido",
|
|
||||||
"description": "Projeto \"{{projectName}}\" removido com sucesso"
|
|
||||||
},
|
|
||||||
"projectAlreadyExists": {
|
|
||||||
"title": "Projeto Já Existe",
|
|
||||||
"description": "Projeto \"{{projectName}}\" já existe"
|
|
||||||
},
|
|
||||||
"projectDeleteFailed": {
|
|
||||||
"title": "Falha ao Remover",
|
|
||||||
"description": "Falha ao remover projeto. Tente novamente."
|
|
||||||
},
|
|
||||||
"threadAssignedToProject": {
|
|
||||||
"title": "Conversa Atribuída",
|
|
||||||
"description": "Conversa atribuída a \"{{projectName}}\" com sucesso"
|
|
||||||
},
|
|
||||||
"threadRemovedFromProject": {
|
|
||||||
"title": "Conversa Removida",
|
|
||||||
"description": "Conversa removida de \"{{projectName}}\" com sucesso"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"sortNewest": "Mais Recentes",
|
|
||||||
"sortMostDownloaded": "Mais Baixados",
|
|
||||||
"use": "Usar",
|
|
||||||
"download": "Baixar",
|
|
||||||
"downloaded": "Baixado",
|
|
||||||
"loadingModels": "Carregando modelos...",
|
|
||||||
"noModels": "Nenhum modelo encontrado",
|
|
||||||
"by": "Por",
|
|
||||||
"downloads": "Downloads",
|
|
||||||
"variants": "Variantes",
|
|
||||||
"showVariants": "Mostrar variantes",
|
|
||||||
"useModel": "Usar este modelo",
|
|
||||||
"downloadModel": "Baixar modelo",
|
|
||||||
"tools": "Ferramentas",
|
|
||||||
"searchPlaceholder": "Buscar modelos no Hugging Face...",
|
|
||||||
"joyride": {
|
|
||||||
"recommendedModelTitle": "Modelo Recomendado",
|
|
||||||
"recommendedModelContent": "Navegue e baixe modelos de IA poderosos de vários provedores, tudo em um só lugar. Sugerimos começar com o Jan-Nano - um modelo otimizado para chamadas de função, integração de ferramentas e capacidades de pesquisa. É ideal para construir agentes de IA interativos.",
|
|
||||||
"downloadInProgressTitle": "Download em Progresso",
|
|
||||||
"downloadInProgressContent": "Seu modelo está sendo baixado. Acompanhe o progresso aqui - uma vez finalizado, estará pronto para usar.",
|
|
||||||
"downloadModelTitle": "Baixar Modelo",
|
|
||||||
"downloadModelContent": "Clique no botão Baixar para começar a baixar o modelo.",
|
|
||||||
"back": "Voltar",
|
|
||||||
"close": "Fechar",
|
|
||||||
"lastWithDownload": "Baixar",
|
|
||||||
"last": "Finalizar",
|
|
||||||
"next": "Próximo",
|
|
||||||
"skip": "Pular"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"noLogs": "Nenhum log disponível"
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"editServer": "Editar Servidor MCP",
|
|
||||||
"addServer": "Adicionar Servidor MCP",
|
|
||||||
"serverName": "Nome do Servidor",
|
|
||||||
"enterServerName": "Digite o nome do servidor",
|
|
||||||
"command": "Comando",
|
|
||||||
"enterCommand": "Digite o comando (uvx ou npx)",
|
|
||||||
"arguments": "Argumentos",
|
|
||||||
"argument": "Argumento {{index}}",
|
|
||||||
"envVars": "Variáveis de Ambiente",
|
|
||||||
"key": "Chave",
|
|
||||||
"value": "Valor",
|
|
||||||
"save": "Salvar",
|
|
||||||
"status": "Status",
|
|
||||||
"connected": "Conectado",
|
|
||||||
"disconnected": "Desconectado",
|
|
||||||
"deleteServer": {
|
|
||||||
"title": "Remover Servidor MCP",
|
|
||||||
"description": "Tem certeza de que deseja remover o servidor MCP {{serverName}}? Esta ação não pode ser desfeita.",
|
|
||||||
"delete": "Remover",
|
|
||||||
"success": "Servidor MCP {{serverName}} removido com sucesso"
|
|
||||||
},
|
|
||||||
"editJson": {
|
|
||||||
"title": "Editar JSON para Servidor MCP: {{serverName}}",
|
|
||||||
"titleAll": "Editar JSON de Todos os Servidores MCP",
|
|
||||||
"placeholder": "Digite a configuração JSON",
|
|
||||||
"errorParse": "Falha ao analisar dados iniciais",
|
|
||||||
"errorPaste": "Formato JSON inválido no conteúdo colado",
|
|
||||||
"errorFormat": "Formato JSON inválido",
|
|
||||||
"errorServerName": " Nome do servidor é obrigatório e não pode estar vazio",
|
|
||||||
"errorMissingServerNameKey": "JSON deve estar estruturado como {\"serverName\": {config}} - chave com nome do servidor ausente",
|
|
||||||
"errorInvalidType": "Tipo inválido '{{type}}' para servidor '{{serverName}}'. Tipo deve ser 'stdio', 'http', ou 'sse'",
|
|
||||||
"save": "Salvar"
|
|
||||||
},
|
|
||||||
"checkParams": "Por favor, verifique os parâmetros de acordo com o tutorial.",
|
|
||||||
"title": "Servidores MCP",
|
|
||||||
"experimental": "Experimental",
|
|
||||||
"editAllJson": "Editar JSON de Todos os Servidores",
|
|
||||||
"findMore": "Encontre mais servidores MCP em",
|
|
||||||
"allowPermissions": "Permitir Todas as Permissões de Ferramentas MCP",
|
|
||||||
"allowPermissionsDesc": "Quando habilitado, todas as chamadas de ferramentas MCP serão automaticamente aprovadas sem mostrar diálogos de permissão. Esta configuração se aplica globalmente a todas as conversas, incluindo novos chats.",
|
|
||||||
"noServers": "Nenhum servidor MCP encontrado",
|
|
||||||
"args": "Args",
|
|
||||||
"env": "Env",
|
|
||||||
"serverStatusActive": "Servidor {{serverKey}} ativado com sucesso",
|
|
||||||
"serverStatusInactive": "Servidor {{serverKey}} desativado com sucesso"
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Erro de contexto esgotado",
|
|
||||||
"description": "Este chat está atingindo o limite de memória da IA, como um quadro branco ficando cheio. Podemos expandir a janela de memória (chamada tamanho do contexto) para que ela lembre mais, mas pode usar mais da memória do seu computador. Também podemos truncar a entrada, o que significa que ela esquecerá parte do histórico do chat para dar espaço a novas mensagens.",
|
|
||||||
"increaseContextSizeDescription": "Você quer aumentar o tamanho do contexto?",
|
|
||||||
"truncateInput": "Truncar Entrada",
|
|
||||||
"increaseContextSize": "Aumentar Tamanho do Contexto"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"addProvider": "Adicionar Provedor",
|
|
||||||
"addOpenAIProvider": "Adicionar Provedor OpenAI",
|
|
||||||
"enterNameForProvider": "Digite o nome para o provedor"
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"joyride": {
|
|
||||||
"chooseProviderTitle": "Escolha um Provedor",
|
|
||||||
"chooseProviderContent": "Escolha o provedor que você quer usar, certifique-se de ter acesso a uma chave API para ele.",
|
|
||||||
"getApiKeyTitle": "Obtenha sua Chave de API",
|
|
||||||
"getApiKeyContent": "Entre no painel do provedor para encontrar ou gerar sua chave de API.",
|
|
||||||
"insertApiKeyTitle": "Insira sua Chave de API",
|
|
||||||
"insertApiKeyContent": "Cole sua chave de API aqui para conectar e ativar o provedor.",
|
|
||||||
"back": "Voltar",
|
|
||||||
"close": "Fechar",
|
|
||||||
"last": "Finalizar",
|
|
||||||
"next": "Próximo",
|
|
||||||
"skip": "Pular"
|
|
||||||
},
|
|
||||||
"refreshModelsError": "Provedor deve ter URL base e chave API configuradas para buscar modelos.",
|
|
||||||
"refreshModelsSuccess": "Adicionado {{count}} novo(s) modelo(s) de {{provider}}.",
|
|
||||||
"noNewModels": "Nenhum modelo novo encontrado. Todos os modelos disponíveis já foram adicionados.",
|
|
||||||
"refreshModelsFailed": "Falha ao buscar modelos de {{provider}}. Verifique sua chave de API e URL base.",
|
|
||||||
"models": "Modelos",
|
|
||||||
"refreshing": "Atualizando...",
|
|
||||||
"refresh": "Atualizar",
|
|
||||||
"import": "Importar",
|
|
||||||
"importModelSuccess": "Modelo {{provider}} foi importado com sucesso.",
|
|
||||||
"importModelError": "Falha ao importar modelo:",
|
|
||||||
"stop": "Parar",
|
|
||||||
"start": "Iniciar",
|
|
||||||
"noModelFound": "Nenhum modelo encontrado",
|
|
||||||
"noModelFoundDesc": "Modelos disponíveis serão listados aqui. Se você ainda não tem modelos, visite o Hub para baixar.",
|
|
||||||
"configuration": "Configuração",
|
|
||||||
"apiEndpoint": "Endpoint da API",
|
|
||||||
"testConnection": "Testar Conexão",
|
|
||||||
"addModel": {
|
|
||||||
"title": "Adicionar Novo Modelo",
|
|
||||||
"description": "Adicionar um novo modelo ao provedor {{provider}}.",
|
|
||||||
"modelId": "ID do Modelo",
|
|
||||||
"enterModelId": "Digite o ID do modelo",
|
|
||||||
"exploreModels": "Ver lista de modelos de {{provider}}",
|
|
||||||
"addModel": "Adicionar Modelo",
|
|
||||||
"modelExists": "Modelo já existe",
|
|
||||||
"modelExistsDesc": "Por favor, escolha um ID de modelo diferente."
|
|
||||||
},
|
|
||||||
"deleteModel": {
|
|
||||||
"title": "Remover Modelo: {{modelId}}",
|
|
||||||
"description": "Tem certeza de que deseja remover este modelo? Esta ação não pode ser desfeita.",
|
|
||||||
"success": "Modelo {{modelId}} foi removido permanentemente.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Remover"
|
|
||||||
},
|
|
||||||
"deleteProvider": {
|
|
||||||
"title": "Remover Provedor",
|
|
||||||
"description": "Remover este provedor e todos os seus modelos. Esta ação não pode ser desfeita.",
|
|
||||||
"success": "Provedor {{provider}} foi removido permanentemente.",
|
|
||||||
"confirmTitle": "Remover Provedor: {{provider}}",
|
|
||||||
"confirmDescription": "Tem certeza de que deseja remover este provedor? Esta ação não pode ser desfeita.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Remover"
|
|
||||||
},
|
|
||||||
"editModel": {
|
|
||||||
"title": "Editar Modelo: {{modelId}}",
|
|
||||||
"description": "Configure as capacidades do modelo alterando as opções abaixo.",
|
|
||||||
"capabilities": "Capacidades",
|
|
||||||
"tools": "Ferramentas",
|
|
||||||
"vision": "Visão",
|
|
||||||
"embeddings": "Embeddings",
|
|
||||||
"notAvailable": "Ainda não disponível",
|
|
||||||
"warning": {
|
|
||||||
"title": "Prossiga com Cautela",
|
|
||||||
"description": "Modificar capacidades do modelo pode afetar desempenho e funcionalidade. Configurações incorretas podem causar comportamento inesperado ou erros."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"addProvider": "Adicionar Provedor",
|
|
||||||
"addOpenAIProvider": "Adicionar Provedor OpenAI",
|
|
||||||
"enterNameForProvider": "Digite o nome para o provedor"
|
|
||||||
}
|
|
||||||
@ -1,304 +0,0 @@
|
|||||||
{
|
|
||||||
"autoDownload": "Download automático de novas atualizações",
|
|
||||||
"checkForUpdates": "Verificar Atualizações",
|
|
||||||
"checkForUpdatesDesc": "Verificar se uma versão mais nova do Jan está disponível.",
|
|
||||||
"checkingForUpdates": "Verificando atualizações...",
|
|
||||||
"noUpdateAvailable": "Você está executando a versão mais recente",
|
|
||||||
"devVersion": "Versão de desenvolvimento detectada",
|
|
||||||
"updateError": "Falha ao verificar atualizações",
|
|
||||||
"checkForBackendUpdates": "Verificar Atualizações do Llamacpp",
|
|
||||||
"checkForBackendUpdatesDesc": "Verificar se uma versão mais nova do backend Llamacpp está disponível.",
|
|
||||||
"checkingForBackendUpdates": "Verificando atualizações do Llamacpp...",
|
|
||||||
"noBackendUpdateAvailable": "Você está executando a versão mais recente do Llamacpp",
|
|
||||||
"backendUpdateError": "Falha ao verificar atualizações do Llamacpp",
|
|
||||||
"changeLocation": "Alterar Localização",
|
|
||||||
"copied": "Copiado",
|
|
||||||
"copyPath": "Copiar Caminho",
|
|
||||||
"openLogs": "Abrir Logs",
|
|
||||||
"revealLogs": "Mostrar Logs",
|
|
||||||
"showInFinder": "Mostrar no Finder",
|
|
||||||
"showInFileExplorer": "Mostrar no Explorer de Arquivos",
|
|
||||||
"openContainingFolder": "Abrir Pasta",
|
|
||||||
"failedToRelocateDataFolder": "Falha ao realocar pasta de dados",
|
|
||||||
"failedToRelocateDataFolderDesc": "Falha ao realocar pasta de dados. Tente novamente.",
|
|
||||||
"factoryResetTitle": "Redefinir para Configurações originais",
|
|
||||||
"factoryResetDesc": "Isso redefinirá todas as configurações do app para os padrões originais. Isso não pode ser desfeito. Recomendamos isso apenas se o app estiver corrompido.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"reset": "Redefinir",
|
|
||||||
"resources": "Recursos",
|
|
||||||
"documentation": "Documentação",
|
|
||||||
"documentationDesc": "Aprenda como usar o Jan e explore seus recursos.",
|
|
||||||
"viewDocs": "Ver Documentação",
|
|
||||||
"releaseNotes": "Notas de Versão",
|
|
||||||
"releaseNotesDesc": "Veja o que há de novo na versão mais recente do Jan.",
|
|
||||||
"viewReleases": "Ver Versões",
|
|
||||||
"community": "Comunidade",
|
|
||||||
"github": "GitHub",
|
|
||||||
"githubDesc": "Contribua para o desenvolvimento do Jan.",
|
|
||||||
"discord": "Discord",
|
|
||||||
"discordDesc": "Junte-se à nossa comunidade para suporte e discussões.",
|
|
||||||
"support": "Suporte",
|
|
||||||
"reportAnIssue": "Reportar um Problema",
|
|
||||||
"reportAnIssueDesc": "Encontrou um bug? Ajude-nos relatando um problema no GitHub.",
|
|
||||||
"reportIssue": "Reportar Problema",
|
|
||||||
"credits": "Créditos",
|
|
||||||
"creditsDesc1": "👋 Jan é construído com ❤️ pela equipe Menlo Research.",
|
|
||||||
"creditsDesc2": "Agradecimentos especiais às nossas dependências de código aberto—especialmente llama.cpp e Tauri—e à nossa incrível comunidade de IA.",
|
|
||||||
"appVersion": "Versão do App",
|
|
||||||
"dataFolder": {
|
|
||||||
"appData": "Dados do App",
|
|
||||||
"appDataDesc": "Localização padrão para mensagens e outros dados do usuário.",
|
|
||||||
"appLogs": "Logs do App",
|
|
||||||
"appLogsDesc": "Ver logs detalhados do App."
|
|
||||||
},
|
|
||||||
"others": {
|
|
||||||
"spellCheck": "Verificação Ortográfica",
|
|
||||||
"spellCheckDesc": "Habilitar verificação ortográfica para suas conversas.",
|
|
||||||
"resetFactory": "Redefinir para Configurações Originais",
|
|
||||||
"resetFactoryDesc": "Restaurar aplicação ao seu estado inicial, apagando todos os modelos e histórico de chat. Esta ação é irreversível e recomendada apenas se a aplicação estiver corrompida."
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
|
||||||
"application": "Aplicação",
|
|
||||||
"newChat": "Novo Chat",
|
|
||||||
"newChatDesc": "Criar um novo chat.",
|
|
||||||
"toggleSidebar": "Mostrar/Ocultar Barra Lateral",
|
|
||||||
"toggleSidebarDesc": "Mostrar/ocultar a barra lateral.",
|
|
||||||
"zoomIn": "Aumentar Zoom",
|
|
||||||
"zoomInDesc": "Aumentar o nível de zoom.",
|
|
||||||
"zoomOut": "Diminuir Zoom",
|
|
||||||
"zoomOutDesc": "Diminuir o nível de zoom.",
|
|
||||||
"chat": "Chat",
|
|
||||||
"sendMessage": "Enviar Mensagem",
|
|
||||||
"sendMessageDesc": "Enviar a mensagem atual.",
|
|
||||||
"enter": "Enter",
|
|
||||||
"newLine": "Nova Linha",
|
|
||||||
"newLineDesc": "Inserir uma nova linha.",
|
|
||||||
"shiftEnter": "Shift + Enter",
|
|
||||||
"navigation": "Navegação",
|
|
||||||
"goToSettings": "Ir para Configurações",
|
|
||||||
"goToSettingsDesc": "Abrir configurações."
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"title": "Aparência",
|
|
||||||
"theme": "Tema",
|
|
||||||
"themeDesc": "Corresponder ao tema do Sistema Operacional.",
|
|
||||||
"fontSize": "Tamanho da Fonte",
|
|
||||||
"fontSizeDesc": "Ajustar o tamanho da fonte do app.",
|
|
||||||
"windowBackground": "Fundo da Janela",
|
|
||||||
"windowBackgroundDesc": "Definir a cor de fundo da janela do app.",
|
|
||||||
"appMainView": "Visualização Principal do App",
|
|
||||||
"appMainViewDesc": "Definir a cor de fundo da área de conteúdo principal.",
|
|
||||||
"primary": "Primário",
|
|
||||||
"primaryDesc": "Definir a cor primária para componentes da UI.",
|
|
||||||
"accent": "Destaque",
|
|
||||||
"accentDesc": "Definir a cor de destaque para realces da UI.",
|
|
||||||
"destructive": "Destrutivo",
|
|
||||||
"destructiveDesc": "Definir a cor para ações destrutivas.",
|
|
||||||
"resetToDefault": "Redefinir para Padrão",
|
|
||||||
"resetToDefaultDesc": "Redefinir todas as configurações de aparência para padrão.",
|
|
||||||
"resetAppearanceSuccess": "Aparência redefinida com sucesso",
|
|
||||||
"resetAppearanceSuccessDesc": "Todas as configurações de aparência foram restauradas para padrão.",
|
|
||||||
"chatWidth": "Largura do Chat",
|
|
||||||
"chatWidthDesc": "Personalizar a largura da visualização do chat.",
|
|
||||||
"tokenCounterCompact": "Contador de Tokens Compacto",
|
|
||||||
"tokenCounterCompactDesc": "Mostrar contador de tokens dentro da entrada do chat. Quando desabilitado, contador de tokens aparece abaixo da entrada.",
|
|
||||||
"codeBlockTitle": "Bloco de Código",
|
|
||||||
"codeBlockDesc": "Escolher um estilo de realce de sintaxe.",
|
|
||||||
"showLineNumbers": "Mostrar Números de Linha",
|
|
||||||
"showLineNumbersDesc": "Exibir números de linha em blocos de código.",
|
|
||||||
"resetCodeBlockStyle": "Redefinir Estilo do Bloco de Código",
|
|
||||||
"resetCodeBlockStyleDesc": "Redefinir estilo do bloco de código para padrão.",
|
|
||||||
"resetCodeBlockSuccess": "Estilo do bloco de código redefinido com sucesso",
|
|
||||||
"resetCodeBlockSuccessDesc": "Estilo do bloco de código foi restaurado para padrão."
|
|
||||||
},
|
|
||||||
"hardware": {
|
|
||||||
"os": "Sistema Operacional",
|
|
||||||
"name": "Nome",
|
|
||||||
"version": "Versão",
|
|
||||||
"cpu": "CPU",
|
|
||||||
"model": "Modelo",
|
|
||||||
"architecture": "Arquitetura",
|
|
||||||
"cores": "Cores",
|
|
||||||
"instructions": "Instruções",
|
|
||||||
"usage": "Uso",
|
|
||||||
"memory": "Memória",
|
|
||||||
"totalRam": "RAM Total",
|
|
||||||
"availableRam": "RAM Disponível",
|
|
||||||
"vulkan": "Vulkan",
|
|
||||||
"enableVulkan": "Habilitar Vulkan",
|
|
||||||
"enableVulkanDesc": "Usar API Vulkan para aceleração GPU. Não habilite Vulkan se você tem uma GPU NVIDIA pois pode causar problemas de compatibilidade.",
|
|
||||||
"gpus": "GPUs",
|
|
||||||
"noGpus": "Nenhuma GPU detectada",
|
|
||||||
"vram": "VRAM",
|
|
||||||
"freeOf": "livre de",
|
|
||||||
"driverVersion": "Versão do Driver",
|
|
||||||
"computeCapability": "Capacidade de Computação",
|
|
||||||
"systemMonitor": "Monitor do Sistema"
|
|
||||||
},
|
|
||||||
"httpsProxy": {
|
|
||||||
"proxy": "Proxy",
|
|
||||||
"proxyUrl": "URL do Proxy",
|
|
||||||
"proxyUrlDesc": "A URL e porta do seu servidor proxy.",
|
|
||||||
"proxyUrlPlaceholder": "http://proxy.example.com:8080",
|
|
||||||
"authentication": "Autenticação",
|
|
||||||
"authenticationDesc": "Credenciais para o servidor proxy, se necessário.",
|
|
||||||
"username": "Nome de Usuário",
|
|
||||||
"password": "Senha",
|
|
||||||
"noProxy": "Sem Proxy",
|
|
||||||
"noProxyDesc": "Uma lista separada por vírgulas de hosts para contornar o proxy.",
|
|
||||||
"noProxyPlaceholder": "localhost,127.0.0.1,.local",
|
|
||||||
"sslVerification": "Verificação SSL",
|
|
||||||
"ignoreSsl": "Ignorar Certificados SSL",
|
|
||||||
"ignoreSslDesc": "Permitir certificados auto-assinados ou não verificados. Isso pode ser necessário para alguns proxies, mas reduz a segurança. Habilite apenas se confiar no seu proxy.",
|
|
||||||
"proxySsl": "SSL do Proxy",
|
|
||||||
"proxySslDesc": "Validar o certificado SSL ao conectar ao proxy.",
|
|
||||||
"proxyHostSsl": "SSL do Host do Proxy",
|
|
||||||
"proxyHostSslDesc": "Validar o certificado SSL do host do proxy.",
|
|
||||||
"peerSsl": "SSL do Peer",
|
|
||||||
"peerSslDesc": "Validar os certificados SSL das conexões peer.",
|
|
||||||
"hostSsl": "SSL do Host",
|
|
||||||
"hostSslDesc": "Validar os certificados SSL dos hosts de destino."
|
|
||||||
},
|
|
||||||
"localApiServer": {
|
|
||||||
"title": "Servidor de API Local",
|
|
||||||
"description": "Executar um servidor compatível com OpenAI localmente.",
|
|
||||||
"startServer": "Iniciar Servidor",
|
|
||||||
"loadingModel": "Carregando Modelo",
|
|
||||||
"startingServer": "Iniciando Servidor",
|
|
||||||
"stopServer": "Parar Servidor",
|
|
||||||
"serverLogs": "Logs do Servidor",
|
|
||||||
"serverLogsDesc": "Ver logs detalhados do servidor API local.",
|
|
||||||
"openLogs": "Abrir Logs",
|
|
||||||
"swaggerDocs": "Documentação da API",
|
|
||||||
"swaggerDocsDesc": "Ver documentação interativa da API (Swagger UI).",
|
|
||||||
"openDocs": "Abrir Documentos",
|
|
||||||
"startupConfiguration": "Configuração de Inicialização",
|
|
||||||
"runOnStartup": "Inicialização automática",
|
|
||||||
"runOnStartupDesc": "Iniciar automaticamente o Servidor API Local quando a aplicação for lançada. Usa o último modelo usado, ou escolhe o primeiro modelo disponível se indisponível.",
|
|
||||||
"serverConfiguration": "Configuração do Servidor",
|
|
||||||
"serverHost": "Host do Servidor",
|
|
||||||
"serverHostDesc": "Endereço de rede para o servidor.",
|
|
||||||
"serverPort": "Porta do Servidor",
|
|
||||||
"serverPortDesc": "Número da porta para o servidor API.",
|
|
||||||
"apiPrefix": "Prefixo da API",
|
|
||||||
"apiPrefixDesc": "Prefixo do caminho para endpoints da API.",
|
|
||||||
"apiKey": "Chave de API",
|
|
||||||
"apiKeyDesc": "Autenticar requisições com uma chave de API.",
|
|
||||||
"trustedHosts": "Hosts Confiáveis",
|
|
||||||
"trustedHostsDesc": "Hosts permitidos para acessar o servidor, separados por vírgulas.",
|
|
||||||
"advancedSettings": "Configurações Avançadas",
|
|
||||||
"cors": "Compartilhamento de Recursos de Origem Cruzada (CORS)",
|
|
||||||
"corsDesc": "Permitir requisições de origem cruzada para o servidor de API.",
|
|
||||||
"verboseLogs": "Logs Detalhados do Servidor",
|
|
||||||
"verboseLogsDesc": "Habilitar logs detalhados do servidor para depuração.",
|
|
||||||
"proxyTimeout": "Timeout de Requisição",
|
|
||||||
"proxyTimeoutDesc": "Tempo para aguardar uma resposta do modelo local, segundos."
|
|
||||||
},
|
|
||||||
"privacy": {
|
|
||||||
"analytics": "Analytics",
|
|
||||||
"helpUsImprove": "Ajude-nos a melhorar",
|
|
||||||
"helpUsImproveDesc": "Para nos ajudar a melhorar o Jan, você pode compartilhar dados anônimos como uso de recursos e contagem de usuários. Nunca coletamos seus chats ou informações pessoais.",
|
|
||||||
"privacyPolicy": "Você tem controle total sobre seus dados. Saiba mais em nossa Política de Privacidade.",
|
|
||||||
"analyticsDesc": "Para melhorar o Jan, precisamos entender como é usado—mas apenas com sua ajuda. Você pode alterar esta configuração a qualquer momento.",
|
|
||||||
"privacyPromises": "Sua escolha aqui não mudará nossas promessas básicas de privacidade:",
|
|
||||||
"promise1": "Suas conversas permanecem privadas e no seu dispositivo",
|
|
||||||
"promise2": "Nunca coletamos suas informações pessoais ou conteúdo de chat",
|
|
||||||
"promise3": "Todo compartilhamento de dados é anônimo e agregado",
|
|
||||||
"promise4": "Você pode optar por sair a qualquer momento sem perder funcionalidade",
|
|
||||||
"promise5": "Somos transparentes sobre o que coletamos e por quê"
|
|
||||||
},
|
|
||||||
"general": {
|
|
||||||
"showInFinder": "Mostrar no Finder",
|
|
||||||
"showInFileExplorer": "Mostrar no Explorador de Arquivos",
|
|
||||||
"openContainingFolder": "Abrir Pasta Contendo",
|
|
||||||
"failedToRelocateDataFolder": "Falha ao realocar pasta de dados",
|
|
||||||
"couldNotRelocateToRoot": "Não é possível realocar pasta de dados para diretório raiz. Por favor, escolha outra localização.",
|
|
||||||
"couldNotResetRootDirectory": "Não é possível redefinir pasta de dados quando está definida para um diretório raiz. Por favor, delete a pasta de dados manualmente.",
|
|
||||||
"failedToRelocateDataFolderDesc": "Falha ao realocar pasta de dados. Tente novamente.",
|
|
||||||
"devVersion": "Versão de desenvolvimento detectada",
|
|
||||||
"noUpdateAvailable": "Você está executando a versão mais recente",
|
|
||||||
"updateError": "Falha ao verificar atualizações",
|
|
||||||
"appVersion": "Versão do App",
|
|
||||||
"checkForUpdates": "Verificar Atualizações",
|
|
||||||
"checkForUpdatesDesc": "Verificar se uma versão mais nova do Jan está disponível.",
|
|
||||||
"checkingForUpdates": "Verificando atualizações...",
|
|
||||||
"copied": "Copiado",
|
|
||||||
"copyPath": "Copiar Caminho",
|
|
||||||
"changeLocation": "Alterar Localização",
|
|
||||||
"openLogs": "Abrir Logs",
|
|
||||||
"revealLogs": "Mostrar Logs",
|
|
||||||
"factoryResetTitle": "Redefinir para Configurações de Fábrica",
|
|
||||||
"factoryResetDesc": "Isso redefinirá todas as configurações do app para os padrões. Isso não pode ser desfeito. Recomendamos isso apenas se o app estiver corrompido.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"reset": "Redefinir",
|
|
||||||
"huggingfaceToken": "Token HuggingFace",
|
|
||||||
"huggingfaceTokenDesc": "Seu token da API HuggingFace para acessar modelos.",
|
|
||||||
"resources": "Recursos",
|
|
||||||
"documentation": "Documentação",
|
|
||||||
"documentationDesc": "Aprenda como usar o Jan e explore seus recursos.",
|
|
||||||
"viewDocs": "Ver Documentos",
|
|
||||||
"releaseNotes": "Notas de Versão",
|
|
||||||
"releaseNotesDesc": "Veja o que há de novo na versão mais recente do Jan.",
|
|
||||||
"viewReleases": "Ver Versões",
|
|
||||||
"community": "Comunidade",
|
|
||||||
"github": "GitHub",
|
|
||||||
"githubDesc": "Contribua para o desenvolvimento do Jan.",
|
|
||||||
"discord": "Discord",
|
|
||||||
"discordDesc": "Junte-se à nossa comunidade para suporte e discussões.",
|
|
||||||
"support": "Suporte",
|
|
||||||
"reportAnIssue": "Reportar um Problema",
|
|
||||||
"reportAnIssueDesc": "Encontrou um bug? Ajude-nos relatando um problema no GitHub.",
|
|
||||||
"reportIssue": "Reportar Problema",
|
|
||||||
"credits": "Créditos",
|
|
||||||
"creditsDesc1": "👋 Jan é construído com ❤️ pela equipe Menlo Research.",
|
|
||||||
"creditsDesc2": "Agradecimentos especiais às nossas dependências de código aberto—especialmente llama.cpp e Tauri—e à nossa incrível comunidade de IA."
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
"title": "Extensões"
|
|
||||||
},
|
|
||||||
"attachments": {
|
|
||||||
"subtitle": "Configure anexos de documentos, limites de tamanho e comportamento de recuperação.",
|
|
||||||
"featureTitle": "Recurso",
|
|
||||||
"enable": "Habilitar Anexos",
|
|
||||||
"enableDesc": "Permitir upload e indexação de documentos para recuperação.",
|
|
||||||
"limitsTitle": "Limites",
|
|
||||||
"maxFile": "Tamanho Máximo do Arquivo (MB)",
|
|
||||||
"maxFileDesc": "Tamanho máximo por arquivo. Aplicado no upload e processamento.",
|
|
||||||
"retrievalTitle": "Recuperação",
|
|
||||||
"topK": "Top-K",
|
|
||||||
"topKDesc": "Máximo de citações para retornar.",
|
|
||||||
"threshold": "Limite de Afinidade",
|
|
||||||
"thresholdDesc": "Pontuação mínima de similaridade (0-1). Usado apenas para busca linear cosseno, não ANN.",
|
|
||||||
"searchMode": "Modo de Busca Vetorial",
|
|
||||||
"searchModeDesc": "Escolha entre sqlite-vec ANN, cosseno linear, ou auto.",
|
|
||||||
"searchModeAuto": "Auto (recomendado)",
|
|
||||||
"searchModeAnn": "ANN (sqlite-vec)",
|
|
||||||
"searchModeLinear": "Linear",
|
|
||||||
"chunkingTitle": "Fragmentação",
|
|
||||||
"chunkSize": "Tamanho do Fragmento (tokens)",
|
|
||||||
"chunkSizeDesc": "Máximo aproximado de tokens por fragmento para embeddings.",
|
|
||||||
"chunkOverlap": "Sobreposição (tokens)",
|
|
||||||
"chunkOverlapDesc": "Sobreposição de tokens entre fragmentos consecutivos."
|
|
||||||
},
|
|
||||||
"dialogs": {
|
|
||||||
"changeDataFolder": {
|
|
||||||
"title": "Alterar Localização da Pasta de Dados",
|
|
||||||
"description": "Tem certeza de que deseja alterar a localização da pasta de dados? Isso moverá todos os seus dados para a nova localização e reiniciará a aplicação.",
|
|
||||||
"currentLocation": "Localização Atual:",
|
|
||||||
"newLocation": "Nova Localização:",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"changeLocation": "Alterar Localização"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backendUpdater": {
|
|
||||||
"newBackendVersion": "Nova Versão Llamacpp {{version}}",
|
|
||||||
"backendUpdateAvailable": "Atualização Llamacpp Disponível",
|
|
||||||
"remindMeLater": "Lembre-me Mais Tarde",
|
|
||||||
"updating": "Atualizando...",
|
|
||||||
"updateNow": "Atualizar Agora",
|
|
||||||
"updateSuccess": "Llamacpp atualizado com sucesso",
|
|
||||||
"updateError": "Falha ao atualizar Llamacpp"
|
|
||||||
},
|
|
||||||
"backendInstallSuccess": "Backend instalado com sucesso",
|
|
||||||
"backendInstallError": "Falha ao instalar backend"
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Bem-vindo ao Jan",
|
|
||||||
"description": "Para começar, você precisará baixar um modelo de IA local ou conectar-se a um modelo em nuvem usando uma chave de API",
|
|
||||||
"localModel": "Configurar modelo local",
|
|
||||||
"remoteProvider": "Configurar provedor remoto"
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Monitor do Sistema",
|
|
||||||
"cpuUsage": "Uso da CPU",
|
|
||||||
"model": "Modelo",
|
|
||||||
"cores": "Núcleos",
|
|
||||||
"architecture": "Arquitetura",
|
|
||||||
"currentUsage": "Uso Atual",
|
|
||||||
"memoryUsage": "Uso da Memória",
|
|
||||||
"totalRam": "RAM Total",
|
|
||||||
"availableRam": "RAM Disponível",
|
|
||||||
"usedRam": "RAM Usada",
|
|
||||||
"runningModels": "Modelos em Execução",
|
|
||||||
"noRunningModels": "Nenhum modelo está executando atualmente",
|
|
||||||
"provider": "Provedor",
|
|
||||||
"uptime": "Tempo de Atividade",
|
|
||||||
"actions": "Ações",
|
|
||||||
"stop": "Parar",
|
|
||||||
"activeGpus": "GPUs Ativas",
|
|
||||||
"noGpus": "Nenhuma GPU detectada",
|
|
||||||
"noActiveGpus": "Nenhuma GPU ativa. Todas as GPUs estão atualmente desabilitadas.",
|
|
||||||
"vramUsage": "Uso da VRAM",
|
|
||||||
"driverVersion": "Versão do Driver:",
|
|
||||||
"computeCapability": "Capacidade de Computação:",
|
|
||||||
"active": "Ativo",
|
|
||||||
"performance": "Desempenho",
|
|
||||||
"resources": "Recursos",
|
|
||||||
"refresh": "Atualizar"
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Solicitação de Chamada de Ferramenta",
|
|
||||||
"description": "O assistente quer usar a ferramenta: <strong>{{toolName}}</strong>",
|
|
||||||
"securityNotice": "<strong>Aviso de Segurança:</strong> Ferramentas maliciosas ou conteúdo de conversa podem potencialmente enganar o assistente para tentar ações prejudiciais. Revise cada chamada de ferramenta cuidadosamente antes de aprovar.",
|
|
||||||
"deny": "Negar",
|
|
||||||
"allowOnce": "Permitir Uma Vez",
|
|
||||||
"alwaysAllow": "Permitir na conversa",
|
|
||||||
"permissions": "Permissões",
|
|
||||||
"approve": "Aprovar",
|
|
||||||
"reject": "Rejeitar",
|
|
||||||
"parameters": "Parâmetros da Ferramenta"
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"toolApproval": {
|
|
||||||
"title": "Aprovação de Ferramenta Necessária",
|
|
||||||
"description": "O assistente quer usar a ferramenta: <strong>{{toolName}}</strong>",
|
|
||||||
"securityNotice": "<strong>Aviso de Segurança:</strong> Ferramentas maliciosas ou conteúdo de conversa podem potencialmente enganar o assistente para tentar ações prejudiciais. Revise cada chamada de ferramenta cuidadosamente antes de aprovar.",
|
|
||||||
"deny": "Negar",
|
|
||||||
"allowOnce": "Permitir Uma Vez",
|
|
||||||
"alwaysAllow": "Permitir na conversa",
|
|
||||||
"parameters": "Parâmetros da Ferramenta",
|
|
||||||
"permissionScope": "Permissões concedidas aplicam-se apenas a esta conversa."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"newVersion": "Nova Versão {{version}}",
|
|
||||||
"updateAvailable": "Atualização Disponível",
|
|
||||||
"nightlyBuild": "Build Noturno",
|
|
||||||
"showReleaseNotes": "Mostrar Notas de Versão",
|
|
||||||
"hideReleaseNotes": "Ocultar Notas de Versão",
|
|
||||||
"remindMeLater": "Lembre-me Mais Tarde",
|
|
||||||
"downloading": "Baixando...",
|
|
||||||
"updateNow": "Atualizar Agora"
|
|
||||||
}
|
|
||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "Công cụ",
|
"tools": "Công cụ",
|
||||||
"webSearch": "Tìm kiếm trên web",
|
"webSearch": "Tìm kiếm trên web",
|
||||||
"reasoning": "Lý luận",
|
"reasoning": "Lý luận",
|
||||||
"proactive": "Chủ động",
|
|
||||||
"selectAModel": "Chọn một mô hình",
|
"selectAModel": "Chọn một mô hình",
|
||||||
"noToolsAvailable": "Không có công cụ nào",
|
"noToolsAvailable": "Không có công cụ nào",
|
||||||
"noModelsFoundFor": "Không tìm thấy mô hình nào cho \"{{searchValue}}\"",
|
"noModelsFoundFor": "Không tìm thấy mô hình nào cho \"{{searchValue}}\"",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "Khả năng",
|
"capabilities": "Khả năng",
|
||||||
"tools": "Công cụ",
|
"tools": "Công cụ",
|
||||||
"vision": "Thị giác",
|
"vision": "Thị giác",
|
||||||
"proactive": "Chủ động (Thử nghiệm)",
|
|
||||||
"embeddings": "Nhúng",
|
"embeddings": "Nhúng",
|
||||||
"notAvailable": "Chưa có",
|
"notAvailable": "Chưa có",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"webSearch": "网页搜索",
|
"webSearch": "网页搜索",
|
||||||
"reasoning": "推理",
|
"reasoning": "推理",
|
||||||
"proactive": "主动模式",
|
|
||||||
"selectAModel": "选择一个模型",
|
"selectAModel": "选择一个模型",
|
||||||
"noToolsAvailable": "无可用工具",
|
"noToolsAvailable": "无可用工具",
|
||||||
"noModelsFoundFor": "未找到“{{searchValue}}”的模型",
|
"noModelsFoundFor": "未找到“{{searchValue}}”的模型",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "功能",
|
"capabilities": "功能",
|
||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"vision": "视觉",
|
"vision": "视觉",
|
||||||
"proactive": "主动模式(实验性)",
|
|
||||||
"embeddings": "嵌入",
|
"embeddings": "嵌入",
|
||||||
"notAvailable": "尚不可用",
|
"notAvailable": "尚不可用",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"webSearch": "網路搜尋",
|
"webSearch": "網路搜尋",
|
||||||
"reasoning": "推理",
|
"reasoning": "推理",
|
||||||
"proactive": "主動模式",
|
|
||||||
"selectAModel": "選擇一個模型",
|
"selectAModel": "選擇一個模型",
|
||||||
"noToolsAvailable": "沒有可用的工具",
|
"noToolsAvailable": "沒有可用的工具",
|
||||||
"noModelsFoundFor": "找不到符合「{{searchValue}}」的模型",
|
"noModelsFoundFor": "找不到符合「{{searchValue}}」的模型",
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"capabilities": "功能",
|
"capabilities": "功能",
|
||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"vision": "視覺",
|
"vision": "視覺",
|
||||||
"proactive": "主動模式(實驗性)",
|
|
||||||
"embeddings": "嵌入",
|
"embeddings": "嵌入",
|
||||||
"notAvailable": "尚不可用",
|
"notAvailable": "尚不可用",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user