Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
bright-spark committed Jan 3, 2025
2 parents 32b98ea + 31e03ce commit 475081d
Show file tree
Hide file tree
Showing 37 changed files with 1,384 additions and 620 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ OPENAI_LIKE_API_KEY=
# Get your Together API Key
TOGETHER_API_KEY=

# You only need this environment variable set if you want to use Hyperbolic models
#Get your Hyperbolics API Key at https://app.hyperbolic.xyz/settings
#baseURL="https://api.hyperbolic.xyz/v1/chat/completions"
HYPERBOLIC_API_KEY=
HYPERBOLIC_API_BASE_URL=

# Get your Mistral API Key by following these instructions -
# https://console.mistral.ai/api-keys/
# You only need this environment variable set if you want to use Mistral models
Expand Down
6 changes: 3 additions & 3 deletions .github/scripts/generate-changelog.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ while IFS= read -r commit_line; do
fi

CATEGORIES["$CATEGORY"]=1
COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by [@$GITHUB_USERNAME](https://github.com/$GITHUB_USERNAME)"$'\n'
COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n'
else
COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM))"$'\n'
fi
Expand Down Expand Up @@ -165,7 +165,7 @@ while IFS= read -r commit_line; do
CATEGORIES["$CATEGORY"]=1
COMMIT_TITLE=${COMMIT_MSG%% (#*} # Remove the PR number suffix
COMMIT_TITLE=${COMMIT_TITLE#*: } # Remove the type prefix
COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by [@$GITHUB_USERNAME](https://github.com/$GITHUB_USERNAME)"$'\n'
COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n'
else
COMMIT_TITLE=${COMMIT_MSG%% (#*} # Remove the PR number suffix
COMMIT_TITLE=${COMMIT_TITLE#*: } # Remove the type prefix
Expand Down Expand Up @@ -196,7 +196,7 @@ while IFS= read -r commit_line; do

CATEGORIES["$CATEGORY"]=1
COMMIT_TITLE=${COMMIT_MSG#*: } # Remove the type prefix
COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by [@$GITHUB_USERNAME](https://github.com/$GITHUB_USERNAME)"$'\n'
COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by @$GITHUB_USERNAME"$'\n'
else
# Fallback to git author name if no GitHub username found
AUTHOR_NAME=$(git show -s --format='%an' "$HASH")
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/update-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ jobs:
id: changelog
env:
NEW_VERSION: ${{ steps.bump_version.outputs.new_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

run: .github/scripts/generate-changelog.sh

- name: Get the latest commit hash and version tag
Expand Down
18 changes: 18 additions & 0 deletions app/components/chat/APIKeyManager.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { IconButton } from '~/components/ui/IconButton';
import type { ProviderInfo } from '~/types/model';
import Cookies from 'js-cookie';

interface APIKeyManagerProps {
provider: ProviderInfo;
Expand All @@ -10,6 +11,23 @@ interface APIKeyManagerProps {
labelForGetApiKey?: string;
}

const apiKeyMemoizeCache: { [k: string]: Record<string, string> } = {};

export function getApiKeysFromCookies() {
const storedApiKeys = Cookies.get('apiKeys');
let parsedKeys = {};

if (storedApiKeys) {
parsedKeys = apiKeyMemoizeCache[storedApiKeys];

if (!parsedKeys) {
parsedKeys = apiKeyMemoizeCache[storedApiKeys] = JSON.parse(storedApiKeys);
}
}

return parsedKeys;
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
const [isEditing, setIsEditing] = useState(false);
Expand Down
165 changes: 96 additions & 69 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Preventing TS checks with files presented in the video for a better presentation.
*/
import type { Message } from 'ai';
import React, { type RefCallback, useEffect, useState } from 'react';
import React, { type RefCallback, useCallback, useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { Menu } from '~/components/sidebar/Menu.client';
import { IconButton } from '~/components/ui/IconButton';
Expand All @@ -12,7 +12,7 @@ import { classNames } from '~/utils/classNames';
import { MODEL_LIST, PROVIDER_LIST, initializeModelList } from '~/utils/constants';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { APIKeyManager } from './APIKeyManager';
import { APIKeyManager, getApiKeysFromCookies } from './APIKeyManager';
import Cookies from 'js-cookie';
import * as Tooltip from '@radix-ui/react-tooltip';

Expand All @@ -31,6 +31,7 @@ import { toast } from 'react-toastify';
import StarterTemplates from './StarterTemplates';
import type { ActionAlert } from '~/types/actions';
import ChatAlert from './ChatAlert';
import { LLMManager } from '~/lib/modules/llm/manager';

const TEXTAREA_MIN_HEIGHT = 76;

Expand Down Expand Up @@ -100,53 +101,15 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
ref,
) => {
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
const [apiKeys, setApiKeys] = useState<Record<string, string>>(() => {
const savedKeys = Cookies.get('apiKeys');

if (savedKeys) {
try {
return JSON.parse(savedKeys);
} catch (error) {
console.error('Failed to parse API keys from cookies:', error);
return {};
}
}

return {};
});
const [apiKeys, setApiKeys] = useState<Record<string, string>>(getApiKeysFromCookies());
const [modelList, setModelList] = useState(MODEL_LIST);
const [isModelSettingsCollapsed, setIsModelSettingsCollapsed] = useState(false);
const [isListening, setIsListening] = useState(false);
const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
const [transcript, setTranscript] = useState('');
const [isModelLoading, setIsModelLoading] = useState<string | undefined>('all');

useEffect(() => {
console.log(transcript);
}, [transcript]);

useEffect(() => {
// Load API keys from cookies on component mount

let parsedApiKeys: Record<string, string> | undefined = {};

try {
const storedApiKeys = Cookies.get('apiKeys');

if (storedApiKeys) {
const parsedKeys = JSON.parse(storedApiKeys);

if (typeof parsedKeys === 'object' && parsedKeys !== null) {
setApiKeys(parsedKeys);
parsedApiKeys = parsedKeys;
}
}
} catch (error) {
console.error('Error loading API keys from cookies:', error);

// Clear invalid cookie data
Cookies.remove('apiKeys');
}

const getProviderSettings = useCallback(() => {
let providerSettings: Record<string, IProviderSetting> | undefined = undefined;

try {
Expand All @@ -166,11 +129,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
Cookies.remove('providers');
}

initializeModelList({ apiKeys: parsedApiKeys, providerSettings }).then((modelList) => {
console.log('Model List: ', modelList);
setModelList(modelList);
});
return providerSettings;
}, []);
useEffect(() => {
console.log(transcript);
}, [transcript]);

useEffect(() => {
if (typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
Expand Down Expand Up @@ -202,6 +167,65 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
}
}, []);

useEffect(() => {
if (typeof window !== 'undefined') {
const providerSettings = getProviderSettings();
let parsedApiKeys: Record<string, string> | undefined = {};

try {
parsedApiKeys = getApiKeysFromCookies();
setApiKeys(parsedApiKeys);
} catch (error) {
console.error('Error loading API keys from cookies:', error);

// Clear invalid cookie data
Cookies.remove('apiKeys');
}
setIsModelLoading('all');
initializeModelList({ apiKeys: parsedApiKeys, providerSettings })
.then((modelList) => {
// console.log('Model List: ', modelList);
setModelList(modelList);
})
.catch((error) => {
console.error('Error initializing model list:', error);
})
.finally(() => {
setIsModelLoading(undefined);
});
}
}, [providerList]);

const onApiKeysChange = async (providerName: string, apiKey: string) => {
const newApiKeys = { ...apiKeys, [providerName]: apiKey };
setApiKeys(newApiKeys);
Cookies.set('apiKeys', JSON.stringify(newApiKeys));

const provider = LLMManager.getInstance(import.meta.env || process.env || {}).getProvider(providerName);

if (provider && provider.getDynamicModels) {
setIsModelLoading(providerName);

try {
const providerSettings = getProviderSettings();
const staticModels = provider.staticModels;
const dynamicModels = await provider.getDynamicModels(
newApiKeys,
providerSettings,
import.meta.env || process.env || {},
);

setModelList((preModels) => {
const filteredOutPreModels = preModels.filter((x) => x.provider !== providerName);
return [...filteredOutPreModels, ...staticModels, ...dynamicModels];
});
} catch (error) {
console.error('Error loading dynamic models:', error);
}
setIsModelLoading(undefined);
}
};

const startListening = () => {
if (recognition) {
recognition.start();
Expand Down Expand Up @@ -379,29 +403,32 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
</svg>
<div>
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
<ModelSelector
key={provider?.name + ':' + modelList.length}
model={model}
setModel={setModel}
modelList={modelList}
provider={provider}
setProvider={setProvider}
providerList={providerList || (PROVIDER_LIST as ProviderInfo[])}
apiKeys={apiKeys}
/>
{(providerList || []).length > 0 && provider && (
<APIKeyManager
provider={provider}
apiKey={apiKeys[provider.name] || ''}
setApiKey={(key) => {
const newApiKeys = { ...apiKeys, [provider.name]: key };
setApiKeys(newApiKeys);
Cookies.set('apiKeys', JSON.stringify(newApiKeys));
}}
/>
<ClientOnly>
{() => (
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
<ModelSelector
key={provider?.name + ':' + modelList.length}
model={model}
setModel={setModel}
modelList={modelList}
provider={provider}
setProvider={setProvider}
providerList={providerList || (PROVIDER_LIST as ProviderInfo[])}
apiKeys={apiKeys}
modelLoading={isModelLoading}
/>
{(providerList || []).length > 0 && provider && (
<APIKeyManager
provider={provider}
apiKey={apiKeys[provider.name] || ''}
setApiKey={(key) => {
onApiKeysChange(provider.name, key);
}}
/>
)}
</div>
)}
</div>
</ClientOnly>
</div>
<FilePreview
files={uploadedFiles}
Expand Down
Loading

0 comments on commit 475081d

Please sign in to comment.