Skip to content

Commit 51c2f9b

Browse files
committed
chore: initial commit
0 parents  commit 51c2f9b

24 files changed

+1970
-0
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Add your secrets
2+
CLOUDFLARE_API_TOKEN=your_token
3+
CLOUDFLARE_ACCOUNT_ID=your_account_id

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: oven-sh/setup-bun@v1
16+
with:
17+
bun-version: latest
18+
19+
- name: Install dependencies
20+
run: bun install
21+
22+
- name: Type check
23+
run: bun run typecheck
24+
25+
- name: Lint
26+
run: bun run lint
27+
28+
- name: Test
29+
run: bun run test
30+
31+
- name: Upload coverage
32+
uses: actions/upload-artifact@v4
33+
with:
34+
name: coverage
35+
path: coverage/
36+
37+
- name: Deploy to Cloudflare
38+
run: bun run deploy
39+
env:
40+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
41+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# dependencies (bun install)
2+
node_modules
3+
4+
# output
5+
out
6+
dist
7+
*.tgz
8+
9+
# code coverage
10+
coverage
11+
*.lcov
12+
13+
# logs
14+
logs
15+
_.log
16+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17+
18+
# dotenv environment variable files
19+
.env
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
.env.local
24+
25+
# caches
26+
.eslintcache
27+
.cache
28+
*.tsbuildinfo
29+
30+
# IntelliJ based IDEs
31+
.idea
32+
33+
# Finder (MacOS) folder config
34+
.DS_Store
35+
36+
# Wrangler
37+
.wrangler

.husky/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bun run lint:fix
2+
bun run typecheck

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# lablet.ai
2+
3+
## Architecture
4+
5+
```mermaid
6+
7+
flowchart TD
8+
subgraph Sources
9+
BB[Bitbucket]
10+
GH[GitHub]
11+
GL[GitLab]
12+
end
13+
14+
subgraph EventIngestion
15+
WH[Webhook Service]
16+
Q[Queue System]
17+
C[Cache Layer]
18+
end
19+
20+
subgraph Processing
21+
CF[Cloudflare Workers]
22+
AI[AI Processing Service]
23+
AGG[Aggregator Service]
24+
end
25+
26+
subgraph Storage
27+
DB[(Database)]
28+
ML[(Model Training Data)]
29+
end
30+
31+
subgraph Output
32+
LAB[Labeling Service]
33+
API[API Service]
34+
end
35+
36+
BB --> WH
37+
GH --> WH
38+
GL --> WH
39+
WH --> Q
40+
Q --> CF
41+
CF --> C
42+
C --> AI
43+
AI --> AGG
44+
AGG --> LAB
45+
LAB --> DB
46+
LAB --> ML
47+
LAB --> API
48+
ML --> AI
49+
```

bun.lock

Lines changed: 1227 additions & 0 deletions
Large diffs are not rendered by default.

eslint.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import antfu from '@antfu/eslint-config'
2+
3+
export default antfu({
4+
typescript: true,
5+
ignores: ['dist', '.wrangler', 'coverage'],
6+
})

package.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "lablet.ai",
3+
"type": "module",
4+
"private": true,
5+
"module": "src/worker.ts",
6+
"scripts": {
7+
"prepare": "husky",
8+
"dev": "wrangler dev",
9+
"deploy": "wrangler deploy",
10+
"test": "vitest run",
11+
"lint": "eslint .",
12+
"lint:fix": "eslint . --fix",
13+
"typecheck": "tsc --noEmit"
14+
},
15+
"peerDependencies": {
16+
"typescript": "^5.7.3"
17+
},
18+
"dependencies": {
19+
"ai": "^4.1.46",
20+
"hono": "^4.7.2",
21+
"std-env": "^3.8.0",
22+
"workers-ai-provider": "^0.0.13",
23+
"zod": "^3.24.2"
24+
},
25+
"devDependencies": {
26+
"@antfu/eslint-config": "^4.3.0",
27+
"@cloudflare/vitest-pool-workers": "^0.7.3",
28+
"@cloudflare/workers-types": "^4.20250224.0",
29+
"@types/bun": "1.2.3",
30+
"@types/node": "^22.13.5",
31+
"@vitest/coverage-v8": "^3.0.7",
32+
"bun-types": "^1.2.4",
33+
"eslint": "^9.21.0",
34+
"husky": "^9.1.7",
35+
"typescript": "^5.7.3",
36+
"vitest": "^3.0.7",
37+
"wrangler": "^3.110.0"
38+
}
39+
}

src/config/environment.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { env } from 'std-env'
2+
3+
export const ENV = {
4+
CACHE_TTL: Number.parseInt(env.CACHE_TTL || '3600'),
5+
CACHE_NAMESPACE: env.CACHE_NAMESPACE || 'labelit-ai',
6+
MODEL_NAME: env.MODEL_NAME || '@cf/meta/llama-3.1-8b-instruct',
7+
}

src/services/ai-processor.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { Bindings } from '@/types'
2+
import type { ProcessedTicket, TicketData } from '@/types/ticket'
3+
import type { LanguageModelV1 } from 'ai'
4+
import { ENV } from '@/config/environment'
5+
import { generateText } from 'ai'
6+
import { createWorkersAI } from 'workers-ai-provider'
7+
8+
export class AIProcessor {
9+
private model: LanguageModelV1
10+
private systemPrompt: string
11+
12+
constructor(env: Bindings) {
13+
const workersai = createWorkersAI({ binding: env.AI })
14+
this.model = workersai(ENV.MODEL_NAME, {
15+
safePrompt: true,
16+
})
17+
this.systemPrompt = `
18+
You are a ticket classification system. Classify tickets as either Bug, Story, Task, or Spike.
19+
20+
- Bugs: Issues or unexpected behavior
21+
- Stories: User-facing features
22+
- Tasks: Technical work items
23+
- Spikes: Research or exploration items
24+
25+
Respond only with the classification label.
26+
`
27+
}
28+
29+
public async classifyTicket(ticket: TicketData): Promise<ProcessedTicket | Error> {
30+
const startTime = Date.now()
31+
const userContent = `Title: ${ticket.title}\nDescription: ${ticket.description}`
32+
33+
const { text } = await generateText({
34+
model: this.model,
35+
messages: [{
36+
role: 'system',
37+
content: this.systemPrompt,
38+
}, {
39+
role: 'user',
40+
content: userContent,
41+
}],
42+
})
43+
44+
const result: ProcessedTicket = {
45+
...ticket,
46+
predictedLabel: this.parseResponse(text),
47+
processingTime: Date.now() - startTime,
48+
}
49+
50+
return result
51+
}
52+
53+
private parseResponse(response: string): ProcessedTicket['predictedLabel'] {
54+
const normalized = response.trim().toLowerCase()
55+
if (normalized.includes('bug'))
56+
return 'Bug'
57+
if (normalized.includes('story'))
58+
return 'Story'
59+
if (normalized.includes('spike'))
60+
return 'Spike'
61+
return 'Task'
62+
}
63+
}

0 commit comments

Comments
 (0)