Compare commits

..

5 Commits

Author SHA1 Message Date
Akarshan
ea231676bf
fix: correct flash_attn and main_gpu flag checks in llamacpp extension
Previously the condition for `flash_attn` was always truthy, causing
unnecessary or incorrect `--flash-attn` arguments to be added. The
`main_gpu` check also used a loose inequality which could match values
that were not intended. The updated logic uses strict comparison and
correctly handles the empty string case, ensuring the command line
arguments are generated only when appropriate.
2025-10-30 19:49:55 +05:30
Akarshan
1f4977c1d1
fix mmap settings and adjust flash attention 2025-10-29 08:02:11 +05:30
Akarshan
7b6e4cd172
fix: compare 2025-10-29 08:02:11 +05:30
Akarshan
8b15fe4ef2
feat: Simplify backend architecture
This commit introduces a functional flag for embedding models and refactors the backend detection logic for cleaner implementation.

Key changes:

 - Embedding Support: The loadLlamaModel API and SessionInfo now include an isEmbedding: boolean flag. This allows the core process to differentiate and correctly initialize models intended for embedding tasks.

 - Backend Naming Simplification (Refactor): Consolidated the CPU-specific backend tags (e.g., win-noavx-x64, win-avx2-x64) into generic *-common_cpus-x64 variants (e.g., win-common_cpus-x64). This streamlines supported backend detection.

 - File Structure Update: Changed the download path for CUDA runtime libraries (cudart) to place them inside the specific backend's directory (/build/bin/) rather than a shared lib folder, improving asset isolation.
2025-10-29 08:02:09 +05:30
Akarshan
0c5fbc102c
refactor: Simplify Tauri plugin calls and enhance 'Flash Attention' setting
This commit introduces significant improvements to the llama.cpp extension, focusing on the 'Flash Attention' setting and refactoring Tauri plugin interactions for better code clarity and maintenance.

The backend interaction is streamlined by removing the unnecessary `libraryPath` argument from the Tauri plugin commands for loading models and listing devices.

* **Simplified API Calls:** The `loadLlamaModel`, `unloadLlamaModel`, and `get_devices` functions in both the extension and the Tauri plugin now manage the library path internally based on the backend executable's location.
* **Decoupled Logic:** The extension (`src/index.ts`) now uses the new, simplified Tauri plugin functions, which enhances modularity and reduces boilerplate code in the extension.
* **Type Consistency:** Added `UnloadResult` interface to `guest-js/index.ts` for consistency.

* **Updated UI Control:** The 'Flash Attention' setting in `settings.json` is changed from a boolean checkbox to a string-based dropdown, offering **'auto'**, **'on'**, and **'off'** options.
* **Improved Logic:** The extension logic in `src/index.ts` is updated to correctly handle the new string-based `flash_attn` configuration. It now passes the string value (`'auto'`, `'on'`, or `'off'`) directly as a command-line argument to the llama.cpp backend, simplifying the version-checking logic previously required for older llama.cpp versions. The old, complex logic tied to specific backend versions is removed.

This refactoring cleans up the extension's codebase and moves environment and path setup concerns into the Tauri plugin where they are most relevant.
2025-10-29 08:00:57 +05:30
48 changed files with 136 additions and 2003 deletions

View File

@ -168,62 +168,62 @@ jobs:
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
AWS_EC2_METADATA_DISABLED: 'true'
# noti-discord-nightly-and-update-url-readme:
# needs:
# [
# build-macos,
# build-windows-x64,
# build-linux-x64,
# get-update-version,
# set-public-provider,
# sync-temp-to-latest,
# ]
# secrets: inherit
# if: github.event_name == 'schedule'
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
# with:
# ref: refs/heads/dev
# build_reason: Nightly
# push_to_branch: dev
# new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-nightly-and-update-url-readme:
needs:
[
build-macos,
build-windows-x64,
build-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest,
]
secrets: inherit
if: github.event_name == 'schedule'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Nightly
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
# noti-discord-pre-release-and-update-url-readme:
# needs:
# [
# build-macos,
# build-windows-x64,
# build-linux-x64,
# get-update-version,
# set-public-provider,
# sync-temp-to-latest,
# ]
# secrets: inherit
# if: github.event_name == 'push'
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
# with:
# ref: refs/heads/dev
# build_reason: Pre-release
# push_to_branch: dev
# new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-pre-release-and-update-url-readme:
needs:
[
build-macos,
build-windows-x64,
build-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest,
]
secrets: inherit
if: github.event_name == 'push'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Pre-release
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
# noti-discord-manual-and-update-url-readme:
# needs:
# [
# build-macos,
# build-windows-x64,
# build-linux-x64,
# get-update-version,
# set-public-provider,
# sync-temp-to-latest,
# ]
# secrets: inherit
# if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
# with:
# ref: refs/heads/dev
# build_reason: Manual
# push_to_branch: dev
# new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-manual-and-update-url-readme:
needs:
[
build-macos,
build-windows-x64,
build-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest,
]
secrets: inherit
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Manual
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
comment-pr-build-url:
needs:

View File

@ -149,9 +149,14 @@
"key": "flash_attn",
"title": "Flash Attention",
"description": "Enable Flash Attention for optimized performance.",
"controllerType": "checkbox",
"controllerType": "dropdown",
"controllerProps": {
"value": false
"value": "auto",
"options": [
{ "value": "auto", "name": "Auto" },
{ "value": "on", "name": "ON" },
{ "value": "off", "name": "OFF" }
]
}
},
{

View File

@ -102,50 +102,27 @@ export async function listSupportedBackends(): Promise<
// TODO: fetch versions from the server?
// TODO: select CUDA version based on driver version
if (sysType == 'windows-x86_64') {
// NOTE: if a machine supports AVX2, should we include noavx and avx?
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')
supportedBackends.push('win-common_cpus-x64')
if (features.cuda11) {
if (features.avx512) supportedBackends.push('win-avx512-cuda-cu11.7-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')
supportedBackends.push('win-cuda-11-common_cpus-x64')
}
if (features.cuda12) {
if (features.avx512) supportedBackends.push('win-avx512-cuda-cu12.0-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')
supportedBackends.push('win-cuda-12-common_cpus-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
else if (sysType === 'windows-aarch64' || sysType === 'windows-arm64') {
supportedBackends.push('win-arm64')
} else if (sysType === 'linux-x86_64' || sysType === 'linux-x86') {
supportedBackends.push('linux-noavx-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')
supportedBackends.push('linux-common_cpus-x64')
if (features.cuda11) {
if (features.avx512)
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')
supportedBackends.push('linux-cuda-11-common_cpus-x64')
}
if (features.cuda12) {
if (features.avx512)
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')
supportedBackends.push('linux-cuda-12-common_cpus-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
else if (sysType === 'linux-aarch64' || sysType === 'linux-arm64') {
@ -230,10 +207,7 @@ export async function downloadBackend(
version: string,
source: 'github' | 'cdn' = 'github'
): Promise<void> {
const janDataFolderPath = await getJanDataFolderPath()
const llamacppPath = await joinPath([janDataFolderPath, 'llamacpp'])
const backendDir = await getBackendDir(backend, version)
const libDir = await joinPath([llamacppPath, 'lib'])
const downloadManager = window.core.extensionManager.getByName(
'@janhq/download-extension'
@ -265,7 +239,7 @@ export async function downloadBackend(
source === 'github'
? `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`,
save_path: await joinPath([libDir, 'cuda11.tar.gz']),
save_path: await joinPath([backendDir, 'build', 'bin', 'cuda11.tar.gz']),
proxy: proxyConfig,
})
} else if (backend.includes('cu12.0') && !(await _isCudaInstalled('12.0'))) {
@ -274,7 +248,7 @@ export async function downloadBackend(
source === 'github'
? `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`,
save_path: await joinPath([libDir, 'cuda12.tar.gz']),
save_path: await joinPath([backendDir, 'build', 'bin', 'cuda12.tar.gz']),
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
let minCuda11DriverVersion
let minCuda12DriverVersion
let minCuda11DriverVersion: string
let minCuda12DriverVersion: string
if (sysInfo.os_type === 'linux') {
minCuda11DriverVersion = '450.80.02'
minCuda12DriverVersion = '525.60.13'

View File

@ -38,10 +38,12 @@ import { invoke } from '@tauri-apps/api/core'
import { getProxyConfig } from './util'
import { basename } from '@tauri-apps/api/path'
import {
loadLlamaModel,
readGgufMetadata,
getModelSize,
isModelSupported,
planModelLoadInternal,
unloadLlamaModel,
} from '@janhq/tauri-plugin-llamacpp-api'
import { getSystemUsage, getSystemInfo } from '@janhq/tauri-plugin-hardware-api'
@ -69,7 +71,7 @@ type LlamacppConfig = {
device: string
split_mode: string
main_gpu: number
flash_attn: boolean
flash_attn: string
cont_batching: boolean
no_mmap: boolean
mlock: boolean
@ -549,9 +551,9 @@ export default class llamacpp_extension extends AIEngine {
// Helper to map backend string to a priority category
const getBackendCategory = (backendString: string): string | undefined => {
if (backendString.includes('cu12.0')) return 'cuda-cu12.0'
if (backendString.includes('cu11.7')) return 'cuda-cu11.7'
if (backendString.includes('vulkan')) return 'vulkan'
if (backendString.includes('cuda-12-common_cpus')) return 'cuda-cu12.0'
if (backendString.includes('cuda-11-common_cpus')) return 'cuda-cu11.7'
if (backendString.includes('vulkan-common_cpus')) return 'vulkan'
if (backendString.includes('avx512')) return 'avx512'
if (backendString.includes('avx2')) return 'avx2'
if (
@ -1644,18 +1646,20 @@ export default class llamacpp_extension extends AIEngine {
if (cfg.device.length > 0) args.push('--device', cfg.device)
if (cfg.split_mode.length > 0 && cfg.split_mode != 'layer')
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))
// 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
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')
args.push('--no-mmap')
if (cfg.no_mmap) args.push('--no-mmap')
if (cfg.mlock) args.push('--mlock')
if (cfg.no_kv_offload) args.push('--no-kv-offload')
if (isEmbedding) {
@ -1667,7 +1671,7 @@ export default class llamacpp_extension extends AIEngine {
if (cfg.cache_type_k && cfg.cache_type_k != 'f16')
args.push('--cache-type-k', cfg.cache_type_k)
if (
cfg.flash_attn &&
cfg.flash_attn !== 'on' &&
cfg.cache_type_v != 'f16' &&
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)
const backendPath = await getBackendExePath(backend, version)
const libraryPath = await joinPath([await this.getProviderPath(), 'lib'])
try {
// TODO: add LIBRARY_PATH
const sInfo = await invoke<SessionInfo>(
'plugin:llamacpp|load_llama_model',
{
backendPath,
libraryPath,
args,
envs,
isEmbedding,
}
)
const sInfo = await loadLlamaModel(backendPath, args, envs, isEmbedding)
return sInfo
} catch (error) {
logger.error('Error in load command:\n', error)
@ -1717,12 +1710,7 @@ export default class llamacpp_extension extends AIEngine {
const pid = sInfo.pid
try {
// Pass the PID as the session_id
const result = await invoke<UnloadResult>(
'plugin:llamacpp|unload_llama_model',
{
pid: pid,
}
)
const result = await unloadLlamaModel(pid)
// If successful, remove from active sessions
if (result.success) {
@ -2042,7 +2030,10 @@ export default class llamacpp_extension extends AIEngine {
if (sysInfo?.os_type === 'linux' && Array.isArray(sysInfo.gpus)) {
const usage = await getSystemUsage()
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[]) {
if (u && typeof u.uuid === 'string') {
uuidToUsage[u.uuid] = u
@ -2082,7 +2073,10 @@ export default class llamacpp_extension extends AIEngine {
typeof u.used_memory === 'number'
) {
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 }
}
}

View File

@ -2,11 +2,18 @@ import { invoke } from '@tauri-apps/api/core'
// Types
export interface SessionInfo {
pid: number
port: number
model_id: string
model_path: string
api_key: string
pid: number;
port: number;
model_id: string;
model_path: string;
is_embedding: boolean
api_key: string;
mmproj_path?: string;
}
export interface UnloadResult {
success: boolean;
error?: string;
}
export interface DeviceInfo {
@ -29,19 +36,19 @@ export async function cleanupLlamaProcesses(): Promise<void> {
// LlamaCpp server commands
export async function loadLlamaModel(
backendPath: string,
libraryPath?: string,
args: string[] = [],
isEmbedding: boolean = false
args: string[],
envs: Record<string, string>,
isEmbedding: boolean
): Promise<SessionInfo> {
return await invoke('plugin:llamacpp|load_llama_model', {
backendPath,
libraryPath,
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 })
}

View File

@ -41,7 +41,6 @@ pub struct UnloadResult {
pub async fn load_llama_model<R: Runtime>(
app_handle: tauri::AppHandle<R>,
backend_path: &str,
library_path: Option<&str>,
mut args: Vec<String>,
envs: HashMap<String, String>,
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!("Using arguments: {:?}", args);
validate_binary_path(backend_path)?;
let bin_path = validate_binary_path(backend_path)?;
let port = parse_port_from_args(&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");
// 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.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.stderr(Stdio::piped());
setup_windows_process_flags(&mut command);
@ -280,10 +279,9 @@ pub async fn unload_llama_model<R: Runtime>(
#[tauri::command]
pub async fn get_devices(
backend_path: &str,
library_path: Option<&str>,
envs: HashMap<String, String>,
) -> 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

View File

@ -19,20 +19,19 @@ pub struct DeviceInfo {
pub async fn get_devices_from_backend(
backend_path: &str,
library_path: Option<&str>,
envs: HashMap<String, String>,
) -> ServerResult<Vec<DeviceInfo>> {
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
let mut command = Command::new(backend_path);
let mut command = Command::new(&bin_path);
command.arg("--list-devices");
command.envs(envs);
// 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.stderr(Stdio::piped());
@ -410,4 +409,4 @@ AnotherInvalid
assert_eq!(result[0].id, "Vulkan0");
assert_eq!(result[1].id, "CUDA0");
}
}
}

View File

@ -62,7 +62,6 @@ pub async fn estimate_kv_cache_internal(
ctx_size: Option<u64>,
) -> Result<KVCacheEstimate, KVCacheError> {
log::info!("Received ctx_size parameter: {:?}", ctx_size);
log::info!("Received model metadata:\n{:?}", &meta);
let arch = meta
.get("general.architecture")
.ok_or(KVCacheError::ArchitectureNotFound)?;

View File

@ -10,7 +10,6 @@ import {
IconAtom,
IconWorld,
IconCodeCircle2,
IconSparkles,
} from '@tabler/icons-react'
import { Fragment } from 'react/jsx-runtime'
@ -30,8 +29,6 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
icon = <IconEye className="size-4" />
} else if (capability === 'tools') {
icon = <IconTool className="size-3.5" />
} else if (capability === 'proactive') {
icon = <IconSparkles className="size-3.5" />
} else if (capability === 'reasoning') {
icon = <IconAtom className="size-3.5" />
} else if (capability === 'embeddings') {
@ -57,11 +54,7 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
</TooltipTrigger>
<TooltipContent>
<p>
{capability === 'web_search'
? 'Web Search'
: capability === 'proactive'
? 'Proactive'
: capability}
{capability === 'web_search' ? 'Web Search' : capability}
</p>
</TooltipContent>
</Tooltip>

View File

@ -16,7 +16,6 @@ const LANGUAGES = [
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
{ value: 'de-DE', label: 'Deutsch' },
{ value: 'pt-BR', label: 'Português (Brasil)' },
{ value: 'ja', label: '日本語' },
]

View File

@ -152,19 +152,12 @@ export const ModelInfoHoverCard = ({
</div>
{/* 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">
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
Features
</h5>
<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 && (
<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">
@ -172,10 +165,10 @@ export const ModelInfoHoverCard = ({
</span>
</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">
<span className="text-xs text-main-view-fg font-medium">
Proactive
Tools
</span>
</div>
)}

View File

@ -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()
})
})

View File

@ -437,31 +437,4 @@ describe('ChatInput', () => {
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()
})
})
})

View File

@ -82,7 +82,6 @@ vi.mock('@tabler/icons-react', () => ({
IconEye: () => <div data-testid="eye-icon" />,
IconTool: () => <div data-testid="tool-icon" />,
IconLoader2: () => <div data-testid="loader-icon" />,
IconSparkles: () => <div data-testid="sparkles-icon" />,
}))
describe('DialogEditModel - Basic Component Tests', () => {
@ -190,7 +189,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
{
id: 'test-model.gguf',
displayName: 'Test Model',
capabilities: ['vision', 'tools', 'proactive'],
capabilities: ['vision', 'tools'],
},
],
settings: [],
@ -227,7 +226,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
{
id: 'test-model.gguf',
displayName: 'Test Model',
capabilities: ['vision', 'tools', 'proactive', 'completion', 'embeddings', 'web_search', 'reasoning'],
capabilities: ['vision', 'tools', 'completion', 'embeddings', 'web_search', 'reasoning'],
},
],
settings: [],
@ -241,7 +240,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
)
// 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()
})
})

View File

@ -17,7 +17,6 @@ import {
IconTool,
IconAlertTriangle,
IconLoader2,
IconSparkles,
} from '@tabler/icons-react'
import { useState, useEffect } from 'react'
import { useTranslation } from '@/i18n/react-i18next-compat'
@ -46,7 +45,6 @@ export const DialogEditModel = ({
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
vision: false,
tools: false,
proactive: false,
})
// Initialize with the provided model ID or the first model if available
@ -69,7 +67,6 @@ export const DialogEditModel = ({
const capabilitiesToObject = (capabilitiesList: string[]) => ({
vision: capabilitiesList.includes('vision'),
tools: capabilitiesList.includes('tools'),
proactive: capabilitiesList.includes('proactive'),
})
// Initialize capabilities and display name from selected model
@ -271,23 +268,6 @@ export const DialogEditModel = ({
disabled={isLoading}
/>
</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>

View File

@ -170,7 +170,6 @@ vi.mock('@/lib/completion', () => ({
sendCompletion: vi.fn(),
postMessageProcessing: vi.fn(),
isCompletionResponse: vi.fn(),
captureProactiveScreenshots: vi.fn(() => Promise.resolve([])),
}))
vi.mock('@/lib/messages', () => ({
@ -226,26 +225,4 @@ describe('useChat', () => {
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')
})
})
})

View File

@ -16,7 +16,6 @@ import {
newUserThreadContent,
postMessageProcessing,
sendCompletion,
captureProactiveScreenshots,
} from '@/lib/completion'
import { CompletionMessagesBuilder } from '@/lib/messages'
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
while (
@ -716,10 +694,6 @@ export const useChat = () => {
)
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(
toolCalls,
builder,
@ -727,8 +701,7 @@ export const useChat = () => {
abortController,
useToolApproval.getState().approvedTools,
allowAllMCPPermissions ? undefined : showApprovalModal,
allowAllMCPPermissions,
isProactiveMode
allowAllMCPPermissions
)
addMessage(updatedMessage ?? finalContent)
updateStreamingContent(emptyThreadContent)

View File

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
import {
newUserThreadContent,
newAssistantThreadContent,
emptyThreadContent,
@ -8,8 +8,7 @@ import {
stopModel,
normalizeTools,
extractToolCall,
postMessageProcessing,
captureProactiveScreenshots
postMessageProcessing
} from '../completion'
// Mock dependencies
@ -73,54 +72,6 @@ vi.mock('../extension', () => ({
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', () => {
beforeEach(() => {
vi.clearAllMocks()
@ -236,448 +187,4 @@ describe('completion.ts', () => {
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()
})
})
})

View File

@ -396,120 +396,6 @@ export const extractToolCall = (
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.
* @param calls
@ -519,7 +405,6 @@ const filterOldProactiveScreenshots = (builder: CompletionMessagesBuilder) => {
* @param approvedTools
* @param showModal
* @param allowAllMCPPermissions
* @param isProactiveMode
*/
export const postMessageProcessing = async (
calls: ChatCompletionMessageToolCall[],
@ -532,8 +417,7 @@ export const postMessageProcessing = async (
threadId: string,
toolParameters?: object
) => Promise<boolean>,
allowAllMCPPermissions: boolean = false,
isProactiveMode: boolean = false
allowAllMCPPermissions: boolean = false
) => {
// Handle completed tool calls
if (calls.length) {
@ -589,7 +473,6 @@ export const postMessageProcessing = async (
const toolName = toolCall.function.name
const toolArgs = toolCall.function.arguments.length ? toolParameters : {}
const isRagTool = ragToolNames.has(toolName)
const isBrowserTool = isBrowserMCPTool(toolName)
// Auto-approve RAG tools (local/safe operations), require permission for MCP tools
const approved = isRagTool
@ -679,27 +562,6 @@ export const postMessageProcessing = async (
],
}
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
}
return message

View File

@ -80,7 +80,6 @@
"tools": "Werkzeuge",
"webSearch": "Web Suche",
"reasoning": "Argumentation",
"proactive": "Proaktiv",
"selectAModel": "Wähle ein Modell",
"noToolsAvailable": "Keine Werkzeuge verfügbar",
"noModelsFoundFor": "Keine Modelle gefunden zu \"{{searchValue}}\"",

View File

@ -61,7 +61,6 @@
"capabilities": "Fähigkeiten",
"tools": "Werkzeuge",
"vision": "Vision",
"proactive": "Proaktiv (Experimentell)",
"embeddings": "Einbettungen",
"notAvailable": "Noch nicht verfügbar",
"warning": {

View File

@ -81,7 +81,6 @@
"tools": "Tools",
"webSearch": "Web Search",
"reasoning": "Reasoning",
"proactive": "Proactive",
"selectAModel": "Select a model",
"noToolsAvailable": "No tools available",
"noModelsFoundFor": "No models found for \"{{searchValue}}\"",

View File

@ -61,7 +61,6 @@
"capabilities": "Capabilities",
"tools": "Tools",
"vision": "Vision",
"proactive": "Proactive (Experimental)",
"embeddings": "Embeddings",
"notAvailable": "Not available yet",
"warning": {

View File

@ -80,7 +80,6 @@
"tools": "Alat",
"webSearch": "Pencarian Web",
"reasoning": "Penalaran",
"proactive": "Proaktif",
"selectAModel": "Pilih model",
"noToolsAvailable": "Tidak ada alat yang tersedia",
"noModelsFoundFor": "Tidak ada model yang ditemukan untuk \"{{searchValue}}\"",

View File

@ -61,7 +61,6 @@
"capabilities": "Kemampuan",
"tools": "Alat",
"vision": "Visi",
"proactive": "Proaktif (Eksperimental)",
"embeddings": "Embedding",
"notAvailable": "Belum tersedia",
"warning": {

View File

@ -80,7 +80,6 @@
"tools": "Narzędzia",
"webSearch": "Szukanie w Sieci",
"reasoning": "Rozumowanie",
"proactive": "Proaktywny",
"selectAModel": "Wybierz Model",
"noToolsAvailable": "Brak narzędzi",
"noModelsFoundFor": "Brak modeli dla \"{{searchValue}}\"",

View File

@ -61,7 +61,6 @@
"capabilities": "Możliwości",
"tools": "Narzędzia",
"vision": "Wizja",
"proactive": "Proaktywny (Eksperymentalny)",
"embeddings": "Osadzenia",
"notAvailable": "Jeszcze niedostępne",
"warning": {

View File

@ -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)"
}

View File

@ -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"
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}

View File

@ -1,3 +0,0 @@
{
"noLogs": "Nenhum log disponível"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -1,5 +0,0 @@
{
"addProvider": "Adicionar Provedor",
"addOpenAIProvider": "Adicionar Provedor OpenAI",
"enterNameForProvider": "Digite o nome para o provedor"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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."
}
}

View File

@ -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"
}

View File

@ -80,7 +80,6 @@
"tools": "Công cụ",
"webSearch": "Tìm kiếm trên web",
"reasoning": "Lý luận",
"proactive": "Chủ động",
"selectAModel": "Chọn một mô hình",
"noToolsAvailable": "Không có công cụ nào",
"noModelsFoundFor": "Không tìm thấy mô hình nào cho \"{{searchValue}}\"",

View File

@ -61,7 +61,6 @@
"capabilities": "Khả năng",
"tools": "Công cụ",
"vision": "Thị giác",
"proactive": "Chủ động (Thử nghiệm)",
"embeddings": "Nhúng",
"notAvailable": "Chưa có",
"warning": {

View File

@ -80,7 +80,6 @@
"tools": "工具",
"webSearch": "网页搜索",
"reasoning": "推理",
"proactive": "主动模式",
"selectAModel": "选择一个模型",
"noToolsAvailable": "无可用工具",
"noModelsFoundFor": "未找到“{{searchValue}}”的模型",

View File

@ -61,7 +61,6 @@
"capabilities": "功能",
"tools": "工具",
"vision": "视觉",
"proactive": "主动模式(实验性)",
"embeddings": "嵌入",
"notAvailable": "尚不可用",
"warning": {

View File

@ -80,7 +80,6 @@
"tools": "工具",
"webSearch": "網路搜尋",
"reasoning": "推理",
"proactive": "主動模式",
"selectAModel": "選擇一個模型",
"noToolsAvailable": "沒有可用的工具",
"noModelsFoundFor": "找不到符合「{{searchValue}}」的模型",

View File

@ -61,7 +61,6 @@
"capabilities": "功能",
"tools": "工具",
"vision": "視覺",
"proactive": "主動模式(實驗性)",
"embeddings": "嵌入",
"notAvailable": "尚不可用",
"warning": {