Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
940fbc4
Make tracking salt adjustable (to support MAU metric)
Blaumaus Nov 30, 2025
4492d62
Merge branch 'main' into feature/adjustable-salt-setting
Blaumaus Nov 30, 2025
b9a8cf3
Remove legacy session salt mechanism
Blaumaus Nov 30, 2025
813f8fc
Remove unused countKeysByPattern command
Blaumaus Nov 30, 2025
572d235
smaller online users window
Blaumaus Dec 2, 2025
e5e0437
Eliminate Redis from deriving session ID
Blaumaus Dec 2, 2025
ed3a5b1
remove useless comments
Blaumaus Dec 2, 2025
4a21f0c
Introduce Profiles
Blaumaus Dec 3, 2025
c832af7
Profiles UI
Blaumaus Dec 3, 2025
5d06867
fix cut input
Blaumaus Dec 5, 2025
0fe9064
profile avatars, better UI, icons & fix aggregate info
Blaumaus Dec 5, 2025
9091bfb
activity calendar tooltips
Blaumaus Dec 5, 2025
cb8ea70
z-index
Blaumaus Dec 5, 2025
af1b9a0
knip
Blaumaus Dec 5, 2025
c66f1f2
salt entity - primary storage is mysql
Blaumaus Dec 5, 2025
8a22714
instant tooltip
Blaumaus Dec 5, 2025
7757f9a
CE support
Blaumaus Dec 5, 2025
ff45d0a
Merge branch 'main' into feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
e0cd659
online window: 5 minutes
Blaumaus Dec 5, 2025
52b4db5
fix: analytics ingest race condition
Blaumaus Dec 5, 2025
aab5cac
take/skip type transformation
Blaumaus Dec 5, 2025
a751950
take/skip params min/max validation
Blaumaus Dec 5, 2025
324944d
remove weekly salt; remove salt selector from project settings
Blaumaus Dec 5, 2025
906dcce
load profiles on filters change
Blaumaus Dec 5, 2025
4a2d6d5
rm duplicate noData key
Blaumaus Dec 5, 2025
b02675a
CE - profileId support
Blaumaus Dec 5, 2025
1a32cb6
remove obsolete migrations
Blaumaus Dec 5, 2025
8e86d1e
more accurate migration
Blaumaus Dec 5, 2025
83f508e
accurate session updates
Blaumaus Dec 5, 2025
d6c16fa
Minor security improvements
Blaumaus Dec 5, 2025
f42aeee
format
Blaumaus Dec 5, 2025
fedda88
Merge pull request #425 from Swetrix/feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
29de77b
gray out when disabled
Blaumaus Dec 5, 2025
818ebb3
report profiles count
Blaumaus Dec 5, 2025
86409d9
Merge pull request #440 from Swetrix/feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
488a175
Move horizontal tabs to a vertical sidebar
Blaumaus Dec 5, 2025
3bb2c21
Generic Text component
Blaumaus Dec 5, 2025
ace088a
ring inset
Blaumaus Dec 5, 2025
1ced297
move new project creation to a modal window
Blaumaus Dec 6, 2025
ae381b0
add MAX_PROJECT_NAME_LENGTH var
Blaumaus Dec 6, 2025
7eafd96
better project title styling
Blaumaus Dec 6, 2025
c0abe2b
better colour coding
Blaumaus Dec 6, 2025
dd5eec9
better error handling for project creation
Blaumaus Dec 6, 2025
318761b
Merge pull request #441 from Swetrix/ui-updates
Blaumaus Dec 6, 2025
81357da
Create CAPTCHA landing page
Blaumaus Dec 6, 2025
57a375c
remove PeopleLoveSwetrix block
Blaumaus Dec 6, 2025
08a1bd2
better cursor for disabled input fields
Blaumaus Dec 6, 2025
a9c1045
Merge CAPTCHA projects with regular projects
Blaumaus Dec 6, 2025
d58938c
migrate captcha to proof-of-work mechanism
Blaumaus Dec 6, 2025
573ab7f
captcha difficulty selector
Blaumaus Dec 6, 2025
efa19b0
add missing translations
Blaumaus Dec 6, 2025
3f0511c
fix lint
Blaumaus Dec 6, 2025
b463e6f
Bind PoW challenges to the issuing project to prevent cross‑project d…
Blaumaus Dec 6, 2025
e979d43
fix potential security issues
Blaumaus Dec 6, 2025
d1d0615
Merge pull request #443 from Swetrix/revive-captcha
Blaumaus Dec 6, 2025
ee47c35
Remove unused code
Blaumaus Dec 6, 2025
454b485
Goals analytics
Blaumaus Dec 6, 2025
d30ef45
better translation
Blaumaus Dec 6, 2025
7fec2fa
Better UI
Blaumaus Dec 6, 2025
224bfe7
better sidebar design
Blaumaus Dec 6, 2025
c8697b6
header height var
Blaumaus Dec 6, 2025
a2c00b9
Better Goals UI
Blaumaus Dec 6, 2025
7ad2ae0
fix loading indicator issues
Blaumaus Dec 6, 2025
effca2c
add ability to expand goal cards to see chart
Blaumaus Dec 6, 2025
312cdea
review fixes
Blaumaus Dec 7, 2025
e9b6dc7
CE goals support
Blaumaus Dec 7, 2025
deeed9d
fix potential security issues
Blaumaus Dec 7, 2025
9403f89
remove regex
Blaumaus Dec 7, 2025
03ff456
remove unused code
Blaumaus Dec 7, 2025
3e57a81
Merge pull request #446 from Swetrix/goals
Blaumaus Dec 7, 2025
96e11f1
Ask AI feature
Blaumaus Dec 7, 2025
5ac2fb7
fix incorrect tool calling
Blaumaus Dec 7, 2025
c9ba7ce
style tool calls differently
Blaumaus Dec 7, 2025
2ddfa8f
thinking process
Blaumaus Dec 7, 2025
c764c4c
medium reasoning effort
Blaumaus Dec 7, 2025
d24f3da
better autoscroll
Blaumaus Dec 7, 2025
ea2a527
better UI
Blaumaus Dec 7, 2025
cfa98e6
capabilities tooltip
Blaumaus Dec 7, 2025
7e50f77
add some logging
Blaumaus Dec 7, 2025
c3eead1
minimalistic tools display
Blaumaus Dec 7, 2025
e077bbe
google vertex provider
Blaumaus Dec 7, 2025
0a7204a
fix colours
Blaumaus Dec 7, 2025
d785912
use claude haiku
Blaumaus Dec 7, 2025
7ed290f
askAi -> ai
Blaumaus Dec 7, 2025
1f67ba3
AI chat history
Blaumaus Dec 7, 2025
1672b3e
a few improvements to the AI chat
Blaumaus Dec 8, 2025
4b59806
fix lint
Blaumaus Dec 8, 2025
79efbc2
AI fixes
Blaumaus Dec 8, 2025
9471b6a
security improvements
Blaumaus Dec 8, 2025
982ec5d
Merge pull request #447 from Swetrix/ask-ai
Blaumaus Dec 8, 2025
b3bd3ff
feat: Feature flags
Blaumaus Dec 8, 2025
ecd445d
rm attributes
Blaumaus Dec 8, 2025
70e929f
fix inaccessible endpoint
Blaumaus Dec 9, 2025
61aa5ae
use profileId instead of visitorId
Blaumaus Dec 9, 2025
c913864
fix broken refresh for feature flags tab
Blaumaus Dec 9, 2025
5999166
colour feature flags bar
Blaumaus Dec 9, 2025
392f37f
collapsible sidebar
Blaumaus Dec 9, 2025
9394bfe
visitors -> profiles
Blaumaus Dec 9, 2025
fb42ea0
remove unenforcable filters
Blaumaus Dec 9, 2025
c762b59
feature flag usage statistics
Blaumaus Dec 9, 2025
ef6b731
show served / not served feature flags
Blaumaus Dec 9, 2025
4e89bbc
use Text component
Blaumaus Dec 9, 2025
927a6fd
online status
Blaumaus Dec 9, 2025
9f96f6e
tooltip
Blaumaus Dec 10, 2025
5666b46
display profile info for sessions list
Blaumaus Dec 10, 2025
4970c6d
simplify
Blaumaus Dec 10, 2025
81a21ee
remove duplicated code
Blaumaus Dec 10, 2025
13003c5
better pageflow
Blaumaus Dec 10, 2025
71ad3ed
display metadata as a table
Blaumaus Dec 10, 2025
365392d
CE support
Blaumaus Dec 10, 2025
396745b
code refactoring
Blaumaus Dec 10, 2025
d6a1a3e
fix potential security issues
Blaumaus Dec 10, 2025
6aadc62
Merge pull request #450 from Swetrix/feature-flags
Blaumaus Dec 10, 2025
a0e882c
Update errors list & error view UI
Blaumaus Dec 10, 2025
9aeafee
move errors logic from viewproject to a dedicated component
Blaumaus Dec 10, 2025
b63a7c8
move more stuff to ErrorsView.tsx
Blaumaus Dec 10, 2025
3cce7dc
separate sessions tab
Blaumaus Dec 11, 2025
8b5c744
separate performanceview tab
Blaumaus Dec 11, 2025
1533cf6
move funnels stuff to a separate component
Blaumaus Dec 11, 2025
29c4eec
move profiles into it's own component
Blaumaus Dec 11, 2025
e6907b0
a little bit of refactoring
Blaumaus Dec 11, 2025
3af857b
lint
Blaumaus Dec 11, 2025
c788e69
format
Blaumaus Dec 11, 2025
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
6 changes: 3 additions & 3 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ OIDC_CLIENT_SECRET=
CDN_URL=http://localhost:5006
CDN_ACCESS_TOKEN=SOME_SECRET_TOKEN

# Captcha API
CAPTCHA_SALT=

# Google SSO
GOOGLE_OAUTH2_CLIENT_ID=
GOOGLE_OAUTH2_CLIENT_SECRET=
Expand Down Expand Up @@ -100,3 +97,6 @@ EMAIL_ACTION_ENCRYPTION_KEY=

# Swetrix Enterprise (Cloud)
SWETRIX_LICENSE_KEY=

# OpenRouter API key for AI features
OPENROUTER_API_KEY=
151 changes: 151 additions & 0 deletions backend/apps/cloud/src/ai/ai-chat.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository, FindManyOptions, FindOneOptions } from 'typeorm'
import { AiChat, ChatMessage } from './entity/ai-chat.entity'

@Injectable()
export class AiChatService {
constructor(
@InjectRepository(AiChat)
private aiChatRepository: Repository<AiChat>,
) {}

async findOne(options: FindOneOptions<AiChat>): Promise<AiChat | null> {
return this.aiChatRepository.findOne(options)
}

async find(options: FindManyOptions<AiChat>): Promise<AiChat[]> {
return this.aiChatRepository.find(options)
}

async findRecentByProject(
projectId: string,
userId: string | null,
limit: number = 5,
): Promise<AiChat[]> {
const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.projectId = :projectId', { projectId })
.orderBy('chat.updated', 'DESC')
.take(limit)

if (userId) {
queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})
}

return queryBuilder.getMany()
}

async findAllByProject(
projectId: string,
userId: string | null,
skip: number = 0,
take: number = 20,
): Promise<{ chats: AiChat[]; total: number }> {
const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.projectId = :projectId', { projectId })
.orderBy('chat.updated', 'DESC')
.skip(skip)
.take(take)

if (userId) {
queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})
}

const [chats, total] = await queryBuilder.getManyAndCount()
return { chats, total }
}

async create(data: {
projectId: string
userId: string | null
messages: ChatMessage[]
name?: string
}): Promise<AiChat> {
const chat = this.aiChatRepository.create({
project: { id: data.projectId },
user: data.userId ? { id: data.userId } : null,
messages: data.messages,
name: data.name || this.generateChatName(data.messages),
})
return this.aiChatRepository.save(chat)
}

async update(
id: string,
data: { messages?: ChatMessage[]; name?: string },
): Promise<AiChat | null> {
const chat = await this.aiChatRepository.findOne({ where: { id } })
if (!chat) return null

if (data.messages) {
const previousMessages = chat.messages
chat.messages = data.messages
// Update name if not manually set and we have a new first user message
if (!chat.name || chat.name === this.generateChatName(previousMessages)) {
chat.name = this.generateChatName(data.messages)
}
}
if (data.name !== undefined) {
chat.name = data.name
}

return this.aiChatRepository.save(chat)
}

async delete(id: string): Promise<boolean> {
const result = await this.aiChatRepository.delete(id)
return (result.affected ?? 0) > 0
}

private generateChatName(messages: ChatMessage[]): string {
// Use the first user message as the chat name
const firstUserMessage = messages.find(m => m.role === 'user')
if (firstUserMessage) {
const content = firstUserMessage.content.trim()
// Truncate to reasonable length
return content.length > 100 ? content.slice(0, 97) + '...' : content
}
return 'New conversation'
}

async verifyAccess(
chatId: string,
projectId: string,
userId: string | null,
): Promise<AiChat | null> {
const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.id = :chatId', { chatId })
.andWhere('chat.projectId = :projectId', { projectId })

if (userId) {
queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})
}

return queryBuilder.getOne()
}

/**
* Verify that a chat belongs to a project (without checking user ownership).
* Used for shared chat links where anyone who can view the project can access the chat.
*/
async verifyProjectAccess(
chatId: string,
projectId: string,
): Promise<AiChat | null> {
return this.aiChatRepository.findOne({
where: {
id: chatId,
project: { id: projectId },
},
})
}
}
Loading
Loading