diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 150948ae..9d88605c 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -17,20 +17,18 @@ jobs: - name: Checkout the code uses: actions/checkout@v3 - - name: Get the latest commit hash - run: echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV - - - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - - + - name: Get the latest commit hash + run: | + echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV + echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV + - name: Update commit file run: | - echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV - echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json + echo "{ \"commit\": \"$COMMIT_HASH\", \"version\": \"$CURRENT_VERSION\" }" > app/commit.json - name: Commit and push the update run: | diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml new file mode 100644 index 00000000..b6d64c88 --- /dev/null +++ b/.github/workflows/semantic-pr.yaml @@ -0,0 +1,32 @@ +name: Semantic Pull Request +on: + pull_request_target: + types: [opened, reopened, edited, synchronize] +permissions: + pull-requests: read +jobs: + main: + name: Validate PR Title + runs-on: ubuntu-latest + steps: + # https://github.com/amannn/action-semantic-pull-request/releases/tag/v5.5.3 + - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + subjectPattern: ^(?![A-Z]).+$ + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + doesn't start with an uppercase character. + types: | + fix + feat + chore + build + ci + perf + docs + refactor + revert + test \ No newline at end of file diff --git a/.github/workflows/update-stable.yml b/.github/workflows/update-stable.yml index e6fc2e58..bcb0ad95 100644 --- a/.github/workflows/update-stable.yml +++ b/.github/workflows/update-stable.yml @@ -158,12 +158,15 @@ jobs: echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - - name: Commit and Tag Release + - name: Get the latest commit hash and version tag run: | echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV - echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV + echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV + + - name: Commit and Tag Release + run: | git pull - echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json + echo "{ \"commit\": \"$COMMIT_HASH\", \"version\": \"$NEW_VERSION\" }" > app/commit.json git add package.json pnpm-lock.yaml changelog.md app/commit.json git commit -m "chore: release version ${{ steps.bump_version.outputs.new_version }}" git tag "v${{ steps.bump_version.outputs.new_version }}" diff --git a/README.md b/README.md index 405e3887..84235f8c 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,9 @@ https://thinktank.ottomator.ai - ✅ Mobile friendly (@qwikode) - ✅ Better prompt enhancing (@SujalXplores) - ✅ Attach images to prompts (@atrokhym) +- ✅ Added Git Clone button (@thecodacus) +- ✅ Git Import from url (@thecodacus) +- ✅ PromptLibrary to have different variations of prompts for different use cases (@thecodacus) - ✅ Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er) - ✅ Selection tool to target changes visually (@emcconnell) - ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs) diff --git a/app/commit.json b/app/commit.json index e6f6db03..832678f8 100644 --- a/app/commit.json +++ b/app/commit.json @@ -1 +1 @@ -{ "commit": "eb1d5417e77e699e0489f09814e87fb5afed9dd5" , "version": "" } +{ "commit": "1e72d52278730f7d22448be9d5cf2daf12559486", "version": "0.0.2" } diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index edefe058..2084cbb3 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -77,6 +77,8 @@ export const BaseChat = React.forwardRef( input = '', enhancingPrompt, handleInputChange, + + // promptEnhanced, enhancePrompt, sendMessage, handleStop, diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index dd169235..d9e57f23 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -20,6 +20,7 @@ import Cookies from 'js-cookie'; import { debounce } from '~/utils/debounce'; import { useSettings } from '~/lib/hooks/useSettings'; import type { ProviderInfo } from '~/types/model'; +import { useSearchParams } from '@remix-run/react'; const toastAnimation = cssTransition({ enter: 'animated fadeInRight', @@ -92,8 +93,9 @@ export const ChatImpl = memo( const [chatStarted, setChatStarted] = useState(initialMessages.length > 0); const [uploadedFiles, setUploadedFiles] = useState([]); // Move here const [imageDataList, setImageDataList] = useState([]); // Move here + const [searchParams, setSearchParams] = useSearchParams(); const files = useStore(workbenchStore.files); - const { activeProviders } = useSettings(); + const { activeProviders, promptId } = useSettings(); const [model, setModel] = useState(() => { const savedModel = Cookies.get('selectedModel'); @@ -115,6 +117,7 @@ export const ChatImpl = memo( body: { apiKeys, files, + promptId, }, sendExtraMessageFields: true, onError: (error) => { @@ -137,6 +140,24 @@ export const ChatImpl = memo( initialMessages, initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '', }); + useEffect(() => { + const prompt = searchParams.get('prompt'); + console.log(prompt, searchParams, model, provider); + + if (prompt) { + setSearchParams({}); + runAnimation(); + append({ + role: 'user', + content: [ + { + type: 'text', + text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${prompt}`, + }, + ] as any, // Type assertion to bypass compiler check + }); + } + }, [model, provider, searchParams]); const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer(); const { parsedMessages, parseMessages } = useMessageParser(); diff --git a/app/components/chat/CodeBlock.tsx b/app/components/chat/CodeBlock.tsx index e48913ce..bc20dc2c 100644 --- a/app/components/chat/CodeBlock.tsx +++ b/app/components/chat/CodeBlock.tsx @@ -53,7 +53,7 @@ export const CodeBlock = memo(
{ if (!gitReady && !historyReady) { @@ -109,9 +112,23 @@ ${file.content} return; } - importRepo(url); + importRepo(url).catch((error) => { + console.error('Error importing repo:', error); + toast.error('Failed to import repository'); + setLoading(false); + window.location.href = '/'; + }); setImported(true); }, [searchParams, historyReady, gitReady, imported]); - return }>{() => }; + return ( + }> + {() => ( + <> + + {loading && } + + )} + + ); } diff --git a/app/components/settings/connections/ConnectionsTab.tsx b/app/components/settings/connections/ConnectionsTab.tsx index 4fe43d96..4b89022e 100644 --- a/app/components/settings/connections/ConnectionsTab.tsx +++ b/app/components/settings/connections/ConnectionsTab.tsx @@ -1,21 +1,93 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { toast } from 'react-toastify'; import Cookies from 'js-cookie'; import { logStore } from '~/lib/stores/logs'; +interface GitHubUserResponse { + login: string; + id: number; + [key: string]: any; // for other properties we don't explicitly need +} + export default function ConnectionsTab() { const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || ''); const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || ''); + const [isConnected, setIsConnected] = useState(false); + const [isVerifying, setIsVerifying] = useState(false); - const handleSaveConnection = () => { - Cookies.set('githubUsername', githubUsername); - Cookies.set('githubToken', githubToken); - logStore.logSystem('GitHub connection settings updated', { - username: githubUsername, - hasToken: !!githubToken, - }); - toast.success('GitHub credentials saved successfully!'); - Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' })); + useEffect(() => { + // Check if credentials exist and verify them + if (githubUsername && githubToken) { + verifyGitHubCredentials(); + } + }, []); + + const verifyGitHubCredentials = async () => { + setIsVerifying(true); + + try { + const response = await fetch('https://api.github.com/user', { + headers: { + Authorization: `Bearer ${githubToken}`, + }, + }); + + if (response.ok) { + const data = (await response.json()) as GitHubUserResponse; + + if (data.login === githubUsername) { + setIsConnected(true); + return true; + } + } + + setIsConnected(false); + + return false; + } catch (error) { + console.error('Error verifying GitHub credentials:', error); + setIsConnected(false); + + return false; + } finally { + setIsVerifying(false); + } + }; + + const handleSaveConnection = async () => { + if (!githubUsername || !githubToken) { + toast.error('Please provide both GitHub username and token'); + return; + } + + setIsVerifying(true); + + const isValid = await verifyGitHubCredentials(); + + if (isValid) { + Cookies.set('githubUsername', githubUsername); + Cookies.set('githubToken', githubToken); + logStore.logSystem('GitHub connection settings updated', { + username: githubUsername, + hasToken: !!githubToken, + }); + toast.success('GitHub credentials verified and saved successfully!'); + Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' })); + setIsConnected(true); + } else { + toast.error('Invalid GitHub credentials. Please check your username and token.'); + } + }; + + const handleDisconnect = () => { + Cookies.remove('githubUsername'); + Cookies.remove('githubToken'); + Cookies.remove('git:github.com'); + setGithubUsername(''); + setGithubToken(''); + setIsConnected(false); + logStore.logSystem('GitHub connection removed'); + toast.success('GitHub connection removed successfully!'); }; return ( @@ -28,7 +100,8 @@ export default function ConnectionsTab() { type="text" value={githubUsername} onChange={(e) => setGithubUsername(e.target.value)} - className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor" + disabled={isVerifying} + className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50" />
@@ -37,17 +110,41 @@ export default function ConnectionsTab() { type="password" value={githubToken} onChange={(e) => setGithubToken(e.target.value)} - className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor" + disabled={isVerifying} + className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50" />
-
- +
+ {!isConnected ? ( + + ) : ( + + )} + {isConnected && ( + +
+ Connected to GitHub + + )}
); diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index d07f8ae2..cf2341b5 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -303,7 +303,7 @@ const checkProviderStatus = async (url: string | null, providerName: string): Pr }; export default function DebugTab() { - const { providers, latestBranch } = useSettings(); + const { providers, isLatestBranch } = useSettings(); const [activeProviders, setActiveProviders] = useState([]); const [updateMessage, setUpdateMessage] = useState(''); const [systemInfo] = useState(getSystemInfo()); @@ -363,7 +363,7 @@ export default function DebugTab() { setIsCheckingUpdate(true); setUpdateMessage('Checking for updates...'); - const branchToCheck = latestBranch ? 'main' : 'stable'; + const branchToCheck = isLatestBranch ? 'main' : 'stable'; console.log(`[Debug] Checking for updates against ${branchToCheck} branch`); const localCommitResponse = await fetch(GITHUB_URLS.commitJson(branchToCheck)); @@ -391,7 +391,7 @@ export default function DebugTab() { } finally { setIsCheckingUpdate(false); } - }, [isCheckingUpdate, latestBranch]); + }, [isCheckingUpdate, isLatestBranch]); const handleCopyToClipboard = useCallback(() => { const debugInfo = { @@ -408,7 +408,7 @@ export default function DebugTab() { })), Version: { hash: versionHash.slice(0, 7), - branch: latestBranch ? 'main' : 'stable', + branch: isLatestBranch ? 'main' : 'stable', }, Timestamp: new Date().toISOString(), }; @@ -416,7 +416,7 @@ export default function DebugTab() { navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => { toast.success('Debug information copied to clipboard!'); }); - }, [activeProviders, systemInfo, latestBranch]); + }, [activeProviders, systemInfo, isLatestBranch]); return (
@@ -523,7 +523,7 @@ export default function DebugTab() {

{versionHash.slice(0, 7)} - (v{versionTag || '0.0.1'}) - {latestBranch ? 'nightly' : 'stable'} + (v{versionTag || '0.0.1'}) - {isLatestBranch ? 'nightly' : 'stable'}

diff --git a/app/components/settings/features/FeaturesTab.tsx b/app/components/settings/features/FeaturesTab.tsx index 410f7ca0..0af3d401 100644 --- a/app/components/settings/features/FeaturesTab.tsx +++ b/app/components/settings/features/FeaturesTab.tsx @@ -1,10 +1,20 @@ import React from 'react'; import { Switch } from '~/components/ui/Switch'; +import { PromptLibrary } from '~/lib/common/prompt-library'; import { useSettings } from '~/lib/hooks/useSettings'; export default function FeaturesTab() { - const { debug, enableDebugMode, isLocalModel, enableLocalModels, enableEventLogs, latestBranch, enableLatestBranch } = - useSettings(); + const { + debug, + enableDebugMode, + isLocalModel, + enableLocalModels, + enableEventLogs, + isLatestBranch, + enableLatestBranch, + promptId, + setPromptId, + } = useSettings(); const handleToggle = (enabled: boolean) => { enableDebugMode(enabled); @@ -27,7 +37,7 @@ export default function FeaturesTab() { Check for updates against the main branch instead of stable

- + @@ -37,10 +47,30 @@ export default function FeaturesTab() {

Disclaimer: Experimental features may be unstable and are subject to change.

+
Experimental Providers
+
+
+ Prompt Library +

+ Choose a prompt from the library to use as the system prompt. +

+
+ +
); diff --git a/app/components/sidebar/HistoryItem.tsx b/app/components/sidebar/HistoryItem.tsx index b228edbb..cd5c8b16 100644 --- a/app/components/sidebar/HistoryItem.tsx +++ b/app/components/sidebar/HistoryItem.tsx @@ -4,6 +4,7 @@ import * as Dialog from '@radix-ui/react-dialog'; import { type ChatHistoryItem } from '~/lib/persistence'; import WithTooltip from '~/components/ui/Tooltip'; import { useEditChatDescription } from '~/lib/hooks'; +import { forwardRef, type ForwardedRef } from 'react'; interface HistoryItemProps { item: ChatHistoryItem; @@ -103,25 +104,31 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History ); } -const ChatActionButton = ({ - toolTipContent, - icon, - className, - onClick, -}: { - toolTipContent: string; - icon: string; - className?: string; - onClick: (event: React.MouseEvent) => void; - btnTitle?: string; -}) => { - return ( - -