diff --git a/.github/workflows/_gcp-deploy.yml b/.github/workflows/_gcp-deploy.yml index cee812a4..15fde89b 100644 --- a/.github/workflows/_gcp-deploy.yml +++ b/.github/workflows/_gcp-deploy.yml @@ -60,6 +60,8 @@ jobs: NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }} NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }} + NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }} + NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }} SENTRY_SMUAT=${{ secrets.SENTRY_SMUAT }} SENTRY_ORG=${{ vars.SENTRY_ORG }} SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 169b2e6a..b9dc3522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Introducing Ask Sourcebot - ask natural langauge about your codebase. Get back comprehensive Markdown responses with inline citations back to the code. Bring your own LLM api key. [#392](https://github.com/sourcebot-dev/sourcebot/pull/392) + ## [4.5.3] - 2025-07-20 ### Changed diff --git a/README.md b/README.md index 40193362..e5940334 100644 --- a/README.md +++ b/README.md @@ -44,16 +44,20 @@ # About -Sourcebot lets you index all your repos and branches across multiple code hosts (GitHub, GitLab, Bitbucket, Gitea, or Gerrit) and search through them using a blazingly fast interface. +Sourcebot is a self-hosted tool that helps you understand your codebase. -https://github.com/user-attachments/assets/ced355f3-967e-4f37-ae6e-74ab8c06b9ec +- **Ask Sourcebot:** Ask questions about your codebase and have Sourcebot provide detailed answers grounded with inline citations. +- **Code search:** Search and navigate across all your repos and branches, no matter where they’re hosted. + +https://github.com/user-attachments/assets/286ad97a-a543-4eef-a2f1-4fa31bea1b32 ## Features - 💻 **One-command deployment**: Get started instantly using Docker on your own machine. -- 🔍 **Multi-repo search**: Index and search through multiple public and private repositories and branches on GitHub, GitLab, Bitbucket, Gitea, or Gerrit. -- ⚡**Lightning fast performance**: Built on top of the powerful [Zoekt](https://github.com/sourcegraph/zoekt) search engine. -- 🎨 **Modern web app**: Enjoy a sleek interface with features like syntax highlighting, light/dark mode, and vim-style navigation +- 🤖 **Bring your own model**: Connect Sourcebot to any of the reasoning models you're already using. +- 🔍 **Multi-repo support**: Index and search through multiple public and private repositories and branches on GitHub, GitLab, Bitbucket, Gitea, or Gerrit. +- ⚡ **Lightning fast performance**: Built on top of the powerful [Zoekt](https://github.com/sourcegraph/zoekt) search engine. +- 🎨 **Modern web app**: Enjoy a sleek interface with features like syntax highlighting, light/dark mode, and vim-style navigation. - 📂 **Full file visualization**: Instantly view the entire file when selecting any search result. You can try out our public hosted demo [here](https://demo.sourcebot.dev)! diff --git a/docs/docs.json b/docs/docs.json index e0834f28..8c304519 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -28,13 +28,21 @@ "group": "Features", "pages": [ { - "group": "Search", + "group": "Code Search", "pages": [ + "docs/features/search/overview", "docs/features/search/syntax-reference", "docs/features/search/multi-branch-indexing", "docs/features/search/search-contexts" ] }, + { + "group": "Ask Sourcebot", + "pages": [ + "docs/features/ask/overview", + "docs/features/ask/add-model-providers" + ] + }, "docs/features/code-navigation", "docs/features/analytics", "docs/features/mcp-server", @@ -51,6 +59,7 @@ { "group": "Configuration", "pages": [ + "docs/configuration/config-file", { "group": "Indexing your code", "pages": [ @@ -66,8 +75,7 @@ "docs/connections/request-new" ] }, - "docs/license-key", - "docs/configuration/environment-variables", + "docs/configuration/language-model-providers", { "group": "Authentication", "pages": [ @@ -78,6 +86,8 @@ "docs/configuration/auth/faq" ] }, + "docs/configuration/environment-variables", + "docs/license-key", "docs/configuration/transactional-emails", "docs/configuration/structured-logging", "docs/configuration/audit-logs" diff --git a/docs/docs/configuration/config-file.mdx b/docs/docs/configuration/config-file.mdx new file mode 100644 index 00000000..c0e89c77 --- /dev/null +++ b/docs/docs/configuration/config-file.mdx @@ -0,0 +1,49 @@ +--- +title: Config File +sidebarTitle: Config file +--- + +When self-hosting Sourcebot, you **must** provide it a config file. This is done by defining a config file in a volume that's mounted to Sourcebot, and providing the path to this +file in the `CONFIG_PATH` environment variable. For example: + +```bash icon="terminal" Passing in a CONFIG_PATH to Sourcebot +docker run \ + -v $(pwd)/config.json:/data/config.json \ + -e CONFIG_PATH=/data/config.json \ + ... \ # other options + ghcr.io/sourcebot-dev/sourcebot:latest +``` + +The config file tells Sourcebot which repos to index, what language models to use, and various other settings as defined in the [schema](#config-file-schema). + +# Config File Schema + +The config file you provide Sourcebot must follow the [schema](https://github.com/sourcebot-dev/sourcebot/blob/main/schemas/v3/index.json). This schema consists of the following properties: + +- [Connections](/docs/connections/overview) (`connections`): Defines a set of connections that tell Sourcebot which repos to index and from where +- [Language Models](/docs/configuration/language-model-providers) (`models`): Defines a set of language model providers for use with [Ask Sourcebot](/docs/features/ask) +- [Settings](#settings) (`settings`): Additional settings to tweak your Sourcebot deployment +- [Search Contexts](/docs/features/search/search-contexts) (`contexts`): Groupings of repos that you can search against + +# Config File Syncing + +Sourcebot syncs the config file on startup, and automatically whenever a change is detected. + +# Settings + +The following are settings that can be provided in your config file to modify Sourcebot's behavior + +| Setting | Type | Default | Minimum | Description / Notes | +|-------------------------------------------|---------|------------|---------|----------------------------------------------------------------------------------------| +| `maxFileSize` | number | 2 MB | 1 | Maximum size (bytes) of a file to index. Files exceeding this are skipped. | +| `maxTrigramCount` | number | 20 000 | 1 | Maximum trigrams per document. Larger files are skipped. | +| `reindexIntervalMs` | number | 1 hour | 1 | Interval at which all repositories are re‑indexed. | +| `resyncConnectionIntervalMs` | number | 24 hours | 1 | Interval for checking connections that need re‑syncing. | +| `resyncConnectionPollingIntervalMs` | number | 1 second | 1 | DB polling rate for connections that need re‑syncing. | +| `reindexRepoPollingIntervalMs` | number | 1 second | 1 | DB polling rate for repos that should be re‑indexed. | +| `maxConnectionSyncJobConcurrency` | number | 8 | 1 | Concurrent connection‑sync jobs. | +| `maxRepoIndexingJobConcurrency` | number | 8 | 1 | Concurrent repo‑indexing jobs. | +| `maxRepoGarbageCollectionJobConcurrency` | number | 8 | 1 | Concurrent repo‑garbage‑collection jobs. | +| `repoGarbageCollectionGracePeriodMs` | number | 10 seconds | 1 | Grace period to avoid deleting shards while loading. | +| `repoIndexTimeoutMs` | number | 2 hours | 1 | Timeout for a single repo‑indexing run. | +| `enablePublicAccess` **(deprecated)** | boolean | false | — | Use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead. | diff --git a/docs/docs/configuration/language-model-providers.mdx b/docs/docs/configuration/language-model-providers.mdx new file mode 100644 index 00000000..8c7e8237 --- /dev/null +++ b/docs/docs/configuration/language-model-providers.mdx @@ -0,0 +1,184 @@ +--- +title: Language Model Providers +sidebarTitle: Language model providers +--- + +To use [Ask Sourcebot](/docs/features/ask) you must define at least one Language Model Provider. These providers are defined within the [config file](/docs/configuration/config-file) you +provide Sourcebot. + + +```json wrap icon="code" Example config with language model provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + // 1. Google Vertex config for Gemini 2.5 Pro + { + "provider": "google-vertex", + "model": "gemini-2.5-pro", + "displayName": "Gemini 2.5 Pro", + "project": "sourcebot", + "credentials": { + "env": "GOOGLE_APPLICATION_CREDENTIALS" + } + }, + // 2. OpenAI config for o3 + { + "provider": "openai", + "model": "o3", + "displayName": "o3", + "token": { + "env": "OPENAI_API_KEY" + } + } + ] +} +``` + +# Supported Providers + +Sourcebot uses the [Vercel AI SDK](https://ai-sdk.dev/docs/introduction), so it can integrate with any provider the SDK supports. If you don't see your provider below please submit +a [feature request](https://github.com/sourcebot-dev/sourcebot/discussions/categories/feature-requests). + +For a detailed description of all the providers, please refer to the [schema](https://github.com/sourcebot-dev/sourcebot/blob/main/schemas/v3/languageModel.json). + +Any parameter defined using `env` will read the value from the corresponding environment variable you provide Sourcebot + +### Amazon Bedrock + +[Vercel AI SDK Amazon Bedrock Docs](https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock) + +```json wrap icon="code" Example config with Amazon Bedrock provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "amazon-bedrock", + "model": "YOUR_MODEL_HERE", + "displayName": "OPTIONAL_DISPLAY_NAME", + "accessKeyId": { + "env": "AWS_ACCESS_KEY_ID" + }, + "accessKeySecret": { + "env": "AWS_SECRET_ACCESS_KEY" + }, + "region": "YOUR_REGION_HERE", // defaults to the AWS_REGION env var if not set + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` + +### Anthropic + +[Vercel AI SDK Anthropic Docs](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic) + +```json wrap icon="code" Example config with Anthropic provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "anthropic", + "model": "YOUR_MODEL_HERE", + "displayName": "OPTIONAL_DISPLAY_NAME", + "token": { + "env": "ANTHROPIC_API_KEY" + }, + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` + +### Google Generative AI + +[Vercel AI SDK Google Generative AI Docs](https://ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai) + +```json wrap icon="code" Example config with Google Generative AI provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "google-generative-ai", + "model": "YOUR_MODEL_HERE", + "displayName": "OPTIONAL_DISPLAY_NAME", + "token": { + "env": "GOOGLE_GENERATIVE_AI_API_KEY" + }, + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` + +### Google Vertex + +If you're using an Anthropic model on Google Vertex, you must define a [Google Vertex Anthropic](#google-vertex-anthropic) provider instead +The `credentials` paramater here expects a **path** to a [credentials](https://console.cloud.google.com/apis/credentials) file. This file **must be in a volume mounted by Sourcebot** for it to be readable. + +[Vercel AI SDK Google Vertex AI Docs](https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex) + +```json wrap icon="code" Example config with Google Vertex provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "google-vertex", + "model": "YOUR_MODEL_HERE", // e.g., "gemini-2.0-flash-exp", "gemini-1.5-pro", "gemini-1.5-flash" + "displayName": "OPTIONAL_DISPLAY_NAME", + "project": "YOUR_PROJECT_ID", // defaults to the GOOGLE_VERTEX_PROJECT env var if not set + "region": "YOUR_REGION_HERE", // defaults to the GOOGLE_VERTEX_REGION env var if not set, e.g., "us-central1", "us-east1", "europe-west1" + "credentials": { + "env": "GOOGLE_APPLICATION_CREDENTIALS" + }, + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` + +### Google Vertex Anthropic + +The `credentials` paramater here expects a **path** to a [credentials](https://console.cloud.google.com/apis/credentials) file. This file **must be in a volume mounted by Sourcebot** for it to be readable. + + +[Vercel AI SDK Google Vertex Anthropic Docs](https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex#google-vertex-anthropic-provider-usage) + +```json wrap icon="code" Example config with Google Vertex Anthropic provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "google-vertex-anthropic", + "model": "YOUR_MODEL_HERE", // e.g., "claude-sonnet-4" + "displayName": "OPTIONAL_DISPLAY_NAME", + "project": "YOUR_PROJECT_ID", // defaults to the GOOGLE_VERTEX_PROJECT env var if not set + "region": "YOUR_REGION_HERE", // defaults to the GOOGLE_VERTEX_REGION env var if not set, e.g., "us-central1", "us-east1", "europe-west1" + "credentials": { + "env": "GOOGLE_APPLICATION_CREDENTIALS" + }, + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` + +### OpenAI + +[Vercel AI SDK OpenAI Docs](https://ai-sdk.dev/providers/ai-sdk-providers/openai) + +```json wrap icon="code" Example config with OpenAI provider +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "models": [ + { + "provider": "openai", + "model": "YOUR_MODEL_HERE", // e.g., "gpt-4.1", "o4-mini", "o3", "o3-deep-research" + "displayName": "OPTIONAL_DISPLAY_NAME", + "token": { + "env": "OPENAI_API_KEY" + }, + "baseUrl": "OPTIONAL_BASE_URL" + } + ] +} +``` \ No newline at end of file diff --git a/docs/docs/connections/bitbucket-cloud.mdx b/docs/docs/connections/bitbucket-cloud.mdx index 0c431ce3..bde7a665 100644 --- a/docs/docs/connections/bitbucket-cloud.mdx +++ b/docs/docs/connections/bitbucket-cloud.mdx @@ -12,6 +12,8 @@ import BitbucketSchema from '/snippets/schemas/v3/bitbucket.schema.mdx' Looking for docs on Bitbucket Data Center? See [this doc](/docs/connections/bitbucket-data-center). +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Examples diff --git a/docs/docs/connections/bitbucket-data-center.mdx b/docs/docs/connections/bitbucket-data-center.mdx index c76ca383..77479e46 100644 --- a/docs/docs/connections/bitbucket-data-center.mdx +++ b/docs/docs/connections/bitbucket-data-center.mdx @@ -12,6 +12,8 @@ import BitbucketSchema from '/snippets/schemas/v3/bitbucket.schema.mdx' Looking for docs on Bitbucket Cloud? See [this doc](/docs/connections/bitbucket-cloud). +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Examples diff --git a/docs/docs/connections/generic-git-host.mdx b/docs/docs/connections/generic-git-host.mdx index df6202b2..c960f248 100644 --- a/docs/docs/connections/generic-git-host.mdx +++ b/docs/docs/connections/generic-git-host.mdx @@ -7,6 +7,8 @@ import GenericGitHost from '/snippets/schemas/v3/genericGitHost.schema.mdx' Sourcebot can sync code from any Git host (by clone url). This is helpful when you want to search code that not in a [supported code host](/docs/connections/overview#supported-code-hosts). +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Getting Started To connect to a Git host, create a new [connection](/docs/connections/overview) with type `git` and specify the clone url in the `url` property. For example: diff --git a/docs/docs/connections/gerrit.mdx b/docs/docs/connections/gerrit.mdx index b7253259..08e72a8d 100644 --- a/docs/docs/connections/gerrit.mdx +++ b/docs/docs/connections/gerrit.mdx @@ -10,6 +10,8 @@ import GerritSchema from '/snippets/schemas/v3/gerrit.schema.mdx' Sourcebot can sync code from self-hosted gerrit instances. +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Connecting to a Gerrit instance To connect to a gerrit instance, provide the `url` property to your config: diff --git a/docs/docs/connections/gitea.mdx b/docs/docs/connections/gitea.mdx index 8869ebff..2318b166 100644 --- a/docs/docs/connections/gitea.mdx +++ b/docs/docs/connections/gitea.mdx @@ -8,6 +8,8 @@ import GiteaSchema from '/snippets/schemas/v3/gitea.schema.mdx' Sourcebot can sync code from Gitea Cloud, and self-hosted. +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Examples diff --git a/docs/docs/connections/github.mdx b/docs/docs/connections/github.mdx index 2f31994e..0abf13b8 100644 --- a/docs/docs/connections/github.mdx +++ b/docs/docs/connections/github.mdx @@ -8,6 +8,8 @@ import GitHubSchema from '/snippets/schemas/v3/github.schema.mdx' Sourcebot can sync code from GitHub.com, GitHub Enterprise Server, and GitHub Enterprise Cloud. +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Examples diff --git a/docs/docs/connections/gitlab.mdx b/docs/docs/connections/gitlab.mdx index 2677a67a..87c9bd03 100644 --- a/docs/docs/connections/gitlab.mdx +++ b/docs/docs/connections/gitlab.mdx @@ -8,6 +8,7 @@ import GitLabSchema from '/snippets/schemas/v3/gitlab.schema.mdx' Sourcebot can sync code from GitLab.com, Self Managed (CE & EE), and Dedicated. +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. ## Examples diff --git a/docs/docs/connections/local-repos.mdx b/docs/docs/connections/local-repos.mdx index 0791b8f0..114c3c8c 100644 --- a/docs/docs/connections/local-repos.mdx +++ b/docs/docs/connections/local-repos.mdx @@ -7,6 +7,8 @@ import GenericGitHost from '/snippets/schemas/v3/genericGitHost.schema.mdx' Sourcebot can sync code from generic git repositories stored in a local directory. This can be helpful in scenarios where you already have a large number of repos already checked out. Local repositories are treated as **read-only**, meaning Sourcebot will **not** `git fetch` new revisions. +If you're not familiar with Sourcebot [connections](/docs/connections/overview), please read that overview first. + ## Getting Started diff --git a/docs/docs/connections/overview.mdx b/docs/docs/connections/overview.mdx index 5165a2aa..15d0a4eb 100644 --- a/docs/docs/connections/overview.mdx +++ b/docs/docs/connections/overview.mdx @@ -6,20 +6,7 @@ sidebarTitle: Overview import SupportedPlatforms from '/snippets/platform-support.mdx' import ConfigSchema from '/snippets/schemas/v3/index.schema.mdx' -To index your code with Sourcebot, you must provide a configuration file. When running Sourcebot, this file must be mounted in a volume that is accessible to the container, with its -path specified in the `CONFIG_PATH` environment variable. For example: - -```bash icon="terminal" Passing in a CONFIG_PATH to Sourcebot -docker run \ - -v $(pwd)/config.json:/data/config.json \ - -e CONFIG_PATH=/data/config.json \ - ... \ # other config - ghcr.io/sourcebot-dev/sourcebot:latest -``` - -## Config Schema - -The configuration file defines a set of **connections**. A connection in Sourcebot represents a link to a code host (such as GitHub, GitLab, Bitbucket, etc.). +A **connection** represents Sourcebot's link to a code host platform (GitHub, GitLab, etc). Connections are defined within the [config file](/docs/configuration/config-file) you provide Sourcebot. Each connection defines how Sourcebot should authenticate and interact with a particular host, and which repositories to sync and index from that host. Connections are uniquely identified by their name. @@ -55,10 +42,11 @@ Each connection defines how Sourcebot should authenticate and interact with a pa Configuration files must conform to the [JSON schema](#schema-reference). -## Config Syncing -Sourcebot performs syncing in the background. Syncing consists of two steps: -1. Fetch the latest changes from `HEAD` (and any [additional branches](/docs/features/search/multi-branch-indexing)) from the code host. -2. Re-indexes the repository. +## Connection Syncing + +When a connection is first discovered, or the `resyncConnectionIntervalMs` [setting](/docs/configuration/config-file#settings) has exceeded, the connection will be synced. This consists of: +1. Fetching the latest changes from `HEAD` (and any [additional branches](/docs/features/search/multi-branch-indexing)) from the code host. +2. Re-indexing the repository. This is processed in a [job queue](/docs/overview#architecture), and is parallelized across multiple worker processes. Jobs will take longer to complete the first time a repository is synced, or when a diff is large. diff --git a/docs/docs/deployment-guide.mdx b/docs/docs/deployment-guide.mdx index 7e785486..d74cf783 100644 --- a/docs/docs/deployment-guide.mdx +++ b/docs/docs/deployment-guide.mdx @@ -72,7 +72,7 @@ The following guide will walk you through the steps to deploy Sourcebot on your - You're all set! You can now start searching - checkout the [syntax guide](/docs/features/search/syntax-reference) to learn more about how to search. + You're all set! If you'd like to setup [Ask Sourcebot](/docs/features/ask/overview), configure a language model [provider](/docs/configuration/language-model-providers) diff --git a/docs/docs/features/ask/add-model-providers.mdx b/docs/docs/features/ask/add-model-providers.mdx new file mode 100644 index 00000000..19f03add --- /dev/null +++ b/docs/docs/features/ask/add-model-providers.mdx @@ -0,0 +1,5 @@ +--- +sidebarTitle: Configure language models +url: /docs/configuration/language-model-providers +title: Configure Language Models +--- \ No newline at end of file diff --git a/docs/docs/features/ask/overview.mdx b/docs/docs/features/ask/overview.mdx new file mode 100644 index 00000000..0e85915c --- /dev/null +++ b/docs/docs/features/ask/overview.mdx @@ -0,0 +1,52 @@ +--- +title: Overview +--- + +Ask Sourcebot gives you the ability to ask complex questions about your codebase in natural language. + +It uses Sourcebot’s existing [code search](/docs/features/search/overview) and [navigation](/docs/features/code-navigation) tools to allow reasoning models to search your code, +follow code nav references, and provide an answer that’s rich with inline citations and navigable code snippets. + + + + Learn how to connect your language model to Sourcebot + + + Learn how to index your repos so you can ask questions about them + + + Learn how to self-host Sourcebot in a few simple steps. + + + Try Ask Sourcebot on our public demo instance. + + + + + +# Why do we need another AI dev tool? + +Existing AI dev tools (Cursor, Claude Code, Copilot) are great at generating code. However, we believe one of the hardest parts of being +a software engineer is **understanding code**. + +In this domain, these tools fall short: +- You can only ask questions about the code you have checked out locally +- You get a wall of text that's difficult to parse, requiring you to go back and forth through different code snippets in the response +- The richness of the explanation is limited by the fact that you're in your IDE + +We built Ask Sourcebot to address these problems. With Ask Sourcebot, you can: +- Ask questions about your teams entire codebase (even on repos you don't have locally) +- Easily parse the response with side-by-side citations and code navigation +- Share answers with your team to spread the knowledge + +Being a web app is less convenient than being in your IDE, but it allows Sourcebot to provide responses in a richer UI that isn't constrained by the IDE. + +We believe this experience of understanding your codebase is superior, and we hope you find it useful. We'd love to know what you think! Feel free to join the discussion on our +[GitHub](https://github.com/sourcebot-dev/sourcebot/discussions). \ No newline at end of file diff --git a/docs/docs/features/search/overview.mdx b/docs/docs/features/search/overview.mdx new file mode 100644 index 00000000..5f703a3c --- /dev/null +++ b/docs/docs/features/search/overview.mdx @@ -0,0 +1,40 @@ +--- +title: Overview +--- + +Search across all your repos/branches across any code host platform. Blazingly fast, and supports regular expressions, repo/language search filters, boolean logic, and more. + + +- **Regex support:** Use regular expressions to find code with precision. +- **Query language:** Scope searches to specific files, repos, languages, symbol definitions and more using a rich [query language](/docs/features/search/syntax-reference). +- **Branch search:** Specify a list of branches to search across ([docs](/docs/features/search/multi-branch-indexing)). +- **Fast & scalable:** Sourcebot uses [trigram indexing](https://en.wikipedia.org/wiki/Trigram_search), allowing it to scale to massive codebases. +- **Syntax highlighting:** Syntax highlighting support for over [100+ languages](https://github.com/sourcebot-dev/sourcebot/blob/57724689303f351c279d37f45b6406f1d5d5d5ab/packages/web/src/lib/codemirrorLanguage.ts#L125). +- **Multi-repository:** Search across all of your repositories in a single search. +- **Search suggestions:** Get search suggestions as you craft your query. +- **Filter panel:** Filter results by repository or by language. + + + + + Learn how to index your repos so you can ask questions about them + + + Learn how to index and search through your branches + + + Learn how to self-host Sourcebot in a few simple steps. + + + Try Sourcebot's code search on our public demo instance. + + + + \ No newline at end of file diff --git a/docs/docs/overview.mdx b/docs/docs/overview.mdx index 35bf8be3..2363323b 100644 --- a/docs/docs/overview.mdx +++ b/docs/docs/overview.mdx @@ -2,7 +2,10 @@ title: "Overview" --- -[Sourcebot]((https://github.com/sourcebot-dev/sourcebot)) is an open-source, self-hosted code search tool. It allows you to search and navigate across millions of lines of code across several code host platforms. +[Sourcebot](https://github.com/sourcebot-dev/sourcebot) is a self-hosted tool that helps you understand your codebase. + +- [Code search](/docs/features/search/overview): Search and navigate across all your repos and branches, no matter where they’re hosted +- [Ask Sourcebot](/docs/features/ask): Ask questions about your codebase and have Sourcebot provide detailed answers grounded with inline citations @@ -30,9 +33,28 @@ title: "Overview" Find an overview of all Sourcebot features below. For details, see the individual documentation pages. -### Fast indexed based search +### Ask Sourcebot -Search across millions of lines of code instantly using Sourcebot's blazingly fast indexed search. Find exactly what you are looking for with regular expressions, search filters, boolean logic, and more. +[Ask Sourcebot](/docs/features/ask) gives you the ability to ask complex questions about your codebase, and have Sourcebot provide detailed answers with inline citations. + + +- **Bring your own model:** [Configure](/docs/configuration/language-model-providers) to any language model you'd like +- **Inline citations:** Every answer Sourcebot provides is grounded with inline citations directly into your codebase +- **Mutli-repo:** Ask questions about any repository you have indexed on Sourcebot + + + + +### Code Search + +Search across all your repos/branches across any code host platform. Blazingly fast, and supports regular expressions, repo/language search filters, boolean logic, and more. - **Regex support:** Use regular expressions to find code with precision. diff --git a/docs/images/ask_sourcebot_low_res.mp4 b/docs/images/ask_sourcebot_low_res.mp4 new file mode 100644 index 00000000..25495568 Binary files /dev/null and b/docs/images/ask_sourcebot_low_res.mp4 differ diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 0b7907ed..7e059ac6 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -1136,6 +1136,886 @@ } }, "additionalProperties": false + }, + "models": { + "type": "array", + "description": "Defines a collection of language models that are available to Sourcebot.", + "items": { + "type": "object", + "title": "LanguageModel", + "definitions": { + "OpenAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AmazonBedrockLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleGenerativeAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexAnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + ] + } } }, "additionalProperties": false diff --git a/docs/snippets/schemas/v3/languageModel.schema.mdx b/docs/snippets/schemas/v3/languageModel.schema.mdx new file mode 100644 index 00000000..bca6b477 --- /dev/null +++ b/docs/snippets/schemas/v3/languageModel.schema.mdx @@ -0,0 +1,879 @@ +{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */} +```json +{ + "type": "object", + "title": "LanguageModel", + "definitions": { + "OpenAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AmazonBedrockLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleGenerativeAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexAnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + ] +} +``` diff --git a/package.json b/package.json index e118780e..59f74fe5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dev:prisma:migrate:dev": "yarn with-env yarn workspace @sourcebot/db prisma:migrate:dev", "dev:prisma:studio": "yarn with-env yarn workspace @sourcebot/db prisma:studio", "dev:prisma:migrate:reset": "yarn with-env yarn workspace @sourcebot/db prisma:migrate:reset", + "dev:prisma:db:push": "yarn with-env yarn workspace @sourcebot/db prisma:db:push", "build:deps": "yarn workspaces foreach -R --from '{@sourcebot/schemas,@sourcebot/error,@sourcebot/crypto,@sourcebot/db,@sourcebot/shared}' run build" }, "devDependencies": { diff --git a/packages/db/prisma/migrations/20250722201612_add_chat_table/migration.sql b/packages/db/prisma/migrations/20250722201612_add_chat_table/migration.sql new file mode 100644 index 00000000..38d64354 --- /dev/null +++ b/packages/db/prisma/migrations/20250722201612_add_chat_table/migration.sql @@ -0,0 +1,23 @@ +-- CreateEnum +CREATE TYPE "ChatVisibility" AS ENUM ('PRIVATE', 'PUBLIC'); + +-- CreateTable +CREATE TABLE "Chat" ( + "id" TEXT NOT NULL, + "name" TEXT, + "createdById" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "orgId" INTEGER NOT NULL, + "visibility" "ChatVisibility" NOT NULL DEFAULT 'PRIVATE', + "isReadonly" BOOLEAN NOT NULL DEFAULT false, + "messages" JSONB NOT NULL, + + CONSTRAINT "Chat_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Chat" ADD CONSTRAINT "Chat_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Chat" ADD CONSTRAINT "Chat_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index bddc8473..20454ddf 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -35,6 +35,11 @@ enum StripeSubscriptionStatus { INACTIVE } +enum ChatVisibility { + PRIVATE + PUBLIC +} + model Repo { id Int @id @default(autoincrement()) name String @@ -183,6 +188,8 @@ model Org { accountRequests AccountRequest[] searchContexts SearchContext[] + + chats Chat[] } enum OrgRole { @@ -276,6 +283,8 @@ model User { apiKeys ApiKey[] + chats Chat[] + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -311,3 +320,23 @@ model VerificationToken { @@unique([identifier, token]) } + +model Chat { + id String @id @default(cuid()) + + name String? + + createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade) + createdById String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) + orgId Int + + visibility ChatVisibility @default(PRIVATE) + isReadonly Boolean @default(false) + + messages Json // This is a JSON array of `Message` types from @ai-sdk/ui-utils. +} \ No newline at end of file diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index 1eafaffe..c44a6b52 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -1135,6 +1135,886 @@ const schema = { } }, "additionalProperties": false + }, + "models": { + "type": "array", + "description": "Defines a collection of language models that are available to Sourcebot.", + "items": { + "type": "object", + "title": "LanguageModel", + "definitions": { + "OpenAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AmazonBedrockLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleGenerativeAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexAnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + ] + } } }, "additionalProperties": false diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index 1390bf4e..65001d80 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -11,6 +11,13 @@ export type ConnectionConfig = | GerritConnectionConfig | BitbucketConnectionConfig | GenericGitHostConnectionConfig; +export type LanguageModel = + | OpenAILanguageModel + | AmazonBedrockLanguageModel + | AnthropicLanguageModel + | GoogleGenerativeAILanguageModel + | GoogleVertexLanguageModel + | GoogleVertexAnthropicLanguageModel; export interface SourcebotConfig { $schema?: string; @@ -27,6 +34,10 @@ export interface SourcebotConfig { connections?: { [k: string]: ConnectionConfig; }; + /** + * Defines a collection of language models that are available to Sourcebot. + */ + models?: LanguageModel[]; } /** * Defines the global settings for Sourcebot. @@ -417,3 +428,243 @@ export interface GenericGitHostConnectionConfig { url: string; revisions?: GitRevisions; } +export interface OpenAILanguageModel { + /** + * OpenAI Configuration + */ + provider: "openai"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface AmazonBedrockLanguageModel { + /** + * Amazon Bedrock Configuration + */ + provider: "amazon-bedrock"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable. + */ + accessKeyId?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable. + */ + accessKeySecret?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * The AWS region. Defaults to the `AWS_REGION` environment variable. + */ + region?: string; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface AnthropicLanguageModel { + /** + * Anthropic Configuration + */ + provider: "anthropic"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleGenerativeAILanguageModel { + /** + * Google Generative AI Configuration + */ + provider: "google-generative-ai"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleVertexLanguageModel { + /** + * Google Vertex AI Configuration + */ + provider: "google-vertex"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. + */ + project?: string; + /** + * The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable. + */ + region?: string; + /** + * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. + */ + credentials?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleVertexAnthropicLanguageModel { + /** + * Google Vertex AI Anthropic Configuration + */ + provider: "google-vertex-anthropic"; + /** + * The name of the Anthropic language model running on Google Vertex. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. + */ + project?: string; + /** + * The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable. + */ + region?: string; + /** + * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. + */ + credentials?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} diff --git a/packages/schemas/src/v3/languageModel.schema.ts b/packages/schemas/src/v3/languageModel.schema.ts new file mode 100644 index 00000000..c944567f --- /dev/null +++ b/packages/schemas/src/v3/languageModel.schema.ts @@ -0,0 +1,878 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! +const schema = { + "type": "object", + "title": "LanguageModel", + "definitions": { + "OpenAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AmazonBedrockLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "AnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleGenerativeAILanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "GoogleVertexAnthropicLanguageModel": { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "provider": { + "const": "openai", + "description": "OpenAI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gpt-4.1", + "o4-mini", + "o3", + "o3-deep-research" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "amazon-bedrock", + "description": "Amazon Bedrock Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "accessKeyId": { + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "accessKeySecret": { + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "region": { + "type": "string", + "description": "The AWS region. Defaults to the `AWS_REGION` environment variable.", + "examples": [ + "us-east-1", + "us-west-2", + "eu-west-1" + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "anthropic", + "description": "Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-generative-ai", + "description": "Google Generative AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model." + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "token": { + "description": "Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex", + "description": "Google Vertex AI Configuration" + }, + "model": { + "type": "string", + "description": "The name of the language model.", + "examples": [ + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "google-vertex-anthropic", + "description": "Google Vertex AI Anthropic Configuration" + }, + "model": { + "type": "string", + "description": "The name of the Anthropic language model running on Google Vertex.", + "examples": [ + "claude-sonnet-4" + ] + }, + "displayName": { + "type": "string", + "description": "Optional display name." + }, + "project": { + "type": "string", + "description": "The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable." + }, + "region": { + "type": "string", + "description": "The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.", + "examples": [ + "us-central1", + "us-east1", + "europe-west1" + ] + }, + "credentials": { + "description": "Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$", + "description": "Optional base URL." + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + } + ] +} as const; +export { schema as languageModelSchema }; \ No newline at end of file diff --git a/packages/schemas/src/v3/languageModel.type.ts b/packages/schemas/src/v3/languageModel.type.ts new file mode 100644 index 00000000..aaacabaf --- /dev/null +++ b/packages/schemas/src/v3/languageModel.type.ts @@ -0,0 +1,250 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! + +export type LanguageModel = + | OpenAILanguageModel + | AmazonBedrockLanguageModel + | AnthropicLanguageModel + | GoogleGenerativeAILanguageModel + | GoogleVertexLanguageModel + | GoogleVertexAnthropicLanguageModel; + +export interface OpenAILanguageModel { + /** + * OpenAI Configuration + */ + provider: "openai"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface AmazonBedrockLanguageModel { + /** + * Amazon Bedrock Configuration + */ + provider: "amazon-bedrock"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable. + */ + accessKeyId?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable. + */ + accessKeySecret?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * The AWS region. Defaults to the `AWS_REGION` environment variable. + */ + region?: string; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface AnthropicLanguageModel { + /** + * Anthropic Configuration + */ + provider: "anthropic"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleGenerativeAILanguageModel { + /** + * Google Generative AI Configuration + */ + provider: "google-generative-ai"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. + */ + token?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleVertexLanguageModel { + /** + * Google Vertex AI Configuration + */ + provider: "google-vertex"; + /** + * The name of the language model. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. + */ + project?: string; + /** + * The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable. + */ + region?: string; + /** + * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. + */ + credentials?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} +export interface GoogleVertexAnthropicLanguageModel { + /** + * Google Vertex AI Anthropic Configuration + */ + provider: "google-vertex-anthropic"; + /** + * The name of the Anthropic language model running on Google Vertex. + */ + model: string; + /** + * Optional display name. + */ + displayName?: string; + /** + * The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. + */ + project?: string; + /** + * The Google Cloud region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable. + */ + region?: string; + /** + * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. + */ + credentials?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; + /** + * Optional base URL. + */ + baseUrl?: string; +} diff --git a/packages/web/package.json b/packages/web/package.json index 62ac9690..2f12ad9a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -12,6 +12,12 @@ "stripe:listen": "stripe listen --forward-to http://localhost:3000/api/stripe" }, "dependencies": { + "@ai-sdk/amazon-bedrock": "3.0.0-beta.7", + "@ai-sdk/anthropic": "2.0.0-beta.8", + "@ai-sdk/google": "2.0.0-beta.11", + "@ai-sdk/google-vertex": "3.0.0-beta.15", + "@ai-sdk/openai": "2.0.0-beta.9", + "@ai-sdk/react": "2.0.0-beta.21", "@auth/prisma-adapter": "^2.7.4", "@codemirror/commands": "^6.6.0", "@codemirror/lang-cpp": "^6.0.2", @@ -43,9 +49,14 @@ "@hookform/resolvers": "^3.9.0", "@iconify/react": "^5.1.0", "@iizukak/codemirror-lang-wgsl": "^0.3.0", + "@opentelemetry/api-logs": "^0.203.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/sdk-logs": "^0.203.0", + "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.5", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.6", @@ -82,14 +93,17 @@ "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.6.0", "@t3-oss/env-nextjs": "^0.12.0", + "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.53.3", "@tanstack/react-table": "^8.20.5", "@tanstack/react-virtual": "^3.10.8", "@uidotdev/usehooks": "^2.4.1", "@uiw/codemirror-themes": "^4.23.6", "@uiw/react-codemirror": "^4.23.0", + "@vercel/otel": "^1.13.0", "@viz-js/lang-dot": "^1.0.4", "@xiechao/codemirror-lang-handlebars": "^1.0.4", + "ai": "5.0.0-beta.21", "ajv": "^8.17.1", "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.0", @@ -117,14 +131,17 @@ "embla-carousel-react": "^8.3.0", "escape-string-regexp": "^5.0.0", "fuse.js": "^7.0.0", - "google-auth-library": "^9.15.1", + "google-auth-library": "^10.1.0", "graphql": "^16.9.0", "http-status-codes": "^2.3.0", "input-otp": "^1.4.2", + "langfuse": "^3.38.4", + "langfuse-vercel": "^3.38.4", "lucide-react": "^0.517.0", "micromatch": "^4.0.8", "next": "14.2.26", "next-auth": "^5.0.0-beta.25", + "next-navigation-guard": "^0.2.0", "next-themes": "^0.3.0", "nodemailer": "^6.10.0", "octokit": "^4.1.3", @@ -139,23 +156,32 @@ "react-hook-form": "^7.53.0", "react-hotkeys-hook": "^4.5.1", "react-icons": "^5.3.0", + "react-markdown": "^10.1.0", "react-resizable-panels": "^2.1.1", "recharts": "^2.15.3", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "scroll-into-view-if-needed": "^3.1.0", "server-only": "^0.0.1", "sharp": "^0.33.5", "simple-git": "^3.27.0", + "slate": "^0.117.0", + "slate-dom": "^0.116.0", + "slate-history": "^0.113.1", + "slate-react": "^0.117.1", "strip-json-comments": "^5.0.1", "stripe": "^17.6.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^3.1.0", "vscode-icons-js": "^11.6.1", - "zod": "^3.24.3", + "zod": "^3.25.74", "zod-to-json-schema": "^3.24.5" }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.74.7", + "@testing-library/react-hooks": "^8.0.1", "@types/micromatch": "^4.0.9", "@types/node": "^20", "@types/nodemailer": "^6.4.17", diff --git a/packages/web/public/anthropic.svg b/packages/web/public/anthropic.svg new file mode 100644 index 00000000..578301de --- /dev/null +++ b/packages/web/public/anthropic.svg @@ -0,0 +1,7 @@ + + + Anthropic Icon Streamline Icon: https://streamlinehq.com + + + + \ No newline at end of file diff --git a/packages/web/public/bedrock.svg b/packages/web/public/bedrock.svg new file mode 100644 index 00000000..a5649399 --- /dev/null +++ b/packages/web/public/bedrock.svg @@ -0,0 +1 @@ +Bedrock \ No newline at end of file diff --git a/packages/web/public/gemini.svg b/packages/web/public/gemini.svg new file mode 100644 index 00000000..3d7ccb42 --- /dev/null +++ b/packages/web/public/gemini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/public/openai.svg b/packages/web/public/openai.svg new file mode 100644 index 00000000..3b4eff96 --- /dev/null +++ b/packages/web/public/openai.svg @@ -0,0 +1,2 @@ + +OpenAI icon \ No newline at end of file diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/rangeHighlightingExtension.ts b/packages/web/src/app/[domain]/browse/[...path]/components/rangeHighlightingExtension.ts index 7f1076c3..cb0fe977 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/rangeHighlightingExtension.ts +++ b/packages/web/src/app/[domain]/browse/[...path]/components/rangeHighlightingExtension.ts @@ -9,7 +9,7 @@ const markDecoration = Decoration.mark({ }); const lineDecoration = Decoration.line({ - attributes: { class: "lineHighlight" }, + attributes: { class: "cm-range-border-radius lineHighlight" }, }); export const rangeHighlightingExtension = (range: BrowseHighlightRange) => StateField.define({ diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx index f2484507..91fba4c1 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx @@ -39,6 +39,7 @@ export const TreePreviewPanel = async ({ path, repoName, revisionName, domain }: webUrl: repoInfoResponse.webUrl, }} pathType="tree" + isFileIconVisible={false} /> diff --git a/packages/web/src/app/[domain]/browse/hooks/useBrowseNavigation.ts b/packages/web/src/app/[domain]/browse/hooks/useBrowseNavigation.ts index 83780153..0d79170e 100644 --- a/packages/web/src/app/[domain]/browse/hooks/useBrowseNavigation.ts +++ b/packages/web/src/app/[domain]/browse/hooks/useBrowseNavigation.ts @@ -1,3 +1,5 @@ +'use client'; + import { useRouter } from "next/navigation"; import { useDomain } from "@/hooks/useDomain"; import { useCallback } from "react"; @@ -13,15 +15,47 @@ export type BrowseHighlightRange = { export const HIGHLIGHT_RANGE_QUERY_PARAM = 'highlightRange'; -interface NavigateToPathOptions { +export interface GetBrowsePathProps { repoName: string; revisionName?: string; path: string; pathType: 'blob' | 'tree'; highlightRange?: BrowseHighlightRange; setBrowseState?: Partial; + domain: string; } +export const getBrowsePath = ({ + repoName, + revisionName = 'HEAD', + path, + pathType, + highlightRange, + setBrowseState, + domain, +}: GetBrowsePathProps) => { + const params = new URLSearchParams(); + + if (highlightRange) { + const { start, end } = highlightRange; + + if ('column' in start && 'column' in end) { + params.set(HIGHLIGHT_RANGE_QUERY_PARAM, `${start.lineNumber}:${start.column},${end.lineNumber}:${end.column}`); + } else { + params.set(HIGHLIGHT_RANGE_QUERY_PARAM, `${start.lineNumber},${end.lineNumber}`); + } + } + + if (setBrowseState) { + params.set(SET_BROWSE_STATE_QUERY_PARAM, JSON.stringify(setBrowseState)); + } + + const encodedPath = encodeURIComponent(path); + const browsePath = `/${domain}/browse/${repoName}@${revisionName}/-/${pathType}/${encodedPath}${params.size > 0 ? `?${params.toString()}` : ''}`; + return browsePath; +} + + export const useBrowseNavigation = () => { const router = useRouter(); const domain = useDomain(); @@ -33,24 +67,18 @@ export const useBrowseNavigation = () => { pathType, highlightRange, setBrowseState, - }: NavigateToPathOptions) => { - const params = new URLSearchParams(); + }: Omit) => { + const browsePath = getBrowsePath({ + repoName, + revisionName, + path, + pathType, + highlightRange, + setBrowseState, + domain, + }); - if (highlightRange) { - const { start, end } = highlightRange; - - if ('column' in start && 'column' in end) { - params.set(HIGHLIGHT_RANGE_QUERY_PARAM, `${start.lineNumber}:${start.column},${end.lineNumber}:${end.column}`); - } else { - params.set(HIGHLIGHT_RANGE_QUERY_PARAM, `${start.lineNumber},${end.lineNumber}`); - } - } - - if (setBrowseState) { - params.set(SET_BROWSE_STATE_QUERY_PARAM, JSON.stringify(setBrowseState)); - } - - router.push(`/${domain}/browse/${repoName}@${revisionName}/-/${pathType}/${path}?${params.toString()}`); + router.push(browsePath); }, [domain, router]); return { diff --git a/packages/web/src/app/[domain]/browse/hooks/useBrowsePath.ts b/packages/web/src/app/[domain]/browse/hooks/useBrowsePath.ts new file mode 100644 index 00000000..fcf29be8 --- /dev/null +++ b/packages/web/src/app/[domain]/browse/hooks/useBrowsePath.ts @@ -0,0 +1,32 @@ +'use client'; + +import { useMemo } from "react"; +import { getBrowsePath, GetBrowsePathProps } from "./useBrowseNavigation"; +import { useDomain } from "@/hooks/useDomain"; + +export const useBrowsePath = ({ + repoName, + revisionName, + path, + pathType, + highlightRange, + setBrowseState, +}: Omit) => { + const domain = useDomain(); + + const browsePath = useMemo(() => { + return getBrowsePath({ + repoName, + revisionName, + path, + pathType, + highlightRange, + setBrowseState, + domain, + }); + }, [repoName, revisionName, path, pathType, highlightRange, setBrowseState, domain]); + + return { + path: browsePath, + } +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/browse/layout.tsx b/packages/web/src/app/[domain]/browse/layout.tsx index f4c15c66..6807a38f 100644 --- a/packages/web/src/app/[domain]/browse/layout.tsx +++ b/packages/web/src/app/[domain]/browse/layout.tsx @@ -6,33 +6,33 @@ import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle import { BrowseStateProvider } from "./browseStateProvider"; import { FileTreePanel } from "@/features/fileTree/components/fileTreePanel"; import { TopBar } from "@/app/[domain]/components/topBar"; -import { Separator } from '@/components/ui/separator'; import { useBrowseParams } from "./hooks/useBrowseParams"; import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; +import { useDomain } from "@/hooks/useDomain"; +import { SearchBar } from "../components/searchBar"; interface LayoutProps { children: React.ReactNode; - params: { - domain: string; - } } export default function Layout({ - children: codePreviewPanel, - params, + children, }: LayoutProps) { const { repoName, revisionName } = useBrowseParams(); + const domain = useDomain(); return (
-
- + - -
+ @@ -53,7 +53,7 @@ export default function Layout({ order={1} id="code-preview-panel" > - {codePreviewPanel} + {children} { + // @note: we are guaranteed to have a chatId because this component will only be + // mounted when on a /chat/[id] route. + const chatId = useChatId()!; + const router = useRouter(); + const searchParams = useSearchParams(); + const [inputMessage, setInputMessage] = useState | undefined>(undefined); + + // Use the last user's last message to determine what repos we should select by default. + const [selectedRepos, setSelectedRepos] = useState(messages.findLast((message) => message.role === "user")?.metadata?.selectedRepos ?? []); + + useEffect(() => { + const setChatState = searchParams.get(SET_CHAT_STATE_QUERY_PARAM); + if (!setChatState) { + return; + } + + try { + const { inputMessage, selectedRepos } = JSON.parse(setChatState) as SetChatStatePayload; + setInputMessage(inputMessage); + setSelectedRepos(selectedRepos); + } catch { + console.error('Invalid message in URL'); + } + + // Remove the message from the URL + const newSearchParams = new URLSearchParams(searchParams.toString()); + newSearchParams.delete(SET_CHAT_STATE_QUERY_PARAM); + router.replace(`?${newSearchParams.toString()}`, { scroll: false }); + }, [searchParams, router]); + + return ( + +
+ +
+
+ ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/[id]/page.tsx b/packages/web/src/app/[domain]/chat/[id]/page.tsx new file mode 100644 index 00000000..67331eb4 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/[id]/page.tsx @@ -0,0 +1,84 @@ +import { getRepos } from '@/actions'; +import { getUserChatHistory, getConfiguredLanguageModelsInfo, getChatInfo } from '@/features/chat/actions'; +import { ServiceErrorException } from '@/lib/serviceError'; +import { isServiceError } from '@/lib/utils'; +import { ChatThreadPanel } from './components/chatThreadPanel'; +import { notFound } from 'next/navigation'; +import { StatusCodes } from 'http-status-codes'; +import { TopBar } from '../../components/topBar'; +import { ChatName } from '../components/chatName'; +import { auth } from '@/auth'; +import { AnimatedResizableHandle } from '@/components/ui/animatedResizableHandle'; +import { ChatSidePanel } from '../components/chatSidePanel'; +import { ResizablePanelGroup } from '@/components/ui/resizable'; + +interface PageProps { + params: { + domain: string; + id: string; + }; +} + +export default async function Page({ params }: PageProps) { + const languageModels = await getConfiguredLanguageModelsInfo(); + const repos = await getRepos(params.domain); + const chatInfo = await getChatInfo({ chatId: params.id }, params.domain); + const session = await auth(); + const chatHistory = session ? await getUserChatHistory(params.domain) : []; + + if (isServiceError(chatHistory)) { + throw new ServiceErrorException(chatHistory); + } + + if (isServiceError(repos)) { + throw new ServiceErrorException(repos); + } + + if (isServiceError(chatInfo)) { + if (chatInfo.statusCode === StatusCodes.NOT_FOUND) { + return notFound(); + } + + throw new ServiceErrorException(chatInfo); + } + + const { messages, name, visibility, isReadonly } = chatInfo; + + const indexedRepos = repos.filter((repo) => repo.indexedAt !== undefined); + + return ( + <> + +
+ / + +
+
+ + + + + + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/components/chatName.tsx b/packages/web/src/app/[domain]/chat/components/chatName.tsx new file mode 100644 index 00000000..b305d3d7 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/components/chatName.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useToast } from "@/components/hooks/use-toast"; +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { updateChatName } from "@/features/chat/actions"; +import { useDomain } from "@/hooks/useDomain"; +import { isServiceError } from "@/lib/utils"; +import { GlobeIcon } from "@radix-ui/react-icons"; +import { ChatVisibility } from "@sourcebot/db"; +import { LockIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useCallback, useState } from "react"; +import { RenameChatDialog } from "./renameChatDialog"; + +interface ChatNameProps { + name: string | null; + visibility: ChatVisibility; + id: string; + isReadonly: boolean; +} + +export const ChatName = ({ name, visibility, id, isReadonly }: ChatNameProps) => { + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const { toast } = useToast(); + const domain = useDomain(); + const router = useRouter(); + + const onRenameChat = useCallback(async (name: string) => { + + const response = await updateChatName({ + chatId: id, + name: name, + }, domain); + + if (isServiceError(response)) { + toast({ + description: `❌ Failed to rename chat. Reason: ${response.message}` + }); + } else { + toast({ + description: `✅ Chat renamed successfully` + }); + router.refresh(); + } + }, [id, domain, toast, router]); + + return ( + <> +
+ + +

{ + setIsRenameDialogOpen(true); + }} + > + {name ?? 'Untitled chat'} +

+
+ + Rename chat + +
+ {visibility && ( + + +
+ + {visibility === ChatVisibility.PUBLIC ? ( + + ) : ( + + )} + {visibility === ChatVisibility.PUBLIC ? (isReadonly ? 'Public (Read-only)' : 'Public') : 'Private'} + +
+
+ + {visibility === ChatVisibility.PUBLIC ? `Anyone with the link can view this chat${!isReadonly ? ' and ask follow-up questions' : ''}.` : 'Only you can view and edit this chat.'} + +
+ )} +
+ + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/components/chatSidePanel.tsx b/packages/web/src/app/[domain]/chat/components/chatSidePanel.tsx new file mode 100644 index 00000000..16e955f8 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/components/chatSidePanel.tsx @@ -0,0 +1,264 @@ +'use client'; + +import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint"; +import { useToast } from "@/components/hooks/use-toast"; +import { Button } from "@/components/ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { ResizablePanel } from "@/components/ui/resizable"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { deleteChat, updateChatName } from "@/features/chat/actions"; +import { useDomain } from "@/hooks/useDomain"; +import { cn, isServiceError } from "@/lib/utils"; +import { CirclePlusIcon, EllipsisIcon, PencilIcon, TrashIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useCallback, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { + GoSidebarCollapse as ExpandIcon, +} from "react-icons/go"; +import { ImperativePanelHandle } from "react-resizable-panels"; +import { useChatId } from "../useChatId"; +import { RenameChatDialog } from "./renameChatDialog"; +import { DeleteChatDialog } from "./deleteChatDialog"; +import Link from "next/link"; + +interface ChatSidePanelProps { + order: number; + chatHistory: { + id: string; + name: string | null; + createdAt: Date; + }[]; + isAuthenticated: boolean; + isCollapsedInitially: boolean; +} + +export const ChatSidePanel = ({ + order, + chatHistory, + isAuthenticated, + isCollapsedInitially, +}: ChatSidePanelProps) => { + const domain = useDomain(); + const [isCollapsed, setIsCollapsed] = useState(isCollapsedInitially); + const sidePanelRef = useRef(null); + const router = useRouter(); + const { toast } = useToast(); + const chatId = useChatId(); + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const [chatIdToRename, setChatIdToRename] = useState(null); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [chatIdToDelete, setChatIdToDelete] = useState(null); + + useHotkeys("mod+b", () => { + if (isCollapsed) { + sidePanelRef.current?.expand(); + } else { + sidePanelRef.current?.collapse(); + } + }, { + enableOnFormTags: true, + enableOnContentEditable: true, + description: "Toggle side panel", + }); + + const onRenameChat = useCallback(async (name: string, chatId: string) => { + if (!chatId) { + return; + } + + const response = await updateChatName({ + chatId, + name: name, + }, domain); + + if (isServiceError(response)) { + toast({ + description: `❌ Failed to rename chat. Reason: ${response.message}` + }); + } else { + toast({ + description: `✅ Chat renamed successfully` + }); + router.refresh(); + } + }, [router, toast, domain]); + + const onDeleteChat = useCallback(async (chatIdToDelete: string) => { + if (!chatIdToDelete) { + return; + } + + const response = await deleteChat({ chatId: chatIdToDelete }, domain); + + if (isServiceError(response)) { + toast({ + description: `❌ Failed to delete chat. Reason: ${response.message}` + }); + } else { + toast({ + description: `✅ Chat deleted successfully` + }); + + // If we just deleted the current chat, navigate to new chat + if (chatIdToDelete === chatId) { + router.push(`/${domain}/chat`); + } + + router.refresh(); + } + }, [chatId, router, toast, domain]); + + return ( + <> + setIsCollapsed(true)} + onExpand={() => setIsCollapsed(false)} + > +
+
+ +
+ +

Recent Chats

+
+ {!isAuthenticated ? ( +
+

+ + Sign in + to access your chat history. +

+
+ ) : chatHistory.length === 0 ? ( +
+

Recent chats will appear here.

+
+ ) : chatHistory.map((chat) => ( +
{ + router.push(`/${domain}/chat/${chat.id}`); + }} + > + {chat.name ?? 'Untitled chat'} + + + + + + { + e.stopPropagation(); + setChatIdToRename(chat.id); + setIsRenameDialogOpen(true); + }} + > + + Rename + + { + e.stopPropagation(); + setChatIdToDelete(chat.id); + setIsDeleteDialogOpen(true); + }} + > + + Delete + + + +
+ ))} +
+
+
+ +
+ {isCollapsed && ( +
+ + + + + + + + Open side panel + + +
+ )} + { + if (chatIdToRename) { + onRenameChat(name, chatIdToRename); + } + }} + currentName={chatHistory?.find((chat) => chat.id === chatIdToRename)?.name ?? ""} + /> + { + if (chatIdToDelete) { + onDeleteChat(chatIdToDelete); + } + }} + /> + + ) +} diff --git a/packages/web/src/app/[domain]/chat/components/deleteChatDialog.tsx b/packages/web/src/app/[domain]/chat/components/deleteChatDialog.tsx new file mode 100644 index 00000000..095f4054 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/components/deleteChatDialog.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { useCallback } from "react"; + +interface DeleteChatDialogProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + onDelete: () => void; +} + +export const DeleteChatDialog = ({ isOpen, onOpenChange, onDelete }: DeleteChatDialogProps) => { + const handleDelete = useCallback(() => { + onDelete(); + onOpenChange(false); + }, [onDelete, onOpenChange]); + + return ( + + + + Delete chat? + + The chat will be deleted and removed from your chat history. This action cannot be undone. + + + + + + + + + ); +}; + diff --git a/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx b/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx new file mode 100644 index 00000000..1c5295b9 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { ResizablePanel } from "@/components/ui/resizable"; +import { ChatBox } from "@/features/chat/components/chatBox"; +import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar"; +import { CustomSlateEditor } from "@/features/chat/customSlateEditor"; +import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; +import { LanguageModelInfo } from "@/features/chat/types"; +import { RepositoryQuery } from "@/lib/types"; +import { useCallback, useState } from "react"; +import { Descendant } from "slate"; +import { useLocalStorage } from "usehooks-ts"; + +interface NewChatPanelProps { + languageModels: LanguageModelInfo[]; + repos: RepositoryQuery[]; + order: number; +} + +export const NewChatPanel = ({ + languageModels, + repos, + order, +}: NewChatPanelProps) => { + const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", []); + const { createNewChatThread, isLoading } = useCreateNewChatThread(); + const [isRepoSelectorOpen, setIsRepoSelectorOpen] = useState(false); + + const onSubmit = useCallback((children: Descendant[]) => { + createNewChatThread(children, selectedRepos); + }, [createNewChatThread, selectedRepos]); + + + return ( + +
+

What can I help you understand?

+
+ + +
+ +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/components/renameChatDialog.tsx b/packages/web/src/app/[domain]/chat/components/renameChatDialog.tsx new file mode 100644 index 00000000..e22277e1 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/components/renameChatDialog.tsx @@ -0,0 +1,106 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +interface RenameChatDialogProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + onRename: (name: string) => void; + currentName: string; +} + +export const RenameChatDialog = ({ isOpen, onOpenChange, onRename, currentName }: RenameChatDialogProps) => { + const formSchema = z.object({ + name: z.string().min(1), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + }, + }); + + useEffect(() => { + form.reset({ + name: currentName, + }); + }, [currentName, form]); + + const onSubmit = (data: z.infer) => { + onRename(data.name); + form.reset(); + onOpenChange(false); + } + + return ( + + + + Rename Chat + + {`Rename "${currentName ?? 'untitled chat'}" to a new name.`} + + +
+ { + event.stopPropagation(); + form.handleSubmit(onSubmit)(event); + }} + > + ( + + + + + + + )} + /> + + + + + + +
+
+ ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/layout.tsx b/packages/web/src/app/[domain]/chat/layout.tsx new file mode 100644 index 00000000..e82ea91f --- /dev/null +++ b/packages/web/src/app/[domain]/chat/layout.tsx @@ -0,0 +1,18 @@ +import { NavigationGuardProvider } from 'next-navigation-guard'; + +interface LayoutProps { + children: React.ReactNode; +} + +export default async function Layout({ children }: LayoutProps) { + + return ( + // @note: we use a navigation guard here since we don't support resuming streams yet. + // @see: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence#resuming-ongoing-streams + +
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/page.tsx b/packages/web/src/app/[domain]/chat/page.tsx new file mode 100644 index 00000000..f15a11cb --- /dev/null +++ b/packages/web/src/app/[domain]/chat/page.tsx @@ -0,0 +1,57 @@ +import { getRepos } from "@/actions"; +import { getUserChatHistory, getConfiguredLanguageModelsInfo } from "@/features/chat/actions"; +import { ServiceErrorException } from "@/lib/serviceError"; +import { isServiceError } from "@/lib/utils"; +import { NewChatPanel } from "./components/newChatPanel"; +import { TopBar } from "../components/topBar"; +import { ResizablePanelGroup } from "@/components/ui/resizable"; +import { ChatSidePanel } from "./components/chatSidePanel"; +import { auth } from "@/auth"; +import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; + +interface PageProps { + params: { + domain: string; + }; +} + +export default async function Page({ params }: PageProps) { + const languageModels = await getConfiguredLanguageModelsInfo(); + const repos = await getRepos(params.domain); + const session = await auth(); + const chatHistory = session ? await getUserChatHistory(params.domain) : []; + + if (isServiceError(chatHistory)) { + throw new ServiceErrorException(chatHistory); + } + + if (isServiceError(repos)) { + throw new ServiceErrorException(repos); + } + + const indexedRepos = repos.filter((repo) => repo.indexedAt !== undefined); + + return ( + <> + + + + + + + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/chat/useChatId.ts b/packages/web/src/app/[domain]/chat/useChatId.ts new file mode 100644 index 00000000..b42aa2a4 --- /dev/null +++ b/packages/web/src/app/[domain]/chat/useChatId.ts @@ -0,0 +1,8 @@ +'use client'; + +import { useParams } from "next/navigation"; + +export const useChatId = (): string | undefined => { + const { id: chatId } = useParams<{ id: string }>(); + return chatId; +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/copyIconButton.tsx b/packages/web/src/app/[domain]/components/copyIconButton.tsx new file mode 100644 index 00000000..4c2b0996 --- /dev/null +++ b/packages/web/src/app/[domain]/components/copyIconButton.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { CheckCircle2, Copy } from "lucide-react"; +import { useCallback, useState } from "react"; + +interface CopyIconButtonProps { + onCopy: () => boolean; + className?: string; +} + +export const CopyIconButton = ({ onCopy, className }: CopyIconButtonProps) => { + const [copied, setCopied] = useState(false); + + const onClick = useCallback(() => { + const success = onCopy(); + if (success) { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }, [onCopy]); + + return ( + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx new file mode 100644 index 00000000..ed463120 --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx @@ -0,0 +1,292 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { ChatBox } from "@/features/chat/components/chatBox"; +import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar"; +import { LanguageModelInfo } from "@/features/chat/types"; +import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; +import { resetEditor } from "@/features/chat/utils"; +import { useDomain } from "@/hooks/useDomain"; +import { RepositoryQuery } from "@/lib/types"; +import { getDisplayTime } from "@/lib/utils"; +import { BrainIcon, FileIcon, LucideIcon, SearchIcon } from "lucide-react"; +import Link from "next/link"; +import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; +import { ReactEditor, useSlate } from "slate-react"; +import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar"; +import { useLocalStorage } from "usehooks-ts"; + +// @todo: we should probably rename this to a different type since it sort-of clashes +// with the Suggestion system we have built into the chat box. +type SuggestionType = "understand" | "find" | "summarize"; + +const suggestionTypes: Record = { + understand: { + icon: BrainIcon, + title: "Understand", + description: "Understand the codebase", + }, + find: { + icon: SearchIcon, + title: "Find", + description: "Find the codebase", + }, + summarize: { + icon: FileIcon, + title: "Summarize", + description: "Summarize the codebase", + }, +} + + +const Highlight = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ) +} + +const suggestions: Record = { + understand: [ + { + queryText: "How does authentication work in this codebase?", + openRepoSelector: true, + }, + { + queryText: "How are API endpoints structured and organized?", + openRepoSelector: true, + }, + { + queryText: "How does the build and deployment process work?", + openRepoSelector: true, + }, + { + queryText: "How is error handling implemented across the application?", + openRepoSelector: true, + }, + ], + find: [ + { + queryText: "Find examples of different logging libraries used throughout the codebase.", + }, + { + queryText: "Find examples of potential security vulnerabilities or authentication issues.", + }, + { + queryText: "Find examples of API endpoints and route handlers.", + } + ], + summarize: [ + { + queryText: "Summarize the purpose of this file @file:", + queryNode: Summarize the purpose of this file @file: + }, + { + queryText: "Summarize the project structure and architecture.", + openRepoSelector: true, + }, + { + queryText: "Provide a quick start guide for ramping up on this codebase.", + openRepoSelector: true, + } + ], +} + +const MAX_RECENT_CHAT_HISTORY_COUNT = 10; + + +interface AgenticSearchProps { + searchModeSelectorProps: SearchModeSelectorProps; + languageModels: LanguageModelInfo[]; + repos: RepositoryQuery[]; + chatHistory: { + id: string; + createdAt: Date; + name: string | null; + }[]; +} + +export const AgenticSearch = ({ + searchModeSelectorProps, + languageModels, + repos, + chatHistory, +}: AgenticSearchProps) => { + const [selectedSuggestionType, _setSelectedSuggestionType] = useState(undefined); + const { createNewChatThread, isLoading } = useCreateNewChatThread(); + const dropdownRef = useRef(null); + const editor = useSlate(); + const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", []); + const domain = useDomain(); + const [isRepoSelectorOpen, setIsRepoSelectorOpen] = useState(false); + + const setSelectedSuggestionType = useCallback((type: SuggestionType | undefined) => { + _setSelectedSuggestionType(type); + if (type) { + ReactEditor.focus(editor); + } + }, [editor, _setSelectedSuggestionType]); + + // Close dropdown when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + !dropdownRef.current?.contains(event.target as Node) + ) { + setSelectedSuggestionType(undefined); + } + } + + document.addEventListener("mousedown", handleClickOutside) + return () => document.removeEventListener("mousedown", handleClickOutside) + }, [setSelectedSuggestionType]); + + return ( +
+
+ { + createNewChatThread(children, selectedRepos); + }} + className="min-h-[50px]" + isRedirecting={isLoading} + languageModels={languageModels} + selectedRepos={selectedRepos} + onRepoSelectorOpenChanged={setIsRepoSelectorOpen} + /> + +
+
+ + +
+ + {selectedSuggestionType && ( +
+

+ {suggestionTypes[selectedSuggestionType].title} +

+ {suggestions[selectedSuggestionType].map(({ queryText, queryNode, openRepoSelector }, index) => ( +
{ + resetEditor(editor); + editor.insertText(queryText); + setSelectedSuggestionType(undefined); + + if (openRepoSelector) { + setIsRepoSelectorOpen(true); + } else { + ReactEditor.focus(editor); + } + }} + > + + {queryNode ?? queryText} +
+ ))} +
+ )} +
+
+
+
+ {Object.entries(suggestionTypes).map(([type, suggestion], index) => ( + { + setSelectedSuggestionType(type as SuggestionType); + }} + /> + ))} +
+
+ {chatHistory.length > 0 && ( +
+ + Recent conversations +
+ {chatHistory + .slice(0, MAX_RECENT_CHAT_HISTORY_COUNT) + .map((chat) => ( + + + {chat.name ?? "Untitled Chat"} + + + {getDisplayTime(chat.createdAt)} + + + ))} +
+ {chatHistory.length > MAX_RECENT_CHAT_HISTORY_COUNT && ( + + View all + + )} +
+ )} +
+ ) +} + + +interface ExampleButtonProps { + Icon: LucideIcon; + title: string; + onClick: () => void; +} + +const ExampleButton = ({ + Icon, + title, + onClick, +}: ExampleButtonProps) => { + return ( + + ) +} diff --git a/packages/web/src/app/[domain]/components/homepage/index.tsx b/packages/web/src/app/[domain]/components/homepage/index.tsx new file mode 100644 index 00000000..972ca34d --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/index.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { SourcebotLogo } from "@/app/components/sourcebotLogo"; +import { LanguageModelInfo } from "@/features/chat/types"; +import { RepositoryQuery } from "@/lib/types"; +import { useHotkeys } from "react-hotkeys-hook"; +import { useLocalStorage } from "usehooks-ts"; +import { AgenticSearch } from "./agenticSearch"; +import { PreciseSearch } from "./preciseSearch"; +import { SearchMode } from "./toolbar"; +import { CustomSlateEditor } from "@/features/chat/customSlateEditor"; + +interface HomepageProps { + initialRepos: RepositoryQuery[]; + languageModels: LanguageModelInfo[]; + chatHistory: { + id: string; + createdAt: Date; + name: string | null; + }[]; +} + + +export const Homepage = ({ + initialRepos, + languageModels, + chatHistory, +}: HomepageProps) => { + const [searchMode, setSearchMode] = useLocalStorage("search-mode", "precise", { initializeWithValue: false }); + const isAgenticSearchEnabled = languageModels.length > 0; + + useHotkeys("mod+i", (e) => { + e.preventDefault(); + setSearchMode("agentic"); + }, { + enableOnFormTags: true, + enableOnContentEditable: true, + description: "Switch to agentic search", + }); + + useHotkeys("mod+p", (e) => { + e.preventDefault(); + setSearchMode("precise"); + }, { + enableOnFormTags: true, + enableOnContentEditable: true, + description: "Switch to precise search", + }); + + return ( +
+
+ +
+ + {searchMode === "precise" ? ( + + ) : ( + + + + )} +
+ ) +} + diff --git a/packages/web/src/app/[domain]/components/homepage/preciseSearch.tsx b/packages/web/src/app/[domain]/components/homepage/preciseSearch.tsx new file mode 100644 index 00000000..d5608940 --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/preciseSearch.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { Separator } from "@/components/ui/separator"; +import { SyntaxReferenceGuideHint } from "../syntaxReferenceGuideHint"; +import { RepositorySnapshot } from "./repositorySnapshot"; +import { RepositoryQuery } from "@/lib/types"; +import { useDomain } from "@/hooks/useDomain"; +import Link from "next/link"; +import { SearchBar } from "../searchBar/searchBar"; +import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar"; + +interface PreciseSearchProps { + initialRepos: RepositoryQuery[]; + searchModeSelectorProps: SearchModeSelectorProps; +} + +export const PreciseSearch = ({ + initialRepos, + searchModeSelectorProps, +}: PreciseSearchProps) => { + const domain = useDomain(); + + return ( + <> +
+ + +
+ +
+
+
+ +
+
+ + How to search +
+ + + test todo (both test and todo) + + + test or todo (either test or todo) + + + {`"exit boot"`} (exact match) + + + TODO case:yes (case sensitive) + + + + + file:README setup (by filename) + + + repo:torvalds/linux test (by repo) + + + lang:typescript (by language) + + + rev:HEAD (by branch or tag) + + + + + file:{`\\.py$`} {`(files that end in ".py")`} + + + sym:main {`(symbols named "main")`} + + + todo -lang:c (negate filter) + + + content:README (search content only) + + +
+ +
+ + ) +} + +const HowToSection = ({ title, children }: { title: string, children: React.ReactNode }) => { + return ( +
+ {title} + {children} +
+ ) + +} + +const Highlight = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ) +} + +const QueryExample = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ) +} + +const QueryExplanation = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ) +} + +const Query = ({ query, domain, children }: { query: string, domain: string, children: React.ReactNode }) => { + return ( + + {children} + + ) +} diff --git a/packages/web/src/app/[domain]/components/repositoryCarousel.tsx b/packages/web/src/app/[domain]/components/homepage/repositoryCarousel.tsx similarity index 100% rename from packages/web/src/app/[domain]/components/repositoryCarousel.tsx rename to packages/web/src/app/[domain]/components/homepage/repositoryCarousel.tsx diff --git a/packages/web/src/app/[domain]/components/repositorySnapshot.tsx b/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx similarity index 98% rename from packages/web/src/app/[domain]/components/repositorySnapshot.tsx rename to packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx index eb18c945..c867fdd0 100644 --- a/packages/web/src/app/[domain]/components/repositorySnapshot.tsx +++ b/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx @@ -71,7 +71,7 @@ export function RepositorySnapshot({ {`Search ${indexedRepos.length} `} {indexedRepos.length > 1 ? 'repositories' : 'repository'} diff --git a/packages/web/src/app/[domain]/components/homepage/toolbar.tsx b/packages/web/src/app/[domain]/components/homepage/toolbar.tsx new file mode 100644 index 00000000..cc9c65e0 --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/toolbar.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { MessageCircleIcon, SearchIcon, TriangleAlert } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; + +export type SearchMode = "precise" | "agentic"; + +const PRECISE_SEARCH_DOCS_URL = "https://docs.sourcebot.dev/docs/features/search/overview"; +// @tood: point this to the actual docs page +const AGENTIC_SEARCH_DOCS_URL = "https://docs.sourcebot.dev/docs/features/ask/overview"; + +export interface SearchModeSelectorProps { + searchMode: SearchMode; + isAgenticSearchEnabled: boolean; + onSearchModeChange: (searchMode: SearchMode) => void; + className?: string; +} + +export const SearchModeSelector = ({ + searchMode, + isAgenticSearchEnabled, + onSearchModeChange, + className, +}: SearchModeSelectorProps) => { + const [focusedSearchMode, setFocusedSearchMode] = useState(searchMode); + + return ( +
+ +
+ ) +} + + diff --git a/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx b/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx index 7791d83f..bb5912ea 100644 --- a/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx +++ b/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx @@ -153,7 +153,6 @@ export const LightweightCodeHighlighter = memo((prop )} { const info = getCodeHostInfoForRepo({ name: repo.name, @@ -59,28 +66,26 @@ export const PathHeader = ({ const { navigateToPath } = useBrowseNavigation(); const { toast } = useToast(); - const [copied, setCopied] = useState(false); - const containerRef = useRef(null); const breadcrumbsRef = useRef(null); const [visibleSegmentCount, setVisibleSegmentCount] = useState(null); - + // Create breadcrumb segments from file path const breadcrumbSegments = useMemo(() => { const pathParts = path.split('/').filter(Boolean); const segments: BreadcrumbSegment[] = []; - + let currentPath = ''; pathParts.forEach((part, index) => { currentPath = currentPath ? `${currentPath}/${part}` : part; const isLastSegment = index === pathParts.length - 1; - + // Calculate highlight range for this segment if it exists let segmentHighlight: { from: number; to: number } | undefined; if (pathHighlightRange) { const segmentStart = path.indexOf(part, currentPath.length - part.length); const segmentEnd = segmentStart + part.length; - + // Check if highlight overlaps with this segment if (pathHighlightRange.from < segmentEnd && pathHighlightRange.to > segmentStart) { segmentHighlight = { @@ -89,7 +94,7 @@ export const PathHeader = ({ }; } } - + segments.push({ name: part, fullPath: currentPath, @@ -97,7 +102,7 @@ export const PathHeader = ({ highlightRange: segmentHighlight }); }); - + return segments; }, [path, pathHighlightRange]); @@ -105,10 +110,10 @@ export const PathHeader = ({ useEffect(() => { const measureSegments = () => { if (!containerRef.current || !breadcrumbsRef.current) return; - + const containerWidth = containerRef.current.offsetWidth; const availableWidth = containerWidth - 175; // Reserve space for copy button and padding - + // Create a temporary element to measure segment widths const tempElement = document.createElement('div'); tempElement.style.position = 'absolute'; @@ -116,17 +121,17 @@ export const PathHeader = ({ tempElement.style.whiteSpace = 'nowrap'; tempElement.className = 'font-mono text-sm'; document.body.appendChild(tempElement); - + let totalWidth = 0; let visibleCount = breadcrumbSegments.length; - + // Start from the end (most important segments) and work backwards for (let i = breadcrumbSegments.length - 1; i >= 0; i--) { const segment = breadcrumbSegments[i]; tempElement.textContent = segment.name; const segmentWidth = tempElement.offsetWidth; const separatorWidth = i < breadcrumbSegments.length - 1 ? 16 : 0; // ChevronRight width - + if (totalWidth + segmentWidth + separatorWidth > availableWidth && i > 0) { // If adding this segment would overflow and it's not the last segment visibleCount = breadcrumbSegments.length - i; @@ -136,21 +141,21 @@ export const PathHeader = ({ } break; } - + totalWidth += segmentWidth + separatorWidth; } - + document.body.removeChild(tempElement); setVisibleSegmentCount(visibleCount); }; measureSegments(); - + const resizeObserver = new ResizeObserver(measureSegments); if (containerRef.current) { resizeObserver.observe(containerRef.current); } - + return () => resizeObserver.disconnect(); }, [breadcrumbSegments]); @@ -170,9 +175,8 @@ export const PathHeader = ({ const onCopyPath = useCallback(() => { navigator.clipboard.writeText(path); - setCopied(true); toast({ description: "✅ Copied to clipboard" }); - setTimeout(() => setCopied(false), 1500); + return true; }, [path, toast]); const onBreadcrumbClick = useCallback((segment: BreadcrumbSegment) => { @@ -204,19 +208,24 @@ export const PathHeader = ({ return (
- {info?.icon ? ( - - {info.codeHostName} - - ) : ( - + {isCodeHostIconVisible && ( + <> + {info?.icon ? ( + + {info.codeHostName} + + ) : ( + + )} + )} +
navigateToPath({ repoName: repo.name, path: '', @@ -269,8 +278,11 @@ export const PathHeader = ({ )} {visibleSegments.map((segment, index) => (
+ {(isFileIconVisible && index === visibleSegments.length - 1) && ( + + )} onBreadcrumbClick(segment)} @@ -283,18 +295,10 @@ export const PathHeader = ({
))}
- +
) diff --git a/packages/web/src/app/[domain]/components/settingsDropdown.tsx b/packages/web/src/app/[domain]/components/settingsDropdown.tsx index 4f3350f6..377d0ef7 100644 --- a/packages/web/src/app/[domain]/components/settingsDropdown.tsx +++ b/packages/web/src/app/[domain]/components/settingsDropdown.tsx @@ -46,7 +46,7 @@ export const SettingsDropdown = ({ const { theme: _theme, setTheme } = useTheme(); const [keymapType, setKeymapType] = useKeymapType(); - const { data: session, update } = useSession(); + const { data: session } = useSession(); const domain = useDomain(); const theme = useMemo(() => { @@ -67,14 +67,7 @@ export const SettingsDropdown = ({ }, [theme]); return ( - // Was hitting a bug with invite code login where the first time the user signs in, the settingsDropdown doesn't have a valid session. To fix this - // we can simply update the session everytime the settingsDropdown is opened. This isn't a super frequent operation and updating the session is low cost, - // so this is a simple solution to the problem. - { - if (isOpen) { - update(); - } - }}> +