mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-10-19 13:58:14 +00:00
Compare commits
7 Commits
feat/confi
...
canary
Author | SHA1 | Date | |
---|---|---|---|
|
eef6ebb924 | ||
|
65975ba6fc | ||
|
51629b2cca | ||
|
7d71643f42 | ||
|
4564175822 | ||
|
9d52d01f31 | ||
|
5abd42d46d |
@@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@huggingface/transformers": "^3.7.5",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@icons-pack/react-simple-icons": "^12.3.0",
|
||||
"@langchain/anthropic": "^0.3.24",
|
||||
@@ -24,7 +25,6 @@
|
||||
"@langchain/openai": "^0.6.2",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"axios": "^1.8.3",
|
||||
"better-sqlite3": "^11.9.1",
|
||||
"clsx": "^2.1.0",
|
||||
@@ -65,7 +65,6 @@
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.2.5",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
@@ -31,13 +31,22 @@ const Chat = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = () => {
|
||||
messageEnd.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
messageEnd.current?.scrollIntoView({ behavior: 'auto' });
|
||||
};
|
||||
|
||||
if (chatTurns.length === 1) {
|
||||
document.title = `${chatTurns[0].content.substring(0, 30)} - Perplexica`;
|
||||
}
|
||||
|
||||
const messageEndBottom =
|
||||
messageEnd.current?.getBoundingClientRect().bottom ?? 0;
|
||||
|
||||
const distanceFromMessageEnd = window.innerHeight - messageEndBottom;
|
||||
|
||||
if (distanceFromMessageEnd >= -100) {
|
||||
scroll();
|
||||
}
|
||||
|
||||
if (chatTurns[chatTurns.length - 1]?.role === 'user') {
|
||||
scroll();
|
||||
}
|
||||
|
@@ -5,8 +5,7 @@ import Focus from './MessageInputActions/Focus';
|
||||
import Optimization from './MessageInputActions/Optimization';
|
||||
import Attach from './MessageInputActions/Attach';
|
||||
import { useChat } from '@/lib/hooks/useChat';
|
||||
import AttachSmall from './MessageInputActions/AttachSmall';
|
||||
import ModelSelector from './MessageInputActions/ModelSelector';
|
||||
import ModelSelector from './MessageInputActions/ChatModelSelector';
|
||||
|
||||
const EmptyChatMessageInput = () => {
|
||||
const { sendMessage } = useChat();
|
||||
|
@@ -97,7 +97,7 @@ const ModelSelector = () => {
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<PopoverPanel className="absolute z-10 w-[230px] sm:w-[270px] md:w-[300px] -right-4">
|
||||
<div className="bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full flex flex-col shadow-lg overflow-hidden">
|
||||
<div className="bg-light-primary dark:bg-dark-primary max-h-[300px] sm:max-w-none border rounded-lg border-light-200 dark:border-dark-200 w-full flex flex-col shadow-lg overflow-hidden">
|
||||
<div className="p-4 border-b border-light-200 dark:border-dark-200">
|
||||
<div className="relative">
|
||||
<Search
|
||||
@@ -109,7 +109,7 @@ const ModelSelector = () => {
|
||||
placeholder="Search models..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-9 pr-3 py-2 bg-light-secondary dark:bg-dark-secondary rounded-lg text-xs text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-sky-500/20 border border-transparent focus:border-sky-500/30 transition duration-200"
|
||||
className="w-full pl-9 pr-3 py-2 bg-light-secondary dark:bg-dark-secondary rounded-lg placeholder:text-sm text-sm text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-sky-500/20 border border-transparent focus:border-sky-500/30 transition duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
80
src/components/Settings/Sections/Models/ModelSelect.tsx
Normal file
80
src/components/Settings/Sections/Models/ModelSelect.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import Select from '@/components/ui/Select';
|
||||
import { ConfigModelProvider } from '@/lib/config/types';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const ModelSelect = ({
|
||||
providers,
|
||||
type,
|
||||
}: {
|
||||
providers: ConfigModelProvider[];
|
||||
type: 'chat' | 'embedding';
|
||||
}) => {
|
||||
const [selectedModel, setSelectedModel] = useState<string>(
|
||||
`${providers[0]?.id}/${providers[0].embeddingModels[0]?.key}`,
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSave = async (newValue: string) => {
|
||||
setLoading(true);
|
||||
setSelectedModel(newValue);
|
||||
try {
|
||||
if (type === 'chat') {
|
||||
localStorage.setItem('chatModelProviderId', newValue.split('/')[0]);
|
||||
localStorage.setItem('chatModelKey', newValue.split('/')[1]);
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
'embeddingModelProviderId',
|
||||
newValue.split('/')[0],
|
||||
);
|
||||
localStorage.setItem('embeddingModelKey', newValue.split('/')[1]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving config:', error);
|
||||
toast.error('Failed to save configuration.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<h4 className="text-base text-black dark:text-white">
|
||||
Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'}
|
||||
</h4>
|
||||
<p className="text-xs text-black/50 dark:text-white/50">
|
||||
{type === 'chat'
|
||||
? 'Select the model to use for chat responses'
|
||||
: 'Select the model to use for embeddings'}
|
||||
</p>
|
||||
</div>
|
||||
<Select
|
||||
value={selectedModel}
|
||||
onChange={(event) => handleSave(event.target.value)}
|
||||
options={
|
||||
type === 'chat'
|
||||
? providers.flatMap((provider) =>
|
||||
provider.chatModels.map((model) => ({
|
||||
value: `${provider.id}/${model.key}`,
|
||||
label: `${provider.name} - ${model.name}`,
|
||||
})),
|
||||
)
|
||||
: providers.flatMap((provider) =>
|
||||
provider.embeddingModels.map((model) => ({
|
||||
value: `${provider.id}/${model.key}`,
|
||||
label: `${provider.name} - ${model.name}`,
|
||||
})),
|
||||
)
|
||||
}
|
||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer capitalize pr-12"
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelSelect;
|
@@ -6,6 +6,7 @@ import {
|
||||
UIConfigField,
|
||||
} from '@/lib/config/types';
|
||||
import ModelProvider from './ModelProvider';
|
||||
import ModelSelect from './ModelSelect';
|
||||
|
||||
const Models = ({
|
||||
fields,
|
||||
@@ -17,14 +18,21 @@ const Models = ({
|
||||
const [providers, setProviders] = useState<ConfigModelProvider[]>(values);
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-6 overflow-y-auto px-6 py-6">
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<div className="flex-1 space-y-6 overflow-y-auto py-6">
|
||||
<div className="flex flex-col px-6 gap-y-4">
|
||||
<h3 className="text-sm text-black/70 dark:text-white/70">
|
||||
Select models
|
||||
</h3>
|
||||
<ModelSelect providers={values} type="embedding" />
|
||||
</div>
|
||||
<div className="border-t border-light-200 dark:border-dark-200" />
|
||||
<div className="flex flex-row justify-between items-center px-6 ">
|
||||
<p className="text-sm text-black/70 dark:text-white/70">
|
||||
Manage model provider
|
||||
</p>
|
||||
<AddProvider modelProviders={fields} setProviders={setProviders} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col px-6 gap-y-4">
|
||||
{providers.map((provider) => (
|
||||
<ModelProvider
|
||||
key={`provider-${provider.id}`}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Embeddings, type EmbeddingsParams } from '@langchain/core/embeddings';
|
||||
import { chunkArray } from '@langchain/core/utils/chunk_array';
|
||||
import { pipeline } from '@huggingface/transformers';
|
||||
|
||||
export interface HuggingFaceTransformersEmbeddingsParams
|
||||
extends EmbeddingsParams {
|
||||
@@ -67,12 +68,7 @@ export class HuggingFaceTransformersEmbeddings
|
||||
}
|
||||
|
||||
private async runEmbedding(texts: string[]) {
|
||||
const { pipeline } = await import('@xenova/transformers');
|
||||
|
||||
const pipe = await (this.pipelinePromise ??= pipeline(
|
||||
'feature-extraction',
|
||||
this.model,
|
||||
));
|
||||
const pipe = await pipeline('feature-extraction', this.model);
|
||||
|
||||
return this.caller.call(async () => {
|
||||
const output = await pipe(texts, { pooling: 'mean', normalize: true });
|
||||
|
@@ -2,10 +2,12 @@ import { ModelProviderUISection } from '@/lib/config/types';
|
||||
import { ProviderConstructor } from './baseProvider';
|
||||
import OpenAIProvider from './openai';
|
||||
import OllamaProvider from './ollama';
|
||||
import TransformersProvider from './transformers';
|
||||
|
||||
export const providers: Record<string, ProviderConstructor<any>> = {
|
||||
openai: OpenAIProvider,
|
||||
ollama: OllamaProvider,
|
||||
transformers: TransformersProvider,
|
||||
};
|
||||
|
||||
export const getModelProvidersUIConfigSection =
|
||||
|
88
src/lib/models/providers/transformers.ts
Normal file
88
src/lib/models/providers/transformers.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { Model, ModelList, ProviderMetadata } from '../types';
|
||||
import BaseModelProvider from './baseProvider';
|
||||
import { Embeddings } from '@langchain/core/embeddings';
|
||||
import { UIConfigField } from '@/lib/config/types';
|
||||
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
|
||||
import { HuggingFaceTransformersEmbeddings } from '@/lib/huggingfaceTransformer';
|
||||
|
||||
interface TransformersConfig {}
|
||||
|
||||
const defaultEmbeddingModels: Model[] = [
|
||||
{
|
||||
name: 'all-MiniLM-L6-v2',
|
||||
key: 'Xenova/all-MiniLM-L6-v2',
|
||||
},
|
||||
{
|
||||
name: 'mxbai-embed-large-v1',
|
||||
key: 'mixedbread-ai/mxbai-embed-large-v1',
|
||||
},
|
||||
{
|
||||
name: 'nomic-embed-text-v1',
|
||||
key: 'Xenova/nomic-embed-text-v1',
|
||||
},
|
||||
];
|
||||
|
||||
const providerConfigFields: UIConfigField[] = [];
|
||||
|
||||
class TransformersProvider extends BaseModelProvider<TransformersConfig> {
|
||||
constructor(id: string, name: string, config: TransformersConfig) {
|
||||
super(id, name, config);
|
||||
}
|
||||
|
||||
async getDefaultModels(): Promise<ModelList> {
|
||||
return {
|
||||
embedding: [...defaultEmbeddingModels],
|
||||
chat: [],
|
||||
};
|
||||
}
|
||||
|
||||
async getModelList(): Promise<ModelList> {
|
||||
const defaultModels = await this.getDefaultModels();
|
||||
const configProvider = getConfiguredModelProviderById(this.id)!;
|
||||
|
||||
return {
|
||||
embedding: [
|
||||
...defaultModels.embedding,
|
||||
...configProvider.embeddingModels,
|
||||
],
|
||||
chat: [],
|
||||
};
|
||||
}
|
||||
|
||||
async loadChatModel(key: string): Promise<BaseChatModel> {
|
||||
throw new Error('Transformers Provider does not support chat models.');
|
||||
}
|
||||
|
||||
async loadEmbeddingModel(key: string): Promise<Embeddings> {
|
||||
const modelList = await this.getModelList();
|
||||
const exists = modelList.embedding.find((m) => m.key === key);
|
||||
|
||||
if (!exists) {
|
||||
throw new Error(
|
||||
'Error Loading OpenAI Embedding Model. Invalid Model Selected.',
|
||||
);
|
||||
}
|
||||
|
||||
return new HuggingFaceTransformersEmbeddings({
|
||||
model: key,
|
||||
});
|
||||
}
|
||||
|
||||
static parseAndValidate(raw: any): TransformersConfig {
|
||||
return {};
|
||||
}
|
||||
|
||||
static getProviderConfigFields(): UIConfigField[] {
|
||||
return providerConfigFields;
|
||||
}
|
||||
|
||||
static getProviderMetadata(): ProviderMetadata {
|
||||
return {
|
||||
key: 'transformers',
|
||||
name: 'Transformers',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default TransformersProvider;
|
Reference in New Issue
Block a user