Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ packages/
core/ - @chatcops/core (AI providers, tools, knowledge base)
widget/ - @chatcops/widget (embeddable chat UI)
server/ - @chatcops/server (server handler + adapters)
website/ - Marketing site + Starlight docs (Astro)
```

## Development Workflow
Expand Down Expand Up @@ -71,6 +72,26 @@ ANTHROPIC_API_KEY=sk-... npx tsx src/examples/express-server.ts
3. Register in `packages/core/src/i18n/index.ts`
4. Add to the test in `tests/i18n/locales.test.ts`

## Website Development

```bash
cd website
pnpm dev
```

The website requires a `.env.local` file with:

```bash
# Required — powers the live chat demo on the landing page
OPENAI_API_KEY=sk-...

# Optional — visitor counter (Upstash Redis)
UPSTASH_REDIS_REST_URL=https://your-endpoint.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here
```

The visitor counter gracefully degrades without Upstash credentials (displays `-`). Google Analytics (`G-GLYL9J6QYX`) is hardcoded in the layout and Starlight config.

## Commit Convention

Use conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`
6 changes: 5 additions & 1 deletion packages/widget/src/dom/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ export interface PanelOptions {

export class Panel {
private el: HTMLDivElement;
private inline: boolean;
messages: Messages;
input: Input;

constructor(root: ShadowRoot, options: PanelOptions) {
this.inline = options.inline ?? false;
this.el = document.createElement('div');
this.el.className = 'cc-panel';
if (options.inline) {
Expand Down Expand Up @@ -86,7 +88,9 @@ export class Panel {

show(): void {
this.el.classList.add('cc-visible');
this.input.focus();
if (!this.inline) {
this.input.focus();
}
}

hide(): void {
Expand Down
13 changes: 12 additions & 1 deletion website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import vercel from '@astrojs/vercel';
export default defineConfig({
output: 'static',
adapter: vercel(),
site: 'https://chatcops.codercops.com',
site: 'https://chat.codercops.com',
vite: {
plugins: [tailwindcss()],
},
Expand All @@ -23,6 +23,17 @@ export default defineConfig({
{ icon: 'github', label: 'GitHub', href: 'https://github.com/codercops/chatcops' },
],
head: [
{
tag: 'script',
attrs: {
src: 'https://www.googletagmanager.com/gtag/js?id=G-GLYL9J6QYX',
async: true,
},
},
{
tag: 'script',
content: `window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'G-GLYL9J6QYX');`,
},
{
tag: 'script',
attrs: {
Expand Down
44 changes: 44 additions & 0 deletions website/src/components/landing/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,51 @@ const year = new Date().getFullYear();
<span class="text-xs text-text-muted">
Built by <a href="https://codercops.com" target="_blank" rel="noopener" class="text-text-secondary hover:text-text transition-colors">CODERCOPS</a>
</span>
<div class="flex items-center gap-4 text-xs text-text-muted">
<span class="inline-flex items-center gap-1.5">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
Today: <span id="visitors-today" class="font-mono font-semibold text-text-secondary">-</span>
</span>
<span class="inline-flex items-center gap-1.5">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
Total: <span id="visitors-total" class="font-mono font-semibold text-text-secondary">-</span>
</span>
</div>
<span class="text-xs text-text-muted">MIT License &middot; {year}</span>
</div>
</div>
</footer>

<script is:inline>
(function() {
function updateDOM(data) {
var today = document.getElementById('visitors-today');
var total = document.getElementById('visitors-total');
if (today) today.textContent = (data.today || 0).toLocaleString();
if (total) total.textContent = (data.total || 0).toLocaleString();
}

function loadVisitors() {
if (!sessionStorage.getItem('chatcops-counted')) {
fetch('/api/visitors', { method: 'POST', headers: { 'Content-Type': 'application/json' } })
.then(function(r) { return r.json(); })
.then(function(data) {
sessionStorage.setItem('chatcops-counted', '1');
updateDOM(data);
})
.catch(function(e) { console.warn('Visitor counter error:', e); });
} else {
fetch('/api/visitors')
.then(function(r) { return r.json(); })
.then(updateDOM)
.catch(function(e) { console.warn('Visitor counter error:', e); });
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadVisitors);
} else {
loadVisitors();
}
})();
</script>
9 changes: 7 additions & 2 deletions website/src/components/landing/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
<a href="/getting-started/quick-start" class="rounded-lg px-3 py-1.5 text-sm text-text-secondary transition-colors hover:text-text">
Docs
</a>
<a href="https://github.com/codercops/chatcops" class="rounded-lg px-3 py-1.5 text-sm text-text-secondary transition-colors hover:text-text" target="_blank" rel="noopener">
GitHub
<a href="https://github.com/codercops/chatcops" class="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-text-secondary transition-colors hover:text-text" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" fill="currentColor" class="h-4 w-4">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
<span id="gh-stars-header" class="inline-flex items-center gap-1 text-xs font-semibold tabular-nums" style="display:none;">
<svg viewBox="0 0 16 16" fill="currentColor" class="h-3 w-3 text-amber-400"><path d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/></svg>
</span>
</a>
<div class="ml-2 h-4 w-px bg-border"></div>
<a href="/getting-started/quick-start" class="ml-2 rounded-lg bg-primary px-4 py-1.5 text-sm font-medium text-white transition-colors hover:bg-primary-dark">
Expand Down
1 change: 1 addition & 0 deletions website/src/components/landing/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
Star on GitHub
<span id="gh-stars" class="inline-flex items-center rounded-md bg-white/10 px-1.5 py-0.5 text-xs font-semibold tabular-nums" style="display:none;"></span>
</a>
</div>

Expand Down
231 changes: 231 additions & 0 deletions website/src/components/landing/SupportBanner.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
---

<!-- Corner ribbon wrapper -->
<div class="ribbon-wrap" id="ribbon-wrap">
<!-- The diagonal tape -->
<a class="ribbon" id="ribbon-tape" href="#" role="button" aria-label="Support this project">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>
Support Us
</a>

<!-- Dropdown that appears on click -->
<div class="ribbon-dropdown" id="ribbon-dropdown">
<span class="ribbon-label">Support this project</span>
<a href="https://buymeacoffee.com/codercops" target="_blank" rel="noopener" class="ribbon-link ribbon-bmc">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" x2="6" y1="2" y2="4"/><line x1="10" x2="10" y1="2" y2="4"/><line x1="14" x2="14" y1="2" y2="4"/></svg>
Buy Me a Coffee
</a>
<a href="https://www.paypal.com/paypalme/codercops" target="_blank" rel="noopener" class="ribbon-link ribbon-paypal">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></svg>
PayPal
</a>
</div>
</div>

<style>
.ribbon-wrap {
position: fixed;
top: 0;
right: 0;
z-index: 60;
width: 150px;
height: 150px;
overflow: visible;
pointer-events: none;
}

.ribbon {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
position: absolute;
top: 28px;
right: -40px;
width: 200px;
padding: 6px 0;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: #fff;
font-family: system-ui, -apple-system, sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.03em;
text-decoration: none;
text-align: center;
transform: rotate(45deg);
box-shadow:
0 2px 8px rgba(239, 68, 68, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.1) inset;
pointer-events: auto;
cursor: pointer;
transition: all 0.2s ease;
}

.ribbon::before,
.ribbon::after {
content: '';
position: absolute;
bottom: -5px;
border: 5px solid transparent;
border-top-color: #991b1b;
}

.ribbon::before {
left: 0;
border-right-color: #991b1b;
}

.ribbon::after {
right: 0;
border-left-color: #991b1b;
}

.ribbon:hover {
background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
box-shadow:
0 4px 16px rgba(239, 68, 68, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.15) inset;
}

.ribbon svg {
flex-shrink: 0;
animation: heartbeat 2s ease-in-out infinite;
}

@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.2); }
56% { transform: scale(1); }
}

/* Dropdown */
.ribbon-dropdown {
display: none;
position: absolute;
top: 90px;
right: 12px;
flex-direction: column;
gap: 6px;
padding: 12px;
background: rgba(20, 20, 20, 0.97);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
min-width: 190px;
pointer-events: auto;
animation: dropIn 0.15s ease-out;
}

.ribbon-dropdown.is-open {
display: flex;
}

@keyframes dropIn {
from { opacity: 0; transform: translateY(-8px) scale(0.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}

.ribbon-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: rgba(255, 255, 255, 0.35);
padding: 0 4px 4px;
font-family: system-ui, -apple-system, sans-serif;
}

.ribbon-link {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
font-family: system-ui, -apple-system, sans-serif;
text-decoration: none;
transition: all 0.15s ease;
color: #fff;
}

.ribbon-link svg {
flex-shrink: 0;
animation: none;
}

.ribbon-bmc {
background: rgba(255, 221, 0, 0.08);
border: 1px solid rgba(255, 221, 0, 0.18);
color: #fde047;
}

.ribbon-bmc:hover {
background: rgba(255, 221, 0, 0.18);
border-color: rgba(255, 221, 0, 0.35);
}

.ribbon-paypal {
background: rgba(0, 112, 186, 0.08);
border: 1px solid rgba(0, 112, 186, 0.18);
color: #60a5fa;
}

.ribbon-paypal:hover {
background: rgba(0, 112, 186, 0.18);
border-color: rgba(0, 112, 186, 0.35);
}

/* Mobile: smaller ribbon */
@media (max-width: 640px) {
.ribbon-wrap {
width: 120px;
height: 120px;
}

.ribbon {
top: 20px;
right: -48px;
width: 170px;
font-size: 11px;
padding: 5px 0;
}

.ribbon-dropdown {
top: 72px;
right: 8px;
min-width: 170px;
}
}
</style>

<script is:inline>
(function() {
var tape = document.getElementById('ribbon-tape');
var dropdown = document.getElementById('ribbon-dropdown');
if (!tape || !dropdown) return;

tape.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
dropdown.classList.toggle('is-open');
});

document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('is-open');
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') dropdown.classList.remove('is-open');
});
})();
</script>
Loading