mirror of
https://github.com/epistemic-technology/co-intelligence.git
synced 2026-07-22 06:45:17 +00:00
Merge main into coding-style-fixes, resolving conflicts
Resolved conflicts in favor of main's nullish coalescing (??) and AI SDK v6 reasoning-start/reasoning-end streaming approach. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
5666bc51c4
13 changed files with 629 additions and 565 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -135,6 +135,9 @@ dist
|
|||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Test vault chat files
|
||||
test-vault/coi/chat_*.md
|
||||
|
||||
#macos
|
||||
.DS_Store
|
||||
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -107,6 +107,35 @@ I am an idependent software developer. If you find Co-Intelligence to be useful,
|
|||
|
||||
[<img style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi6.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' />](https://ko-fi.com/X8X71G7YSI)
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # Watch mode — rebuilds on file changes
|
||||
npm run build # Production build (also validates TypeScript types)
|
||||
npm test # Run tests with Vitest
|
||||
```
|
||||
|
||||
### Manual Testing
|
||||
|
||||
A test vault is included at `test-vault/` so you can test the plugin in Obsidian without affecting your personal vault. To build and open it:
|
||||
|
||||
```bash
|
||||
./scripts/open-test-vault.sh
|
||||
```
|
||||
|
||||
The first time you open the vault, you'll need to enable community plugins in Settings > Community plugins. After that, the Co-Intelligence AI plugin will be pre-enabled.
|
||||
|
||||
The script symlinks the build output (`dist/`) into the test vault's plugin directory, so rebuilds are picked up automatically. For a live development workflow:
|
||||
|
||||
1. Run `npm run dev` in one terminal
|
||||
2. Run `./scripts/open-test-vault.sh` to open the vault
|
||||
3. After changes rebuild, reload Obsidian (Cmd/Ctrl+P > "Reload app without saving")
|
||||
|
||||
The test vault includes sample folders and system prompts for exercising plugin features.
|
||||
|
||||
## Contributing, Feedback, and Help
|
||||
|
||||
This is an open source project, using the [MIT License](LICENSE). Pull requests for bug fixes or small improvements are welcome. If you want to get involved in a more substantial way, please [Contact me](https://epistemic.technology/contact/).
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -14,18 +14,20 @@
|
|||
"author": "Epistemic Technology",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.37",
|
||||
"@ai-sdk/google": "^3.0.21",
|
||||
"@ai-sdk/openai": "^3.0.25",
|
||||
"@ai-sdk/perplexity": "^3.0.17",
|
||||
"ai": "^6.0.73",
|
||||
"@ai-sdk/anthropic": "^3.0.41",
|
||||
"@ai-sdk/google": "^3.0.24",
|
||||
"@ai-sdk/openai": "^3.0.26",
|
||||
"@ai-sdk/perplexity": "^3.0.18",
|
||||
"ai": "^6.0.79",
|
||||
"monkey-around": "^2.3.0",
|
||||
"solid-js": "^1.9.11",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vitest>vite": "^6.4.1"
|
||||
"vitest>vite": "^6.4.1",
|
||||
"seroval": "^1.4.1",
|
||||
"jsondiffpatch": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
1036
pnpm-lock.yaml
1036
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
48
scripts/open-test-vault.sh
Executable file
48
scripts/open-test-vault.sh
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
VAULT_DIR="$PROJECT_DIR/test-vault"
|
||||
PLUGIN_DIR="$VAULT_DIR/.obsidian/plugins/co-intelligence"
|
||||
OBSIDIAN_CONFIG="$HOME/Library/Application Support/obsidian/obsidian.json"
|
||||
|
||||
# Create plugin directory and symlinks
|
||||
mkdir -p "$PLUGIN_DIR"
|
||||
ln -sf "$PROJECT_DIR/dist/main.js" "$PLUGIN_DIR/main.js"
|
||||
ln -sf "$PROJECT_DIR/dist/styles.css" "$PLUGIN_DIR/styles.css"
|
||||
ln -sf "$PROJECT_DIR/manifest.json" "$PLUGIN_DIR/manifest.json"
|
||||
|
||||
# Build latest
|
||||
echo "Building plugin..."
|
||||
npm --prefix "$PROJECT_DIR" run build
|
||||
|
||||
# Register the vault with Obsidian if not already registered
|
||||
if [ -f "$OBSIDIAN_CONFIG" ]; then
|
||||
if ! python3 -c "
|
||||
import json, sys
|
||||
with open('$OBSIDIAN_CONFIG') as f:
|
||||
config = json.load(f)
|
||||
sys.exit(0 if any(v.get('path') == '$VAULT_DIR' for v in config.get('vaults', {}).values()) else 1)
|
||||
"; then
|
||||
echo "Registering test vault with Obsidian..."
|
||||
python3 -c "
|
||||
import json, hashlib, time
|
||||
config_path = '$OBSIDIAN_CONFIG'
|
||||
vault_path = '$VAULT_DIR'
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
vault_id = hashlib.md5(vault_path.encode()).hexdigest()[:16]
|
||||
config.setdefault('vaults', {})[vault_id] = {'path': vault_path, 'ts': int(time.time() * 1000)}
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(config, f)
|
||||
"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Open the test vault in Obsidian
|
||||
echo "Opening test vault in Obsidian..."
|
||||
ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$VAULT_DIR'))")
|
||||
open "obsidian://open?path=$ENCODED_PATH"
|
||||
|
||||
echo "Done. If this is the first time, enable the plugin in Settings > Community plugins."
|
||||
|
|
@ -14,7 +14,7 @@ export const BotMessage: Component<BotMessageProps> = ({
|
|||
}: BotMessageProps) => {
|
||||
const file = useContext(FileContext);
|
||||
const filePath = file?.path || "";
|
||||
const content = (message.content as string) || "";
|
||||
const content = (message.content as string) ?? "";
|
||||
// Perplexity models use <think> tags to indicate the reasoning section.
|
||||
// OpenAI models use chunk types instead. These are translated into <think>
|
||||
// tags by handleSendMessage in ChatInterface.tsx
|
||||
|
|
|
|||
|
|
@ -176,34 +176,31 @@ export const ChatInterface = ({
|
|||
console.error("Error:", chunk.error);
|
||||
new Notice("Unknown error occurred. See console log for details.");
|
||||
}
|
||||
if (
|
||||
chunk.type !== "text-delta" &&
|
||||
chunk.type !== "reasoning-delta"
|
||||
) {
|
||||
if (chunk.type === "reasoning-start") {
|
||||
doingReasoning = true;
|
||||
accumulatedContent += "<think>";
|
||||
continue;
|
||||
}
|
||||
if (!chunk.text) {
|
||||
if (chunk.type === "reasoning-end") {
|
||||
doingReasoning = false;
|
||||
accumulatedContent += "</think>";
|
||||
continue;
|
||||
}
|
||||
if (chunk.type !== "text-delta" && chunk.type !== "reasoning-delta") {
|
||||
continue;
|
||||
}
|
||||
const text = chunk.text;
|
||||
if (isFirstChunk) {
|
||||
const assistantMessage: ModelChatMessage = {
|
||||
role: "assistant",
|
||||
content: chunk.text,
|
||||
content: text,
|
||||
};
|
||||
setMessages([...messages(), assistantMessage]);
|
||||
if (chunk.type === "reasoning-delta") {
|
||||
accumulatedContent = "<think>";
|
||||
doingReasoning = true;
|
||||
}
|
||||
accumulatedContent += chunk.text;
|
||||
accumulatedContent += text;
|
||||
isFirstChunk = false;
|
||||
setIsProcessing(false);
|
||||
} else {
|
||||
if (doingReasoning && chunk.type !== "reasoning-delta") {
|
||||
accumulatedContent += "</think>";
|
||||
doingReasoning = false;
|
||||
}
|
||||
accumulatedContent += chunk.text;
|
||||
accumulatedContent += text;
|
||||
setMessages((prevMessages) => {
|
||||
const updatedMessages = [...prevMessages];
|
||||
updatedMessages[updatedMessages.length - 1] = {
|
||||
|
|
@ -252,7 +249,7 @@ export const ChatInterface = ({
|
|||
|
||||
// Replace source reference numbers [n] with [n+offset]
|
||||
const offset = lastSourceLinkNumber();
|
||||
const updatedContent = (lastMessage.content as string).replace(
|
||||
const updatedContent = ((lastMessage.content as string) ?? "").replace(
|
||||
/\[(\d+)\]/g,
|
||||
(match, num) => {
|
||||
const source = uniqueNewSources[parseInt(num) - 1];
|
||||
|
|
@ -280,7 +277,9 @@ export const ChatInterface = ({
|
|||
if (!hasProcessedSources) {
|
||||
const currentMessage = messages()[messages().length - 1];
|
||||
const newLinks =
|
||||
(currentMessage.content as string).match(/\[(.*?)\]\((.*?)\)/g) || [];
|
||||
((currentMessage.content as string) ?? "").match(
|
||||
/\[(.*?)\]\((.*?)\)/g,
|
||||
) || [];
|
||||
newLinks.forEach((link) => {
|
||||
const [text, url] = link.slice(1, -1).split("](");
|
||||
const existingSource = sources().find((source) => source.url === url);
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ export function serializeCoiNoteContent(
|
|||
): string {
|
||||
const serializedMessages = messages
|
||||
.map(({ role, content }) => {
|
||||
const contentStr = (content as string) || "";
|
||||
const contentStr = (content as string) ?? "";
|
||||
|
||||
// Find the highest level header in the content
|
||||
const headerMatches = contentStr.match(/^#+/gm);
|
||||
|
|
|
|||
7
test-vault/.gitignore
vendored
Normal file
7
test-vault/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Obsidian generates config files when the vault is opened.
|
||||
# Only community-plugins.json is checked in (to pre-enable the plugin).
|
||||
.obsidian/*
|
||||
!.obsidian/community-plugins.json
|
||||
|
||||
# Plugin symlinks are machine-specific; the script recreates them.
|
||||
.obsidian/plugins/
|
||||
3
test-vault/.obsidian/community-plugins.json
vendored
Normal file
3
test-vault/.obsidian/community-plugins.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
"co-intelligence"
|
||||
]
|
||||
1
test-vault/coi/prompts/Code reviewer.md
Normal file
1
test-vault/coi/prompts/Code reviewer.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
You are a code reviewer. Review code for bugs, style issues, and potential improvements. Be constructive and specific.
|
||||
1
test-vault/coi/prompts/Helpful assistant.md
Normal file
1
test-vault/coi/prompts/Helpful assistant.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
You are a helpful assistant. Be concise and clear in your responses.
|
||||
13
test-vault/notes/Sample note.md
Normal file
13
test-vault/notes/Sample note.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
This is a sample note for testing context linking in Co-Intelligence AI.
|
||||
|
||||
## Key points
|
||||
|
||||
- First point with **bold** and *italic* formatting
|
||||
- Second point with a `code snippet`
|
||||
- Third point with a [[link to nowhere]]
|
||||
|
||||
```javascript
|
||||
function hello() {
|
||||
console.log("Hello from a code block");
|
||||
}
|
||||
```
|
||||
Loading…
Reference in a new issue