@base44-preview/cli 0.0.26-pr.172.6febf45 → 0.0.26-pr.174.9ce53d2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates/chrome-extension/.nvmrc +1 -0
- package/dist/templates/chrome-extension/README.md +90 -0
- package/dist/templates/chrome-extension/base44/agents/search_agent.jsonc +13 -0
- package/dist/templates/chrome-extension/base44/app.jsonc.ejs +9 -0
- package/dist/templates/chrome-extension/base44/config.jsonc.ejs +8 -0
- package/dist/templates/chrome-extension/base44/entities/bookmark.jsonc +41 -0
- package/dist/templates/chrome-extension/gitignore.ejs +25 -0
- package/dist/templates/chrome-extension/package.json +30 -0
- package/dist/templates/chrome-extension/postcss.config.js +6 -0
- package/dist/templates/chrome-extension/public/icon/README.md +12 -0
- package/dist/templates/chrome-extension/public/manifest.json +25 -0
- package/dist/templates/chrome-extension/tailwind.config.js +8 -0
- package/dist/templates/chrome-extension/tsconfig.json +31 -0
- package/dist/templates/chrome-extension/tsconfig.node.json +10 -0
- package/dist/templates/chrome-extension/wxt-src/components/BookmarkCard.tsx +83 -0
- package/dist/templates/chrome-extension/wxt-src/components/Button.tsx +36 -0
- package/dist/templates/chrome-extension/wxt-src/components/Input.tsx +12 -0
- package/dist/templates/chrome-extension/wxt-src/entrypoints/background.ts +19 -0
- package/dist/templates/chrome-extension/wxt-src/entrypoints/popup/App.tsx +186 -0
- package/dist/templates/chrome-extension/wxt-src/entrypoints/popup/index.html +12 -0
- package/dist/templates/chrome-extension/wxt-src/entrypoints/popup/main.tsx +10 -0
- package/dist/templates/chrome-extension/wxt-src/entrypoints/popup/style.css +21 -0
- package/dist/templates/chrome-extension/wxt-src/lib/base44Client.ts.ejs +5 -0
- package/dist/templates/chrome-extension/wxt.config.ts +7 -0
- package/dist/templates/templates.json +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Chrome Bookmarks Extension
|
|
2
|
+
|
|
3
|
+
A Chrome extension for bookmarking websites with AI-powered search, built with WXT framework and Base44 backend.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Save Bookmarks**: Save any website with a single click
|
|
8
|
+
- **View Bookmarks**: Browse your saved bookmarks in the popup
|
|
9
|
+
- **AI Search**: Search through your bookmarks using natural language
|
|
10
|
+
- **Base44 Backend**: Powered by Base44 for data storage and AI capabilities
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
base44/ # Backend configuration
|
|
16
|
+
├── config.jsonc # Project settings
|
|
17
|
+
├── entities/ # Data schemas
|
|
18
|
+
│ └── bookmark.jsonc # Bookmark entity
|
|
19
|
+
└── agents/ # AI agents
|
|
20
|
+
└── search_agent.jsonc # Bookmark search agent
|
|
21
|
+
|
|
22
|
+
wxt-src/ # Extension source code
|
|
23
|
+
├── entrypoints/
|
|
24
|
+
│ ├── background/ # Background service worker
|
|
25
|
+
│ ├── content/ # Content scripts
|
|
26
|
+
│ └── popup/ # Extension popup UI
|
|
27
|
+
└── components/ # Shared React components
|
|
28
|
+
|
|
29
|
+
public/ # Static assets
|
|
30
|
+
└── icon/ # Extension icons
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install
|
|
37
|
+
npm run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This will start WXT in development mode with hot-reload enabled.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
| Command | Description |
|
|
45
|
+
|---------|-------------|
|
|
46
|
+
| `npm run dev` | Start development mode (Chrome) |
|
|
47
|
+
| `npm run dev:firefox` | Start development mode (Firefox) |
|
|
48
|
+
| `npm run build` | Build for production (Chrome) |
|
|
49
|
+
| `npm run build:firefox` | Build for production (Firefox) |
|
|
50
|
+
| `npm run zip` | Create distribution zip (Chrome) |
|
|
51
|
+
| `npm run zip:firefox` | Create distribution zip (Firefox) |
|
|
52
|
+
|
|
53
|
+
## Base44 Setup
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
base44 login # Authenticate
|
|
57
|
+
base44 entities push # Push entity schemas
|
|
58
|
+
base44 agents push # Push AI agents
|
|
59
|
+
base44 deploy # Deploy backend
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Loading the Extension
|
|
63
|
+
|
|
64
|
+
### Chrome
|
|
65
|
+
1. Run `npm run build` to create the production build
|
|
66
|
+
2. Open Chrome and navigate to `chrome://extensions/`
|
|
67
|
+
3. Enable "Developer mode"
|
|
68
|
+
4. Click "Load unpacked"
|
|
69
|
+
5. Select the `.output/chrome-mv3` directory
|
|
70
|
+
|
|
71
|
+
### Firefox
|
|
72
|
+
1. Run `npm run build:firefox`
|
|
73
|
+
2. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`
|
|
74
|
+
3. Click "Load Temporary Add-on"
|
|
75
|
+
4. Select any file in the `.output/firefox-mv3` directory
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
1. **Save a Bookmark**: Click the extension icon and press "Save Current Page"
|
|
80
|
+
2. **View Bookmarks**: Open the popup to see your saved bookmarks
|
|
81
|
+
3. **Search**: Use the search bar with natural language queries like "show me articles about React"
|
|
82
|
+
4. **Delete**: Click the trash icon to remove a bookmark
|
|
83
|
+
|
|
84
|
+
## Architecture
|
|
85
|
+
|
|
86
|
+
- **WXT Framework**: Modern web extension development framework with Vite
|
|
87
|
+
- **React**: UI components with hooks
|
|
88
|
+
- **Base44 SDK**: Backend integration for data storage
|
|
89
|
+
- **AI Agents**: Natural language search powered by Base44 agents
|
|
90
|
+
- **Tailwind CSS**: Utility-first styling
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Bookmark Search Agent",
|
|
3
|
+
"description": "AI agent that helps users search through their bookmarks using natural language queries",
|
|
4
|
+
"instructions": "You are a helpful assistant that searches through a user's bookmarks. When a user asks a question or describes what they're looking for, search through their bookmarks and return the most relevant results. You can understand natural language queries like 'articles about React', 'bookmarks from last week', or 'tutorials I saved'.",
|
|
5
|
+
"tools": [
|
|
6
|
+
{
|
|
7
|
+
"type": "query",
|
|
8
|
+
"entity": "Bookmark",
|
|
9
|
+
"description": "Search and filter bookmarks"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"model": "gpt-4-turbo-preview"
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Base44 Project Configuration
|
|
2
|
+
// JSONC enables inline documentation and discoverability directly in config files.
|
|
3
|
+
// Chrome extension template with WXT framework and Base44 backend.
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"name": "<%= name %>"<% if (description) { %>,
|
|
7
|
+
"description": "<%= description %>"<% } %>
|
|
8
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Bookmark",
|
|
3
|
+
"fields": {
|
|
4
|
+
"url": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"required": true,
|
|
7
|
+
"description": "The URL of the bookmarked page"
|
|
8
|
+
},
|
|
9
|
+
"title": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"required": true,
|
|
12
|
+
"description": "The title of the bookmarked page"
|
|
13
|
+
},
|
|
14
|
+
"description": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Optional description or notes about the bookmark"
|
|
17
|
+
},
|
|
18
|
+
"favicon": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "URL to the site's favicon"
|
|
21
|
+
},
|
|
22
|
+
"tags": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"description": "Tags for categorizing bookmarks"
|
|
28
|
+
},
|
|
29
|
+
"createdAt": {
|
|
30
|
+
"type": "timestamp",
|
|
31
|
+
"autoCreate": true,
|
|
32
|
+
"description": "Timestamp when the bookmark was created"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"indexes": [
|
|
36
|
+
{
|
|
37
|
+
"fields": ["createdAt"],
|
|
38
|
+
"direction": "desc"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
outputFileName: .gitignore
|
|
3
|
+
---
|
|
4
|
+
# Dependencies
|
|
5
|
+
node_modules/
|
|
6
|
+
|
|
7
|
+
# Build outputs
|
|
8
|
+
.output/
|
|
9
|
+
.wxt/
|
|
10
|
+
dist/
|
|
11
|
+
|
|
12
|
+
# Environment
|
|
13
|
+
.env
|
|
14
|
+
.env.local
|
|
15
|
+
.app.jsonc
|
|
16
|
+
|
|
17
|
+
# OS
|
|
18
|
+
.DS_Store
|
|
19
|
+
Thumbs.db
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chrome-bookmarks-extension",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "wxt",
|
|
8
|
+
"dev:firefox": "wxt -b firefox",
|
|
9
|
+
"build": "wxt build",
|
|
10
|
+
"build:firefox": "wxt build -b firefox",
|
|
11
|
+
"zip": "wxt zip",
|
|
12
|
+
"zip:firefox": "wxt zip -b firefox",
|
|
13
|
+
"postinstall": "wxt prepare"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@base44/sdk": "^0.8.3",
|
|
17
|
+
"lucide-react": "^0.475.0",
|
|
18
|
+
"react": "^18.2.0",
|
|
19
|
+
"react-dom": "^18.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^18.2.79",
|
|
23
|
+
"@types/react-dom": "^18.2.25",
|
|
24
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
25
|
+
"autoprefixer": "^10.4.20",
|
|
26
|
+
"postcss": "^8.5.3",
|
|
27
|
+
"tailwindcss": "^3.4.17",
|
|
28
|
+
"wxt": "^0.19.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Extension Icons
|
|
2
|
+
|
|
3
|
+
Place your extension icons here:
|
|
4
|
+
|
|
5
|
+
- `16.png` - 16x16 pixels (browser toolbar)
|
|
6
|
+
- `32.png` - 32x32 pixels (Windows)
|
|
7
|
+
- `48.png` - 48x48 pixels (extensions page)
|
|
8
|
+
- `128.png` - 128x128 pixels (Chrome Web Store)
|
|
9
|
+
|
|
10
|
+
You can use tools like [Figma](https://figma.com), [Canva](https://canva.com), or any image editor to create these icons.
|
|
11
|
+
|
|
12
|
+
For now, WXT will generate default icons if these are missing.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Bookmark Manager",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Save and search bookmarks with AI assistance",
|
|
6
|
+
"permissions": ["tabs", "storage"],
|
|
7
|
+
"action": {
|
|
8
|
+
"default_popup": "popup.html",
|
|
9
|
+
"default_icon": {
|
|
10
|
+
"16": "icon/16.png",
|
|
11
|
+
"32": "icon/32.png",
|
|
12
|
+
"48": "icon/48.png",
|
|
13
|
+
"128": "icon/128.png"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"icons": {
|
|
17
|
+
"16": "icon/16.png",
|
|
18
|
+
"32": "icon/32.png",
|
|
19
|
+
"48": "icon/48.png",
|
|
20
|
+
"128": "icon/128.png"
|
|
21
|
+
},
|
|
22
|
+
"background": {
|
|
23
|
+
"service_worker": "background.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
|
|
23
|
+
/* Path aliases */
|
|
24
|
+
"baseUrl": ".",
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": ["./wxt-src/*"]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"include": ["wxt-src"],
|
|
30
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
31
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ExternalLink, Trash2 } from 'lucide-react';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
|
|
5
|
+
interface Bookmark {
|
|
6
|
+
id: string;
|
|
7
|
+
url: string;
|
|
8
|
+
title: string;
|
|
9
|
+
favicon?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
createdAt?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BookmarkCardProps {
|
|
15
|
+
bookmark: Bookmark;
|
|
16
|
+
onDelete: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const BookmarkCard: React.FC<BookmarkCardProps> = ({ bookmark, onDelete }) => {
|
|
20
|
+
const handleClick = () => {
|
|
21
|
+
browser.tabs.create({ url: bookmark.url });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="group flex items-start gap-3 p-3 bg-white rounded-lg border border-slate-200 hover:shadow-md transition-all duration-200">
|
|
26
|
+
{/* Favicon */}
|
|
27
|
+
{bookmark.favicon ? (
|
|
28
|
+
<img
|
|
29
|
+
src={bookmark.favicon}
|
|
30
|
+
alt=""
|
|
31
|
+
className="w-5 h-5 mt-0.5 rounded"
|
|
32
|
+
onError={(e) => {
|
|
33
|
+
(e.target as HTMLImageElement).style.display = 'none';
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
) : (
|
|
37
|
+
<div className="w-5 h-5 mt-0.5 bg-slate-200 rounded" />
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
{/* Content */}
|
|
41
|
+
<div className="flex-1 min-w-0">
|
|
42
|
+
<button
|
|
43
|
+
onClick={handleClick}
|
|
44
|
+
className="text-left w-full group/link"
|
|
45
|
+
>
|
|
46
|
+
<h3 className="text-sm font-medium text-slate-900 group-hover/link:text-blue-600 transition-colors line-clamp-1">
|
|
47
|
+
{bookmark.title}
|
|
48
|
+
</h3>
|
|
49
|
+
<p className="text-xs text-slate-500 line-clamp-1 mt-0.5">
|
|
50
|
+
{new URL(bookmark.url).hostname}
|
|
51
|
+
</p>
|
|
52
|
+
</button>
|
|
53
|
+
{bookmark.description && (
|
|
54
|
+
<p className="text-xs text-slate-600 mt-1 line-clamp-2">
|
|
55
|
+
{bookmark.description}
|
|
56
|
+
</p>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Actions */}
|
|
61
|
+
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
62
|
+
<Button
|
|
63
|
+
variant="ghost"
|
|
64
|
+
size="icon"
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
className="h-7 w-7 text-slate-400 hover:text-blue-600"
|
|
67
|
+
title="Open in new tab"
|
|
68
|
+
>
|
|
69
|
+
<ExternalLink className="w-3.5 h-3.5" />
|
|
70
|
+
</Button>
|
|
71
|
+
<Button
|
|
72
|
+
variant="ghost"
|
|
73
|
+
size="icon"
|
|
74
|
+
onClick={onDelete}
|
|
75
|
+
className="h-7 w-7 text-slate-400 hover:text-red-600 hover:bg-red-50"
|
|
76
|
+
title="Delete bookmark"
|
|
77
|
+
>
|
|
78
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
79
|
+
</Button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
|
+
variant?: 'default' | 'ghost';
|
|
5
|
+
size?: 'default' | 'sm' | 'icon';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Button: React.FC<ButtonProps> = ({
|
|
9
|
+
children,
|
|
10
|
+
className = '',
|
|
11
|
+
variant = 'default',
|
|
12
|
+
size = 'default',
|
|
13
|
+
...props
|
|
14
|
+
}) => {
|
|
15
|
+
const baseStyles = 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 disabled:pointer-events-none disabled:opacity-50';
|
|
16
|
+
|
|
17
|
+
const variants = {
|
|
18
|
+
default: 'bg-slate-900 text-white hover:bg-slate-800',
|
|
19
|
+
ghost: 'hover:bg-slate-100 text-slate-700',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const sizes = {
|
|
23
|
+
default: 'h-10 px-4 py-2',
|
|
24
|
+
sm: 'h-8 px-3 text-sm',
|
|
25
|
+
icon: 'h-9 w-9',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
4
|
+
|
|
5
|
+
export const Input: React.FC<InputProps> = ({ className = '', ...props }) => {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
className={`flex h-9 w-full rounded-lg border border-slate-200 bg-white px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default defineBackground(() => {
|
|
2
|
+
console.log('Bookmark Extension background loaded');
|
|
3
|
+
|
|
4
|
+
// Listen for messages from popup or content scripts
|
|
5
|
+
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
6
|
+
if (message.type === 'GET_CURRENT_TAB') {
|
|
7
|
+
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
|
8
|
+
if (tabs[0]) {
|
|
9
|
+
sendResponse({
|
|
10
|
+
url: tabs[0].url,
|
|
11
|
+
title: tabs[0].title,
|
|
12
|
+
favicon: tabs[0].favIconUrl,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return true; // Keep message channel open for async response
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { base44 } from '@/lib/base44Client';
|
|
3
|
+
import { Button } from '@/components/Button';
|
|
4
|
+
import { Input } from '@/components/Input';
|
|
5
|
+
import { BookmarkCard } from '@/components/BookmarkCard';
|
|
6
|
+
import { Bookmark, Plus, Search, Sparkles } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
const BookmarkEntity = base44.entities.Bookmark;
|
|
9
|
+
|
|
10
|
+
interface CurrentTab {
|
|
11
|
+
url?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
favicon?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function App() {
|
|
17
|
+
const [bookmarks, setBookmarks] = useState<any[]>([]);
|
|
18
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
19
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
20
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
21
|
+
const [currentTab, setCurrentTab] = useState<CurrentTab>({});
|
|
22
|
+
const [isAISearch, setIsAISearch] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
fetchBookmarks();
|
|
26
|
+
getCurrentTab();
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const getCurrentTab = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const response = await browser.runtime.sendMessage({ type: 'GET_CURRENT_TAB' });
|
|
32
|
+
setCurrentTab(response);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error getting current tab:', error);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const fetchBookmarks = async () => {
|
|
39
|
+
try {
|
|
40
|
+
const data = await BookmarkEntity.list();
|
|
41
|
+
setBookmarks(data);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Error fetching bookmarks:', error);
|
|
44
|
+
} finally {
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const saveCurrentPage = async () => {
|
|
50
|
+
if (!currentTab.url || !currentTab.title) return;
|
|
51
|
+
|
|
52
|
+
setIsSaving(true);
|
|
53
|
+
try {
|
|
54
|
+
await BookmarkEntity.create({
|
|
55
|
+
url: currentTab.url,
|
|
56
|
+
title: currentTab.title,
|
|
57
|
+
favicon: currentTab.favicon || '',
|
|
58
|
+
tags: [],
|
|
59
|
+
});
|
|
60
|
+
await fetchBookmarks();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error saving bookmark:', error);
|
|
63
|
+
} finally {
|
|
64
|
+
setIsSaving(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const deleteBookmark = async (id: string) => {
|
|
69
|
+
try {
|
|
70
|
+
await BookmarkEntity.delete(id);
|
|
71
|
+
await fetchBookmarks();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error deleting bookmark:', error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleSearch = async () => {
|
|
78
|
+
if (!searchQuery.trim()) {
|
|
79
|
+
fetchBookmarks();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isAISearch) {
|
|
84
|
+
// Use AI agent for natural language search
|
|
85
|
+
try {
|
|
86
|
+
const agent = base44.agents.BookmarkSearchAgent;
|
|
87
|
+
const response = await agent.run(searchQuery);
|
|
88
|
+
// Parse agent response and filter bookmarks
|
|
89
|
+
// This is a placeholder - actual implementation depends on agent response format
|
|
90
|
+
console.log('AI Search result:', response);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Error with AI search:', error);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Simple text search
|
|
96
|
+
const filtered = bookmarks.filter((bookmark) =>
|
|
97
|
+
bookmark.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
98
|
+
bookmark.url.toLowerCase().includes(searchQuery.toLowerCase())
|
|
99
|
+
);
|
|
100
|
+
setBookmarks(filtered);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const filteredBookmarks = searchQuery
|
|
105
|
+
? bookmarks.filter((bookmark) =>
|
|
106
|
+
bookmark.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
107
|
+
bookmark.url.toLowerCase().includes(searchQuery.toLowerCase())
|
|
108
|
+
)
|
|
109
|
+
: bookmarks;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50/30">
|
|
113
|
+
<div className="w-full h-full">
|
|
114
|
+
{/* Header */}
|
|
115
|
+
<div className="bg-white border-b border-slate-200 px-4 py-3">
|
|
116
|
+
<div className="flex items-center justify-between mb-3">
|
|
117
|
+
<h1 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
|
118
|
+
<Bookmark className="w-5 h-5 text-blue-600" />
|
|
119
|
+
My Bookmarks
|
|
120
|
+
</h1>
|
|
121
|
+
<Button
|
|
122
|
+
onClick={saveCurrentPage}
|
|
123
|
+
disabled={isSaving || !currentTab.url}
|
|
124
|
+
size="sm"
|
|
125
|
+
className="bg-blue-600 hover:bg-blue-700"
|
|
126
|
+
>
|
|
127
|
+
<Plus className="w-4 h-4 mr-1" />
|
|
128
|
+
Save Page
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Search Bar */}
|
|
133
|
+
<div className="relative">
|
|
134
|
+
<Input
|
|
135
|
+
type="text"
|
|
136
|
+
value={searchQuery}
|
|
137
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
138
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
|
139
|
+
placeholder="Search bookmarks..."
|
|
140
|
+
className="pl-9 pr-10"
|
|
141
|
+
/>
|
|
142
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
143
|
+
<button
|
|
144
|
+
onClick={() => setIsAISearch(!isAISearch)}
|
|
145
|
+
className={`absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded transition-colors ${
|
|
146
|
+
isAISearch ? 'text-purple-600 bg-purple-50' : 'text-slate-400 hover:text-slate-600'
|
|
147
|
+
}`}
|
|
148
|
+
title="Toggle AI Search"
|
|
149
|
+
>
|
|
150
|
+
<Sparkles className="w-4 h-4" />
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Bookmarks List */}
|
|
156
|
+
<div className="p-4 space-y-2 max-h-[440px] overflow-y-auto">
|
|
157
|
+
{isLoading ? (
|
|
158
|
+
<div className="flex items-center justify-center py-12">
|
|
159
|
+
<div className="w-6 h-6 border-2 border-slate-200 border-t-blue-600 rounded-full animate-spin" />
|
|
160
|
+
</div>
|
|
161
|
+
) : filteredBookmarks.length === 0 ? (
|
|
162
|
+
<div className="text-center py-12">
|
|
163
|
+
<Bookmark className="w-12 h-12 text-slate-300 mx-auto mb-3" />
|
|
164
|
+
<p className="text-slate-400 text-sm">
|
|
165
|
+
{searchQuery ? 'No bookmarks found' : 'No bookmarks yet'}
|
|
166
|
+
</p>
|
|
167
|
+
{!searchQuery && (
|
|
168
|
+
<p className="text-slate-400 text-xs mt-1">
|
|
169
|
+
Click "Save Page" to bookmark this page
|
|
170
|
+
</p>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
) : (
|
|
174
|
+
filteredBookmarks.map((bookmark) => (
|
|
175
|
+
<BookmarkCard
|
|
176
|
+
key={bookmark.id}
|
|
177
|
+
bookmark={bookmark}
|
|
178
|
+
onDelete={() => deleteBookmark(bookmark.id)}
|
|
179
|
+
/>
|
|
180
|
+
))
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Bookmarks</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
min-width: 400px;
|
|
9
|
+
max-width: 400px;
|
|
10
|
+
min-height: 500px;
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
12
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
13
|
+
sans-serif;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
-moz-osx-font-smoothing: grayscale;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#root {
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 100%;
|
|
21
|
+
}
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"name": "Create a basic project",
|
|
12
12
|
"description": "Minimal Base44 backend for defining your data models and logic",
|
|
13
13
|
"path": "backend-only"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "chrome-extension",
|
|
17
|
+
"name": "Chrome Extension",
|
|
18
|
+
"description": "Chrome extension with WXT framework and Base44 backend for bookmarking with AI search",
|
|
19
|
+
"path": "chrome-extension"
|
|
14
20
|
}
|
|
15
21
|
]
|
|
16
22
|
}
|