feat: rebuild as multi-user web agent
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<script lang="ts">
|
||||
import { models } from '$lib/stores';
|
||||
|
||||
export let selectedModels = [''];
|
||||
export let disabled = false;
|
||||
export let showSetDefault = true;
|
||||
|
||||
const strengths = [
|
||||
{ key: 'light', label: '轻度' },
|
||||
{ key: 'medium', label: '中' },
|
||||
{ key: 'high', label: '高' }
|
||||
];
|
||||
|
||||
const allowed = new Set([
|
||||
'chat-light',
|
||||
'chat-medium',
|
||||
'chat-high',
|
||||
'work-light',
|
||||
'work-medium',
|
||||
'work-high'
|
||||
]);
|
||||
|
||||
$: selected = allowed.has(selectedModels?.[0]) ? selectedModels[0] : 'chat-medium';
|
||||
$: mode = selected.startsWith('work-') ? 'work' : 'chat';
|
||||
$: strength = selected.split('-')[1] ?? 'medium';
|
||||
$: availableIds = new Set($models.map((model) => model.id));
|
||||
|
||||
$: if ($models.length > 0 && !availableIds.has(selectedModels?.[0])) {
|
||||
const fallback = availableIds.has('chat-medium')
|
||||
? 'chat-medium'
|
||||
: [...allowed].find((id) => availableIds.has(id));
|
||||
if (fallback) selectedModels = [fallback];
|
||||
}
|
||||
|
||||
const chooseMode = (nextMode: 'chat' | 'work') => {
|
||||
if (disabled || (mode === 'work' && nextMode === 'chat')) return;
|
||||
const next = `${nextMode}-${strength}`;
|
||||
if (availableIds.has(next)) selectedModels = [next];
|
||||
};
|
||||
|
||||
const chooseStrength = (nextStrength: string) => {
|
||||
if (disabled) return;
|
||||
const next = `${mode}-${nextStrength}`;
|
||||
if (availableIds.has(next)) selectedModels = [next];
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex max-w-fit items-center gap-2 select-none">
|
||||
<div
|
||||
class="flex items-center rounded-xl bg-gray-100/90 p-1 text-sm dark:bg-gray-800/90"
|
||||
aria-label="Agent mode"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 font-medium transition {mode === 'chat'
|
||||
? 'bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white'
|
||||
: 'text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200'}"
|
||||
on:click={() => chooseMode('chat')}
|
||||
disabled={disabled || mode === 'work'}
|
||||
title={mode === 'work' ? '此对话已升级为 Work,不能返回 Chat' : '快速对话'}
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 font-medium transition {mode === 'work'
|
||||
? 'bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white'
|
||||
: 'text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200'}"
|
||||
on:click={() => chooseMode('work')}
|
||||
disabled={disabled}
|
||||
title="长链路、多步骤工作"
|
||||
>
|
||||
Work
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center rounded-xl border border-gray-200/80 p-1 text-xs dark:border-gray-700/80"
|
||||
aria-label="Inference strength"
|
||||
>
|
||||
{#each strengths as item}
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-2.5 py-1.5 transition {strength === item.key
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800'}"
|
||||
on:click={() => chooseStrength(item.key)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py
|
||||
index 9c1325e..a19f128 100644
|
||||
--- a/backend/open_webui/routers/openai.py
|
||||
+++ b/backend/open_webui/routers/openai.py
|
||||
@@ -10,7 +10,6 @@ from urllib.parse import quote, urlparse
|
||||
|
||||
import aiohttp
|
||||
from aiocache import cached
|
||||
-from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import (
|
||||
FileResponse,
|
||||
@@ -218,6 +217,8 @@ def get_microsoft_entra_id_access_token():
|
||||
Get Microsoft Entra ID access token using DefaultAzureCredential for Azure OpenAI.
|
||||
Returns the token string or None if authentication fails.
|
||||
"""
|
||||
+ from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
||||
+
|
||||
try:
|
||||
token_provider = get_bearer_token_provider(
|
||||
DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'
|
||||
@@ -0,0 +1,44 @@
|
||||
diff --git a/backend/open_webui/storage/provider.py b/backend/open_webui/storage/provider.py
|
||||
index 261f010..048ea4b 100644
|
||||
--- a/backend/open_webui/storage/provider.py
|
||||
+++ b/backend/open_webui/storage/provider.py
|
||||
@@ -6,14 +6,6 @@ import shutil
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import BinaryIO, Dict, Tuple
|
||||
|
||||
-import boto3
|
||||
-from azure.core.exceptions import ResourceNotFoundError
|
||||
-from azure.identity import DefaultAzureCredential
|
||||
-from azure.storage.blob import BlobServiceClient
|
||||
-from botocore.config import Config
|
||||
-from botocore.exceptions import ClientError
|
||||
-from google.cloud import storage
|
||||
-from google.cloud.exceptions import GoogleCloudError, NotFound
|
||||
from open_webui.config import (
|
||||
AZURE_STORAGE_CONTAINER_NAME,
|
||||
AZURE_STORAGE_ENDPOINT,
|
||||
@@ -335,10 +327,24 @@ def get_storage_provider(storage_provider: str):
|
||||
if storage_provider == 'local':
|
||||
Storage = LocalStorageProvider()
|
||||
elif storage_provider == 's3':
|
||||
+ global boto3, Config, ClientError
|
||||
+ import boto3
|
||||
+ from botocore.config import Config
|
||||
+ from botocore.exceptions import ClientError
|
||||
+
|
||||
Storage = S3StorageProvider()
|
||||
elif storage_provider == 'gcs':
|
||||
+ global storage, GoogleCloudError, NotFound
|
||||
+ from google.cloud import storage
|
||||
+ from google.cloud.exceptions import GoogleCloudError, NotFound
|
||||
+
|
||||
Storage = GCSStorageProvider()
|
||||
elif storage_provider == 'azure':
|
||||
+ global ResourceNotFoundError, DefaultAzureCredential, BlobServiceClient
|
||||
+ from azure.core.exceptions import ResourceNotFoundError
|
||||
+ from azure.identity import DefaultAzureCredential
|
||||
+ from azure.storage.blob import BlobServiceClient
|
||||
+
|
||||
Storage = AzureStorageProvider()
|
||||
else:
|
||||
raise RuntimeError(f'Unsupported storage provider: {storage_provider}')
|
||||
@@ -0,0 +1,71 @@
|
||||
diff --git a/backend/open_webui/retrieval/vector/dbs/disabled.py b/backend/open_webui/retrieval/vector/dbs/disabled.py
|
||||
new file mode 100644
|
||||
index 0000000..7e1ab23
|
||||
--- /dev/null
|
||||
+++ b/backend/open_webui/retrieval/vector/dbs/disabled.py
|
||||
@@ -0,0 +1,50 @@
|
||||
+from typing import Dict, List, Optional, Union
|
||||
+
|
||||
+from open_webui.retrieval.vector.main import (
|
||||
+ GetResult,
|
||||
+ SearchResult,
|
||||
+ VectorDBBase,
|
||||
+ VectorItem,
|
||||
+)
|
||||
+
|
||||
+
|
||||
+class DisabledVectorClient(VectorDBBase):
|
||||
+ """No-op vector backend used when all RAG surfaces are disabled."""
|
||||
+
|
||||
+ def has_collection(self, collection_name: str) -> bool:
|
||||
+ return False
|
||||
+
|
||||
+ def delete_collection(self, collection_name: str) -> None:
|
||||
+ return None
|
||||
+
|
||||
+ def insert(self, collection_name: str, items: List[VectorItem]) -> None:
|
||||
+ raise RuntimeError("Vector storage is disabled")
|
||||
+
|
||||
+ def upsert(self, collection_name: str, items: List[VectorItem]) -> None:
|
||||
+ raise RuntimeError("Vector storage is disabled")
|
||||
+
|
||||
+ def search(
|
||||
+ self,
|
||||
+ collection_name: str,
|
||||
+ vectors: List[List[Union[float, int]]],
|
||||
+ filter: Optional[Dict] = None,
|
||||
+ limit: int = 10,
|
||||
+ ) -> Optional[SearchResult]:
|
||||
+ return None
|
||||
+
|
||||
+ def query(
|
||||
+ self,
|
||||
+ collection_name: str,
|
||||
+ filter: Dict,
|
||||
+ limit: Optional[int] = None,
|
||||
+ ) -> Optional[GetResult]:
|
||||
+ return None
|
||||
+
|
||||
+ def get(self, collection_name: str) -> Optional[GetResult]:
|
||||
+ return None
|
||||
+
|
||||
+ def delete(self, collection_name: str, ids=None, filter=None) -> None:
|
||||
+ return None
|
||||
+
|
||||
+ def reset(self) -> None:
|
||||
+ return None
|
||||
diff --git a/backend/open_webui/retrieval/vector/factory.py b/backend/open_webui/retrieval/vector/factory.py
|
||||
index af10c5d..35c3266 100644
|
||||
--- a/backend/open_webui/retrieval/vector/factory.py
|
||||
+++ b/backend/open_webui/retrieval/vector/factory.py
|
||||
@@ -74,6 +74,10 @@ class Vector:
|
||||
case VectorType.CHROMA:
|
||||
from open_webui.retrieval.vector.dbs.chroma import ChromaClient
|
||||
|
||||
return ChromaClient()
|
||||
+ case 'disabled':
|
||||
+ from open_webui.retrieval.vector.dbs.disabled import DisabledVectorClient
|
||||
+
|
||||
+ return DisabledVectorClient()
|
||||
case VectorType.ORACLE23AI:
|
||||
from open_webui.retrieval.vector.dbs.oracle23ai import Oracle23aiClient
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
|
||||
index 95bf320e..461618ed 100644
|
||||
--- a/src/routes/(app)/+layout.svelte
|
||||
+++ b/src/routes/(app)/+layout.svelte
|
||||
@@ -316,9 +316,7 @@
|
||||
};
|
||||
setupKeyboardShortcuts();
|
||||
|
||||
- if ($user?.role === 'admin' && ($settings?.showChangelog ?? true)) {
|
||||
- showChangelog.set($settings?.version !== $config.version);
|
||||
- }
|
||||
+ showChangelog.set(false);
|
||||
|
||||
if ($user?.role === 'admin' || ($user?.permissions?.chat?.temporary ?? true)) {
|
||||
if ($page.url.searchParams.get('temporary-chat') === 'true') {
|
||||
Reference in New Issue
Block a user