Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/db/schema.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const configs = sqliteTable('config', {
aiProvider: text('ai_provider').notNull().default('openai'),
aiModel: text('ai_model').default('gpt-4o'),
aiBaseUrl: text('ai_base_url'),
codeiumApiKey: text('codeium_api_key'),
// Null: unset. Email: subscribed. "dismissed": dismissed the dialog.
subscriptionEmail: text('subscription_email'),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE `config` ADD `codeium_api_key` text;
260 changes: 260 additions & 0 deletions packages/api/drizzle/meta/0011_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
{
"version": "6",
"dialect": "sqlite",
"id": "a22c9bdd-1d54-448d-9c4a-5c3b2d7b7d60",
"prevId": "aeb418fb-06df-4fc2-8afc-f18d95014b46",
"tables": {
"apps": {
"name": "apps",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"language": {
"name": "language",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"external_id": {
"name": "external_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"apps_external_id_unique": {
"name": "apps_external_id_unique",
"columns": [
"external_id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"config": {
"name": "config",
"columns": {
"base_dir": {
"name": "base_dir",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"default_language": {
"name": "default_language",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'typescript'"
},
"openai_api_key": {
"name": "openai_api_key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"anthropic_api_key": {
"name": "anthropic_api_key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"enabled_analytics": {
"name": "enabled_analytics",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"srcbook_installation_id": {
"name": "srcbook_installation_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'k9nek54ld4r5881475jtrr5jns'"
},
"ai_provider": {
"name": "ai_provider",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'openai'"
},
"ai_model": {
"name": "ai_model",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'gpt-4o'"
},
"ai_base_url": {
"name": "ai_base_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"codeium_api_key": {
"name": "codeium_api_key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"subscription_email": {
"name": "subscription_email",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"secrets": {
"name": "secrets",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"secrets_name_unique": {
"name": "secrets_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"secrets_to_sessions": {
"name": "secrets_to_sessions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"session_id": {
"name": "session_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"secret_id": {
"name": "secret_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"secrets_to_sessions_session_id_secret_id_unique": {
"name": "secrets_to_sessions_session_id_secret_id_unique",
"columns": [
"session_id",
"secret_id"
],
"isUnique": true
}
},
"foreignKeys": {
"secrets_to_sessions_secret_id_secrets_id_fk": {
"name": "secrets_to_sessions_secret_id_secrets_id_fk",
"tableFrom": "secrets_to_sessions",
"tableTo": "secrets",
"columnsFrom": [
"secret_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}
7 changes: 7 additions & 0 deletions packages/api/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
"when": 1726808187994,
"tag": "0010_create_apps",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1728410206740,
"tag": "0011_add_codeium_api_key_to_config",
"breakpoints": true
}
]
}
14 changes: 9 additions & 5 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write .",
"preview": "vite preview",
"check-types": "tsc"
"check-types": "tsc",
"generate-codeium-proto-json": "node --eval 'console.log(\"export default\", JSON.stringify(require(\"protobufjs\").loadSync(\"src/lib/ai-autocomplete/language_server.proto\").toJSON(), null, 2));' > src/lib/ai-autocomplete/languageServerProto.ts"
Copy link
Contributor Author

@1egoman 1egoman Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this npm task to take the language_server.proto file and convert it into the json descriptors data that is required by the client.

Note that currently this generated json descriptors file is committed. I could potentially set this up to generate fresh when starting up the dev server / building for release, but as this in practice will rarely / more likely never change, it seemed like setting up more complex build infrastructure here would be less useful than it otherwise would be.

},
"dependencies": {
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/lang-css": "^6.3.0",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lint": "^6.8.1",
"@srcbook/shared": "workspace:^",
"@srcbook/components": "workspace:^",
"@srcbook/shared": "workspace:^",
"@uiw/codemirror-themes": "^4.23.2",
"@uiw/react-codemirror": "^4.23.2",
"clsx": "^2.1.1",
"codemirror": "^6.0.1",
"codemirror-copilot": "^0.0.7",
"long": "^5.2.3",
"lucide-react": "^0.439.0",
"marked": "catalog:",
"protobufjs": "^7.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
Expand All @@ -37,8 +42,7 @@
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"use-debounce": "^10.0.3",
"zod": "catalog:",
"marked": "catalog:"
"zod": "catalog:"
},
"devDependencies": {
"@types/react": "^18.3.5",
Expand Down
32 changes: 31 additions & 1 deletion packages/web/src/components/cells/code.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { inlineCopilot } from 'codemirror-copilot';
import {
CellType,
CodeCellType,
Expand All @@ -19,6 +20,7 @@ import { useCells } from '@srcbook/components/src/components/use-cell';
import { mapCMLocationToTsServer, mapTsServerLocationToCM } from './util';
import { toast } from 'sonner';
import { getFileContent } from '@/lib/server';
import { runCodeiumAiAutocomplete } from '@/lib/ai-autocomplete';
import { tsHover } from '@/components/cells/hover';
import { autocompletion } from '@codemirror/autocomplete';
import { type Diagnostic, linter } from '@codemirror/lint';
Expand Down Expand Up @@ -136,7 +138,7 @@ export default function ControlledCodeCell(props: Props) {
const [prompt, setPrompt] = useState('');
const [newSource, setNewSource] = useState('');
const [fullscreen, setFullscreen] = useState(false);
const { aiEnabled } = useSettings();
const { aiEnabled, codeiumApiKey } = useSettings();

const [isModalOpen, setIsModalOpen] = useState(false);
const [modalContent, setModalContent] = useState('');
Expand Down Expand Up @@ -165,6 +167,7 @@ export default function ControlledCodeCell(props: Props) {
);

const {
cells,
updateCell: updateCellOnClient,
clearOutput,
getTsServerDiagnostics,
Expand Down Expand Up @@ -379,6 +382,33 @@ export default function ControlledCodeCell(props: Props) {
}),
);
}
extensions.push(
inlineCopilot(async (prefix, suffix) => {
let response;
try {
response = await runCodeiumAiAutocomplete(
codeiumApiKey ?? null,
prefix + suffix,
cell.language,
prefix.length,
cells.filter((c): c is CodeCellType => c.type === 'code' && c.id !== cell.id),
);
} catch (err) {
console.error('Error fetching ai autocomplete suggestion:', err);
return '';
}

const completionItems = response.completionItems ?? [];
const mostLikelyCompletionScore = Math.min(
...completionItems.map((item) => item.completion.score),
);
const mostLikelyCompletion = completionItems.find(
(item) => item.completion.score === mostLikelyCompletionScore,
);

return mostLikelyCompletion?.completionParts[0]?.text ?? '';
}, DEBOUNCE_DELAY),
);
extensions.push(
Prec.highest(
EditorView.domEventHandlers({
Expand Down
Loading