Compare commits

...

14 Commits

Author SHA1 Message Date
ca8728c90c модалочки оп-оп 2026-06-01 05:07:42 +06:00
0dd9efb9fb обновОЧКИ 2026-05-29 04:28:09 +06:00
3c885edc46 brutalism design 2026-05-24 16:26:52 +06:00
12ae0ae839 brutalism design 2026-05-23 22:49:52 +06:00
d06892e990 brutalism design 2026-05-22 06:01:27 +06:00
5d45c9674f brutalism design 2026-05-22 05:27:41 +06:00
1ca73e786c brutalism design 2026-05-22 05:08:41 +06:00
e4ed785911 brutalism design 2026-05-22 05:08:02 +06:00
abf4d41c23 chat wip 2026-05-14 07:09:52 +06:00
edef0a70d2 brutalism design 2026-05-14 01:05:01 +06:00
6a2111092b working hard 2026-05-09 17:39:42 +06:00
0b148c6a7d работаем бля работаем 2026-05-09 03:21:44 +06:00
f845777bac последовательность запуска плагинов 2026-04-25 00:53:57 +06:00
ad477ee813 вложения, канальчики, бим-бим + бам-бам 2026-04-25 00:51:12 +06:00
259 changed files with 33224 additions and 1146 deletions

Binary file not shown.

54
client/.zed/settings.json Normal file
View File

@@ -0,0 +1,54 @@
{
// Use ESLint's --fix:
"code_actions_on_format": {
"source.fixAll.eslint": true,
},
"formatter": [],
// Enable eslint for all supported languages
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
"languages": {
"HTML": {
"language_servers": ["...", "eslint"],
},
"Markdown": {
"language_servers": ["...", "eslint"],
},
"Markdown-Inline": {
"language_servers": ["...", "eslint"],
},
"JSON": {
"language_servers": ["...", "eslint"],
},
"JSONC": {
"language_servers": ["...", "eslint"],
},
"YAML": {
"language_servers": ["...", "eslint"],
},
"CSS": {
"language_servers": ["...", "eslint"],
},
// Add other languages as needed
},
"lsp": {
"eslint": {
"settings": {
"workingDirectories": ["./"],
// Silent the stylistic rules in your IDE, but still auto fix them
"rulesCustomizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true },
],
},
},
},
}

View File

@@ -21,7 +21,7 @@
</PrimeAvatar> </PrimeAvatar>
<p class="flex-1 text-sm leading-5 font-medium text-color truncate w-0"> <p class="flex-1 text-sm leading-5 font-medium text-color truncate w-0">
{{ client.displayName || client.username }} {{ client.displayName || client.username || client.socketId }}
</p> </p>
<Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" /> <Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />

View File

@@ -85,7 +85,7 @@ export const useApp = createGlobalState(() => {
await muteInput() await muteInput()
await signaling.socket.value?.emitWithAck('updateClient', { await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: true, outputMuted: true,
}) })
@@ -98,7 +98,7 @@ export const useApp = createGlobalState(() => {
if (!previousInputMuted.value) if (!previousInputMuted.value)
await unmuteInput() await unmuteInput()
await signaling.socket.value?.emitWithAck('updateClient', { await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: false, outputMuted: false,
}) })

View File

@@ -16,7 +16,7 @@ export const useAuth = createGlobalState(() => {
async function login(username: string, password: string): Promise<void> { async function login(username: string, password: string): Promise<void> {
try { try {
const result = await chadApi<Me>('/login', { const result = await chadApi<Me>('/auth/login', {
method: 'POST', method: 'POST',
body: { body: {
username, username,
@@ -33,7 +33,7 @@ export const useAuth = createGlobalState(() => {
async function register(username: string, password: string): Promise<void> { async function register(username: string, password: string): Promise<void> {
try { try {
const result = await chadApi<Me>('/register', { const result = await chadApi<Me>('/auth/register', {
method: 'POST', method: 'POST',
body: { body: {
username, username,
@@ -50,7 +50,7 @@ export const useAuth = createGlobalState(() => {
async function logout(): Promise<void> { async function logout(): Promise<void> {
try { try {
await chadApi('/logout', { method: 'POST' }) await chadApi('/auth/logout', { method: 'POST' })
setMe(undefined) setMe(undefined)

View File

@@ -1,22 +1,25 @@
import chadApi from '#shared/chad-api'
import { createGlobalState } from '@vueuse/core' import { createGlobalState } from '@vueuse/core'
export interface ChatClientMessage { export interface ChatClientMessage {
text: string text: string
replyTo?: { // replyTo?: {
messageId: string // messageId: string
} // }
} }
export interface ChatMessage { export interface ChatMessage {
id: string id: string
sender: string senderId: string
text: string text: string
createdAt: string createdAt: string
replyTo?: { updatedAt: string
messageId: string attachments: string[]
sender: string // replyTo?: {
text: string // messageId: string
} // sender: string
// text: string
// }
} }
export const useChat = createGlobalState(() => { export const useChat = createGlobalState(() => {
@@ -41,16 +44,16 @@ export const useChat = createGlobalState(() => {
}) })
}, { immediate: true, flush: 'sync' }) }, { immediate: true, flush: 'sync' })
function sendMessage(message: ChatClientMessage) { async function sendMessage(message: ChatClientMessage) {
if (!signaling.connected.value)
return
message.text = message.text.trim() message.text = message.text.trim()
if (!message.text.length) if (!message.text.length)
return return
signaling.socket.value!.emit('chat:message', message) await chadApi<ChatMessage>('/chat/send', {
method: 'POST',
body: message,
})
} }
return { return {

View File

@@ -14,7 +14,7 @@ export const useClients = createGlobalState(() => {
if (!socket) if (!socket)
return return
socket.on('clientChanged', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => { socket.on('client-updated', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => {
const client = getClient(clientId) const client = getClient(clientId)
if (!client) if (!client)

View File

@@ -26,15 +26,15 @@ const ICE_SERVERS: RTCIceServer[] = [
] ]
export const useMediasoup = createSharedComposable(() => { export const useMediasoup = createSharedComposable(() => {
const { emit } = useEventBus() const eventBus = useEventBus()
const signaling = useSignaling() const signaling = useSignaling()
const { addClient, removeClient, me } = useClients() const { addClient, removeClient, me, clients, updateClient } = useClients()
const preferences = usePreferences() const preferences = usePreferences()
const { getShareStream } = useDevices() const { getShareStream } = useDevices()
const device = shallowRef<mediasoupClient.Device>() const device = shallowRef<mediasoupClient.Device>()
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>() const routerRtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
const sendTransport = shallowRef<mediasoupClient.types.Transport>() const sendTransport = shallowRef<mediasoupClient.types.Transport>()
const recvTransport = shallowRef<mediasoupClient.types.Transport>() const recvTransport = shallowRef<mediasoupClient.types.Transport>()
@@ -79,18 +79,30 @@ export const useMediasoup = createSharedComposable(() => {
if (!socket) if (!socket)
return return
socket.on('authenticated', async () => { socket.on('new-client', (client) => {
addClient(client)
eventBus.emit('client:added', client)
})
socket.on('client-switched-channel', (client) => {
updateClient(client.socketId, client)
})
socket.on('initialized', async (initData) => {
if (!signaling.socket.value) if (!signaling.socket.value)
return return
device.value = new mediasoupClient.Device() device.value = new mediasoupClient.Device()
rtpCapabilities.value = await signaling.socket.value.emitWithAck('getRtpCapabilities') routerRtpCapabilities.value = initData.rtpCapabilities
await device.value.load({ routerRtpCapabilities: rtpCapabilities.value! }) clients.value = initData.clients
await device.value.load({ routerRtpCapabilities: routerRtpCapabilities.value! })
// Send transport // Send transport
{ {
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: true, consuming: false }) const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: true, consuming: false })
sendTransport.value = device.value.createSendTransport({ sendTransport.value = device.value.createSendTransport({
...transportInfo, ...transportInfo,
iceServers: [ iceServers: [
@@ -101,7 +113,7 @@ export const useMediasoup = createSharedComposable(() => {
sendTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => { sendTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try { try {
await signaling.socket.value!.emitWithAck('connectTransport', { await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: sendTransport.value!.id, transportId: sendTransport.value!.id,
dtlsParameters, dtlsParameters,
}) })
@@ -135,7 +147,7 @@ export const useMediasoup = createSharedComposable(() => {
// Recv Transport // Recv Transport
{ {
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: false, consuming: true }) const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: false, consuming: true })
recvTransport.value = device.value.createRecvTransport({ recvTransport.value = device.value.createRecvTransport({
...transportInfo, ...transportInfo,
iceServers: [ iceServers: [
@@ -146,7 +158,7 @@ export const useMediasoup = createSharedComposable(() => {
recvTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => { recvTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try { try {
await signaling.socket.value!.emitWithAck('connectTransport', { await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: recvTransport.value!.id, transportId: recvTransport.value!.id,
dtlsParameters, dtlsParameters,
}) })
@@ -160,36 +172,33 @@ export const useMediasoup = createSharedComposable(() => {
} }
}) })
} }
//
// const joinedClients = (await signaling.socket.value.emitWithAck('join', {
// rtpCapabilities: routerRtpCapabilities.value,
// }))
//
// addClient(...joinedClients)
//
// if (me.value)
// eventBus.emit('socket:authenticated', { socketId: me.value.socketId })
//
const joinedClients = (await signaling.socket.value.emitWithAck('join', { // TODO: при переподключении проверять inputMuted
rtpCapabilities: rtpCapabilities.value,
}))
addClient(...joinedClients)
if (me.value)
emit('socket:authenticated', { socketId: me.value.socketId })
await enableMic() await enableMic()
}) })
socket.on('newPeer', (client) => { socket.on('client-disconnected', (id) => {
addClient(client)
emit('client:added', client)
})
socket.on('peerClosed', (id) => {
const { getClient } = useClients() const { getClient } = useClients()
const client = getClient(id) const client = getClient(id)
removeClient(id) removeClient(id)
if (client) if (client)
emit('client:removed', client) eventBus.emit('client:removed', client)
}) })
socket.on( socket.on(
'newConsumer', 'new-consumer',
async ( async (
{ id, producerId, kind, rtpParameters, socketId, appData, producerPaused }, { id, producerId, kind, rtpParameters, socketId, appData, producerPaused },
cb, cb,
@@ -216,18 +225,18 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(consumer), raw: markRaw(consumer),
} }
emit('consumer:added', consumers.value[consumer.id]!) eventBus.emit('consumer:added', consumers.value[consumer.id]!)
consumer.observer.on('resume', () => { consumer.observer.on('resume', () => {
consumers.value[consumer.id]!.paused = false consumers.value[consumer.id]!.paused = false
emit('consumer:resumed', consumers.value[consumer.id]!) eventBus.emit('consumer:resumed', consumers.value[consumer.id]!)
}) })
consumer.observer.on('pause', () => { consumer.observer.on('pause', () => {
consumers.value[consumer.id]!.paused = true consumers.value[consumer.id]!.paused = true
emit('consumer:paused', consumers.value[consumer.id]!) eventBus.emit('consumer:paused', consumers.value[consumer.id]!)
}) })
consumer.observer.on('close', () => { consumer.observer.on('close', () => {
@@ -236,7 +245,7 @@ export const useMediasoup = createSharedComposable(() => {
delete consumers.value[consumer.id] delete consumers.value[consumer.id]
if (consumerData) if (consumerData)
emit('consumer:removed', consumerData) eventBus.emit('consumer:removed', consumerData)
}) })
consumer.on('trackended', () => { consumer.on('trackended', () => {
@@ -248,7 +257,7 @@ export const useMediasoup = createSharedComposable(() => {
) )
socket.on( socket.on(
'consumerClosed', 'consumer-closed',
async ( async (
{ consumerId }, { consumerId },
) => { ) => {
@@ -261,7 +270,7 @@ export const useMediasoup = createSharedComposable(() => {
}, },
) )
socket.on('consumerPaused', ({ consumerId }) => { socket.on('consumer-paused', ({ consumerId }) => {
const consumer = consumers.value[consumerId] const consumer = consumers.value[consumerId]
if (!consumer) if (!consumer)
@@ -270,7 +279,7 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.pause() consumer.raw.pause()
}) })
socket.on('consumerResumed', ({ consumerId }) => { socket.on('consumer-resumed', ({ consumerId }) => {
const consumer = consumers.value[consumerId] const consumer = consumers.value[consumerId]
if (!consumer) if (!consumer)
@@ -279,13 +288,13 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.resume() consumer.raw.resume()
}) })
socket.on('speakingPeers', (value: SpeakingClient[]) => { socket.on('speaking-clients', (value: SpeakingClient[]) => {
speakingClients.value = value speakingClients.value = value
}) })
socket.on('disconnect', () => { socket.on('disconnect', () => {
device.value = undefined device.value = undefined
rtpCapabilities.value = undefined routerRtpCapabilities.value = undefined
sendTransport.value?.close() sendTransport.value?.close()
sendTransport.value = undefined sendTransport.value = undefined
@@ -317,27 +326,28 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(producer), raw: markRaw(producer),
} }
emit('producer:added', producers.value[producer.id]!) eventBus.emit('producer:added', producers.value[producer.id]!)
producer.observer.on('pause', () => { producer.observer.on('pause', () => {
producers.value[producer.id]!.paused = true producers.value[producer.id]!.paused = true
emit('producer:paused', producers.value[producer.id]!) eventBus.emit('producer:paused', producers.value[producer.id]!)
}) })
producer.observer.on('resume', () => { producer.observer.on('resume', () => {
producers.value[producer.id]!.paused = false producers.value[producer.id]!.paused = false
emit('producer:resumed', producers.value[producer.id]!) eventBus.emit('producer:resumed', producers.value[producer.id]!)
}) })
producer.observer.on('close', () => { producer.observer.on('close', () => {
console.log('producer closed')
const producerData = producers.value[producer.id] const producerData = producers.value[producer.id]
delete producers.value[producer.id] delete producers.value[producer.id]
if (producerData) if (producerData)
emit('producer:removed', producerData) eventBus.emit('producer:removed', producerData)
}) })
producer.on('trackended', () => { producer.on('trackended', () => {
@@ -352,7 +362,7 @@ export const useMediasoup = createSharedComposable(() => {
try { try {
producer.raw.close() producer.raw.close()
await signaling.socket.value.emitWithAck('closeProducer', { await signaling.socket.value.emitWithAck('close-producer', {
producerId: producer.id, producerId: producer.id,
}) })
} }
@@ -455,7 +465,7 @@ export const useMediasoup = createSharedComposable(() => {
await createProducer({ await createProducer({
track, track,
streamId: 'share', streamId: 'share',
codec: device.value.rtpCapabilities.codecs?.find( codec: device.value.sendRtpCapabilities.codecs?.find(
c => c.mimeType.toLowerCase() === 'video/AV1', c => c.mimeType.toLowerCase() === 'video/AV1',
), ),
codecOptions: { codecOptions: {
@@ -478,7 +488,7 @@ export const useMediasoup = createSharedComposable(() => {
try { try {
producer.raw.pause() producer.raw.pause()
await signaling.socket.value.emitWithAck('pauseProducer', { await signaling.socket.value.emitWithAck('pause-producer', {
producerId: producer.id, producerId: producer.id,
}) })
} }
@@ -494,7 +504,7 @@ export const useMediasoup = createSharedComposable(() => {
try { try {
producer.raw.resume() producer.raw.resume()
await signaling.socket.value.emitWithAck('resumeProducer', { await signaling.socket.value.emitWithAck('resume-producer', {
producerId: producer.id, producerId: producer.id,
}) })
} }
@@ -526,7 +536,7 @@ export const useMediasoup = createSharedComposable(() => {
speakingClients, speakingClients,
sendTransport, sendTransport,
recvTransport, recvTransport,
rtpCapabilities, rtpCapabilities: routerRtpCapabilities,
device, device,
micProducer, micProducer,
videoProducer, videoProducer,
@@ -536,5 +546,7 @@ export const useMediasoup = createSharedComposable(() => {
enableVideo, enableVideo,
enableShare, enableShare,
disableProducer, disableProducer,
consumersArray,
producersArray,
} }
}) })

View File

@@ -42,7 +42,7 @@ export const usePreferences = createGlobalState(() => {
async ([toggleInputHotkey, toggleOutputHotkey]) => { async ([toggleInputHotkey, toggleOutputHotkey]) => {
try { try {
await chadApi( await chadApi(
'/preferences', '/user/preferences',
{ {
method: 'PATCH', method: 'PATCH',
body: { body: {

View File

@@ -52,11 +52,39 @@
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0"> <PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
<div v-auto-animate class="p-3 space-y-1"> <div v-auto-animate class="p-3 space-y-1">
<ClientRow v-for="client of clients" :key="client.userId" :client="client" /> <template v-for="channel in channels" :key="channel.id">
<PrimeDivider>
<PrimeButton size="small" variant="text" @click="joinChannel(channel)">
{{ channel.name }}
</PrimeButton>
</PrimeDivider>
<ClientRow v-for="client in clients.filter(_client => _client.channelId === channel.id)" :key="client.socketId" :client="client">
{{ client.userId }}
</ClientRow>
</template>
<!-- <ClientRow v-for="client of clients" :key="client.userId" :client="client" /> -->
</div> </div>
</PrimeScrollPanel> </PrimeScrollPanel>
<div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full"> <div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full">
<dl>
<dt>Socket ID</dt>
<dd>{{ socket?.id }}</dd>
<br>
<dt>Producers</dt>
<dd v-for="producer in producersArray" :key="producer.id">
{{ producer.id }}
{{ producer.appData }}
</dd>
<br>
<dl>Consumers</dl>
<dd v-for="consumer in consumersArray" :key="consumer.id">
{{ consumer.id }}
{{ consumer.appData }}
</dd>
</dl>
<slot /> <slot />
</div> </div>
</div> </div>
@@ -65,6 +93,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import chadApi from '#shared/chad-api'
import { import {
Camera, Camera,
CameraOff, CameraOff,
@@ -80,6 +109,13 @@ import {
VolumeOff, VolumeOff,
} from 'lucide-vue-next' } from 'lucide-vue-next'
const channels = shallowRef<any[]>([])
;(async () => {
channels.value = await chadApi<any[]>('/channels', { method: 'GET' })
})()
const { me } = useClients()
const { const {
version, version,
clients, clients,
@@ -93,7 +129,8 @@ const {
toggleVideo, toggleVideo,
toggleShare, toggleShare,
} = useApp() } = useApp()
const { connect, connected } = useSignaling() const { connect, connected, socket } = useSignaling()
const { consumersArray, producersArray } = useMediasoup()
interface Tab { interface Tab {
id: string id: string
@@ -150,4 +187,30 @@ watch(activeTab, (activeTab) => {
}) })
connect() connect()
async function joinChannel(channel) {
socket.value?.emit('join-channel', { channelId: channel.id })
}
watch(socket, (socket) => {
if (!socket)
return
socket.on('channel-removed', (channelId) => {
const idx = channels.value.findIndex(channel => channel.id === channelId)
if (idx === -1)
return
channels.value.splice(idx, 1)
triggerRef(channels)
})
socket.on('channel-created', (channel) => {
channels.value.push(channel)
triggerRef(channels)
})
}, { immediate: true })
</script> </script>

View File

@@ -5,7 +5,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
if (!me.value) { if (!me.value) {
try { try {
setMe(await chadApi('/me', { method: 'GET' })) setMe(await chadApi('/auth/me', { method: 'GET' }))
if (to.meta.auth !== false) if (to.meta.auth !== false)
return navigateTo({ name: 'Index' }) return navigateTo({ name: 'Index' })

View File

@@ -13,7 +13,7 @@ export default defineNuxtRouteMiddleware(async () => {
return return
try { try {
const preferences = await chadApi<SyncedPreferences>('/preferences', { method: 'GET' }) const preferences = await chadApi<SyncedPreferences>('/user/preferences', { method: 'GET' })
if (!preferences) if (!preferences)
return return

View File

@@ -10,25 +10,35 @@
:key="message.id" :key="message.id"
class="w-fit max-w-[60%]" class="w-fit max-w-[60%]"
:class="{ :class="{
'ml-auto': message.sender === me?.username, 'ml-auto': message.senderId === me?.userId,
}" }"
> >
<p <p
v-if="message.sender !== me?.username" v-if="message.senderId !== me?.userId"
class="text-sm text-muted-color mb-1" class="text-sm text-muted-color mb-1"
> >
{{ message.sender }} {{ message.senderId }}
</p> </p>
<div <div
class="px-3 py-2 rounded-lg" class="px-3 py-2 rounded-lg"
:class="{ :class="{
'bg-surface-800': message.sender !== me?.username, 'bg-surface-800 rounded-tl': message.senderId !== me?.userId,
'bg-surface-700': message.sender === me?.username, 'bg-surface-700 rounded-tr': message.senderId === me?.userId,
}" }"
> >
<p class="[&>a]:break-all" @click="handleMessageClick" v-html="parseMessageText(message.text)" /> <p class="[&>a]:break-all" @click="handleMessageClick" v-html="parseMessageText(message.text)" />
<div v-if="message.attachments.length > 0" class="flex flex-col gap-2 mt-2">
<img
v-for="attachmentId in message.attachments"
:key="attachmentId"
class="rounded-xl max-w-60"
:src="`http://localhost:4000/chad/attachment/${attachmentId}`"
:alt="attachmentId"
>
</div>
<p class="mt-1 text-right text-sm text-muted-color" :title="formatDate(message.createdAt, 'dd.MM.yyyy, HH:mm')"> <p class="mt-1 text-right text-sm text-muted-color" :title="formatDate(message.createdAt, 'dd.MM.yyyy, HH:mm')">
{{ formatDate(message.createdAt) }} {{ formatDate(message.createdAt) }}
</p> </p>

View File

@@ -52,7 +52,7 @@ async function save() {
saving.value = true saving.value = true
const updatedMe = await chadApi('/profile', { const updatedMe = await chadApi('/user/profile', {
method: 'PATCH', method: 'PATCH',
body: { body: {
displayName: displayName.value, displayName: displayName.value,

View File

@@ -25,7 +25,7 @@
"linkify-string": "^4.3.2", "linkify-string": "^4.3.2",
"linkifyjs": "^4.3.2", "linkifyjs": "^4.3.2",
"lucide-vue-next": "^0.562.0", "lucide-vue-next": "^0.562.0",
"mediasoup-client": "^3.18.6", "mediasoup-client": "^3.19.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nuxt": "^4.2.2", "nuxt": "^4.2.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",

View File

@@ -3,8 +3,7 @@ import type { Consumer as MediasoupConsumer, Producer as MediasoupProducer } fro
export interface ChadClient { export interface ChadClient {
socketId: string socketId: string
userId: string userId: string
username: string channelId: string
displayName: string
inputMuted?: boolean inputMuted?: boolean
outputMuted?: boolean outputMuted?: boolean

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Chad", "productName": "Chad",
"version": "0.3.0-rc.2", "version": "0.3.0-rc.4",
"identifier": "xyz.koptilnya.chad", "identifier": "xyz.koptilnya.chad",
"build": { "build": {
"frontendDist": "../.output/public", "frontendDist": "../.output/public",

View File

@@ -3004,7 +3004,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.12": "@types/debug@npm:^4.0.0":
version: 4.1.12 version: 4.1.12
resolution: "@types/debug@npm:4.1.12" resolution: "@types/debug@npm:4.1.12"
dependencies: dependencies:
@@ -3013,6 +3013,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/debug@npm:^4.1.13":
version: 4.1.13
resolution: "@types/debug@npm:4.1.13"
dependencies:
"@types/ms": "npm:*"
checksum: 10c0/e5e124021bbdb23a82727eee0a726ae0fc8a3ae1f57253cbcc47497f259afb357de7f6941375e773e1abbfa1604c1555b901a409d762ec2bb4c1612131d4afb7
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": "@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
version: 1.0.8 version: 1.0.8
resolution: "@types/estree@npm:1.0.8" resolution: "@types/estree@npm:1.0.8"
@@ -4082,7 +4091,7 @@ __metadata:
linkify-string: "npm:^4.3.2" linkify-string: "npm:^4.3.2"
linkifyjs: "npm:^4.3.2" linkifyjs: "npm:^4.3.2"
lucide-vue-next: "npm:^0.562.0" lucide-vue-next: "npm:^0.562.0"
mediasoup-client: "npm:^3.18.6" mediasoup-client: "npm:^3.19.0"
mitt: "npm:^3.0.1" mitt: "npm:^3.0.1"
nuxt: "npm:^4.2.2" nuxt: "npm:^4.2.2"
postcss: "npm:^8.5.6" postcss: "npm:^8.5.6"
@@ -7355,11 +7364,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mediasoup-client@npm:^3.18.6": "mediasoup-client@npm:^3.19.0":
version: 3.18.6 version: 3.19.0
resolution: "mediasoup-client@npm:3.18.6" resolution: "mediasoup-client@npm:3.19.0"
dependencies: dependencies:
"@types/debug": "npm:^4.1.12" "@types/debug": "npm:^4.1.13"
"@types/events-alias": "npm:@types/events@^3.0.3" "@types/events-alias": "npm:@types/events@^3.0.3"
awaitqueue: "npm:^3.3.0" awaitqueue: "npm:^3.3.0"
debug: "npm:^4.4.3" debug: "npm:^4.4.3"
@@ -7368,7 +7377,7 @@ __metadata:
h264-profile-level-id: "npm:^2.3.2" h264-profile-level-id: "npm:^2.3.2"
sdp-transform: "npm:^3.0.0" sdp-transform: "npm:^3.0.0"
supports-color: "npm:^10.2.2" supports-color: "npm:^10.2.2"
checksum: 10c0/f5baff9139afccf88de5db767c1139efa5cdd68f4871e2fa9d6ff94d2e71d2365dc40e9ba6e903cde5fbb51a2d82972e738da656be9f6fc7006640fdd82dd5da checksum: 10c0/9fde5ec5daec91d43a88796f49e2b1b7a018c8100a3f99786966678a0e0b5328e88f6e6af36d50f9eed93889b84f23a164865c7177c0767ee805c7a8c7a51eb2
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")"
]
}
}

3
new-client/.env Normal file
View File

@@ -0,0 +1,3 @@
; API_BASE_URL=/api
; API_BASE_URL=https://api.koptilnya.xyz/chad
API_BASE_URL=http://127.0.0.1:4000

24
new-client/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
new-client/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

View File

@@ -0,0 +1,54 @@
{
// Use ESLint's --fix:
"code_actions_on_format": {
"source.fixAll.eslint": true,
},
"formatter": [],
// Enable eslint for all supported languages
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
"languages": {
"HTML": {
"language_servers": ["...", "eslint"],
},
"Markdown": {
"language_servers": ["...", "eslint"],
},
"Markdown-Inline": {
"language_servers": ["...", "eslint"],
},
"JSON": {
"language_servers": ["...", "eslint"],
},
"JSONC": {
"language_servers": ["...", "eslint"],
},
"YAML": {
"language_servers": ["...", "eslint"],
},
"CSS": {
"language_servers": ["...", "eslint"],
},
// Add other languages as needed
},
"lsp": {
"eslint": {
"settings": {
"workingDirectories": ["./"],
// Silent the stylistic rules in your IDE, but still auto fix them
"rulesCustomizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true },
],
},
},
},
}

1
new-client/AGENTS.md Normal file
View File

@@ -0,0 +1 @@
use context7

View File

@@ -0,0 +1,17 @@
import antfu from '@antfu/eslint-config'
export default antfu({
formatters: {
css: true,
},
overrides: {
typescript: {
'no-console': 'off',
},
vue: {
'vue/block-order': ['error', {
order: ['template', 'script', 'style'],
}],
},
},
})

24
new-client/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<title>Chad</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&family=Unbounded:wght@200..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/shared/styles/reset.css" />
<link rel="stylesheet" href="/src/shared/styles/sanitize.css" />
<link rel="stylesheet" href="/src/shared/styles/main.scss" />
</head>
<body>
<div data-mount-point id="app"></div>
<div data-mount-point id="updater"></div>
<div data-mount-point id="preloader"></div>
<script type="module" src="/src/app/entry.ts"></script>
</body>
</html>

50
new-client/package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "new-client",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"typegen": "npx swagger-typescript-api generate --path http://localhost:4000/reference/openapi.yaml --output ./src/shared/api --name generated-chad-api.ts"
},
"dependencies": {
"@lucide/vue": "^1.14.0",
"@tanstack/query-persist-client-core": "^5.100.10",
"@tanstack/vue-query": "^5.100.10",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-opener": "~2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@vueuse/core": "^14.3.0",
"@zag-js/avatar": "^1.40.0",
"@zag-js/collapsible": "^1.40.0",
"@zag-js/dialog": "^1.41.1",
"@zag-js/file-upload": "^1.41.0",
"@zag-js/file-utils": "^1.40.0",
"@zag-js/password-input": "^1.40.0",
"@zag-js/toggle": "^1.40.0",
"@zag-js/vue": "^1.40.0",
"date-fns": "^4.1.0",
"mediasoup-client": "^3.20.0",
"mitt": "^3.0.1",
"primevue": "^4.5.5",
"socket.io-client": "^4.8.3",
"vue": "^3.5.32",
"vue-router": "^5.0.6"
},
"devDependencies": {
"@antfu/eslint-config": "^8.2.0",
"@tauri-apps/cli": "^2.8.4",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/tsconfig": "^0.9.1",
"eslint": "^10.3.0",
"eslint-plugin-format": "^2.0.1",
"sass-embedded": "^1.99.0",
"typescript": "~6.0.2",
"vite": "^8.0.10",
"vue-tsc": "^3.2.7"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

4
new-client/src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas

6228
new-client/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
[package]
name = "app"
version = "0.1.0"
description = "WW додепчик"
authors = ["KPTL"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.4.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.8.5", features = [] }
tauri-plugin-log = "2"
tauri-plugin-process = "2"
windows = { version = "0.52", features = ["Win32_UI_Shell"] }
tauri-plugin-opener = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2"
tauri-plugin-single-instance = "2"
tauri-plugin-updater = "2"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Request camera access for WebRTC</string>
<key>NSMicrophoneUsageDescription</key>
<string>Request microphone access for WebRTC</string>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

View File

@@ -0,0 +1,20 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"updater:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all",
"opener:allow-default-urls",
"opener:allow-open-url"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M232.899 20C237.318 20.0002 240.899 23.5819 240.899 28V227.644C240.899 234.599 232.635 238.241 227.501 233.548L202.03 210.261C200.556 208.913 198.63 208.165 196.632 208.165H23.1006C18.6825 208.165 15.1006 204.583 15.1006 200.165V28C15.1006 23.5819 18.6825 20.0002 23.1006 20H232.899ZM129.758 58.3818C120.029 58.3818 111.244 60.5556 103.403 64.9033C95.5633 69.251 89.3442 75.6118 84.7471 83.9863C80.1499 92.3611 77.8516 102.572 77.8516 114.617C77.8516 126.627 80.1147 136.82 84.6406 145.194C89.1665 153.569 95.3323 159.948 103.137 164.331C110.977 168.679 119.851 170.853 129.758 170.853C137.277 170.852 143.941 169.712 149.75 167.432C155.594 165.151 160.548 162.086 164.61 158.237C168.673 154.353 171.863 150.059 174.18 145.354C175.83 142.054 177.015 138.719 177.735 135.349C178.227 133.051 176.378 131.016 174.028 131.002L155.054 130.888C153.103 130.876 151.468 132.29 150.861 134.145C150.415 135.508 149.849 136.786 149.162 137.978C147.986 140.044 146.471 141.808 144.618 143.27C142.801 144.695 140.68 145.782 138.257 146.53C135.869 147.279 133.214 147.653 130.292 147.653C125.089 147.653 120.581 146.424 116.768 143.965C112.99 141.47 110.05 137.782 107.947 132.899C105.88 127.981 104.847 121.887 104.847 114.617C104.847 107.632 105.862 101.681 107.894 96.7627C109.961 91.8448 112.901 88.0849 116.714 85.4834C120.563 82.8819 125.142 81.5811 130.452 81.5811C133.446 81.5811 136.172 82.009 138.631 82.8643C141.125 83.6839 143.282 84.8773 145.1 86.4453C146.917 88.0134 148.378 89.9028 149.482 92.1123C150.104 93.3561 150.607 94.6845 150.992 96.0977C151.52 98.0371 153.178 99.542 155.188 99.542H173.992C176.355 99.5418 178.218 97.5003 177.806 95.1738C176.953 90.3679 175.494 85.9973 173.431 82.0625C170.758 76.9664 167.283 72.6721 163.007 69.1797C158.73 65.6516 153.777 62.9786 148.146 61.1611C142.516 59.308 136.386 58.3819 129.758 58.3818Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,21 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
// .plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,21 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[cfg(target_os = "windows")]
fn set_app_user_model_id() {
use windows::core::HSTRING;
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
unsafe {
SetCurrentProcessExplicitAppUserModelID(&HSTRING::from("xyz.koptilnya.chad"))
.ok()
.expect("Failed to set AppUserModelID");
}
}
fn main() {
#[cfg(target_os = "windows")]
set_app_user_model_id();
app_lib::run();
}

View File

@@ -0,0 +1 @@
{"rustc_fingerprint":1074701663444030447,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0 (59807616e 2026-04-14)\nbinary: rustc\ncommit-hash: 59807616e1fa2540724bfbac14d7976d7e4a3860\ncommit-date: 2026-04-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.95.0\nLLVM version: 22.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\opti\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}

View File

@@ -0,0 +1 @@
{"rustc":8891013984288978370,"features":"[]","declared_features":"[]","target":12687594469746301899,"profile":8731458305071235362,"path":4942398508502643691,"deps":[[1322478694103194923,"app_lib",false,8070585750093435046],[1322478694103194923,"build_script_build",false,16691984141835454783],[2700615264719386388,"tauri_plugin_opener",false,7350390124577390804],[4736393929136623228,"tauri_plugin_global_shortcut",false,15267933955632970549],[5673630358394410545,"tauri_plugin_updater",false,14019643380268120922],[8695090098768911186,"windows",false,17298248671909505349],[10630857666389190470,"log",false,5016507518680283320],[12653526805862679340,"tauri_plugin_log",false,15855325777720298125],[13548984313718623784,"serde",false,7298479236282139191],[13795362694956882968,"serde_json",false,4974751760879812175],[13905323280135927163,"tauri_plugin_single_instance",false,10523578089830730283],[14261240476176424521,"tauri",false,8738078672178348289],[18393575805749125025,"tauri_plugin_process",false,17190748950968290852]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\app-4e7dc2d6835c5d74\\dep-bin-app","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

Binary file not shown.

View File

@@ -0,0 +1,60 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Chad",
"version": "0.3.0-rc.3",
"identifier": "xyz.koptilnya.chad",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:3000",
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn generate"
},
"app": {
"windows": [
{
"maximizable": true,
"label": "main",
"title": "Chad",
"width": 800,
"height": 600,
"minWidth": 800,
"minHeight": 600,
"resizable": true,
"fullscreen": false,
"center": true,
"theme": "Dark",
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required --lang=en",
"incognito": false
}
],
"security": {
"csp": null,
"capabilities": []
}
},
"bundle": {
"createUpdaterArtifacts": true,
"active": true,
"targets": ["nsis"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"nsis": {
"installerIcon": "icons/icon.ico"
}
}
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3MzkxMzM3RkQ3NTg4QUQKUldTdGlIWDlOeE01NStIak9VbmZTTm9HY2NyNUQrVXB5ZEdIN1BkK2lhYW9zWkNCQnZQSjRmelIK",
"endpoints": [
"https://git.koptilnya.xyz/opti1337/chad/releases/download/latest/updater.json"
]
}
}
}

View File

@@ -0,0 +1,23 @@
<template>
<Component :is="layoutComponent">
<RouterView />
</Component>
<ChadDialogContainer />
</template>
<script setup lang="ts">
import ChadDialogContainer from '@shared/components/ui/DialogContainer.vue'
import DefaultLayout from '@shared/layouts/Default.vue'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const layoutComponent = computed(() => {
return route.meta.layout ?? DefaultLayout
})
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="error-app">
<img class="error-app__image" src="/sad-pepe.png" alt="Oops!" draggable="false">
<p class="error-app__message">
{{ message }}
</p>
</div>
</template>
<script setup lang="ts">
defineProps<{
message: string
}>()
</script>
<style lang="scss">
.error-app {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
&__image {
width: 120px;
margin-bottom: var(--space-6);
user-select: none;
opacity: 0.9;
}
&__message {
@include font-body-bold;
padding: var(--space-2) var(--space-3);
background-color: var(--paper);
outline: var(--border-w) solid var(--ink);
outline-offset: calc(var(--border-w) * -1);
box-shadow: var(--shadow);
}
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div class="preloader-app">
<p>
Loading...
</p>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss">
.preloader-app {
display: flex;
height: 100%;
> p {
@include font-body-bold;
margin: auto;
padding: var(--space-2) var(--space-3);
background-color: var(--paper);
outline: var(--border-w) solid var(--ink);
outline-offset: calc(var(--border-w) * -1);
box-shadow: var(--shadow);
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="updater-app">
<p v-if="checking">
Checking updates...
</p>
<p v-else-if="!!lastUpdate">
Update available: {{ lastUpdate.version }}
</p>
</div>
</template>
<script setup lang="ts">
import { useUpdater } from '@shared/composables/use-updater'
const { checking, lastUpdate } = useUpdater()
</script>
<style lang="scss">
.updater-app {
display: flex;
height: 100%;
> p {
@include font-body-bold;
margin: auto;
padding: var(--space-2) var(--space-3);
background-color: var(--paper);
outline: var(--border-w) solid var(--ink);
outline-offset: calc(var(--border-w) * -1);
box-shadow: var(--shadow);
}
}
</style>

View File

@@ -0,0 +1,28 @@
import { useAuth } from '@shared/composables/use-auth'
import { createApp } from 'vue'
import App from '../App.vue'
import routerPlugin, { router } from '../plugins/router'
import tanstackQueryPlugin from '../plugins/tanstack-query'
const mountPoint = '#app'
export default async function () {
const { authorized } = useAuth()
const app = createApp(App)
app.use(routerPlugin)
await router.isReady()
if (!authorized.value && router.currentRoute.value.meta.auth === undefined) {
await router.push({ name: '/auth/login', replace: true })
}
if (authorized.value && router.currentRoute.value.meta.auth === 'guest') {
await router.push({ name: '/', replace: true })
}
app.use(tanstackQueryPlugin)
app.mount(mountPoint)
}

View File

@@ -0,0 +1,17 @@
import api from '@shared/api/client'
import { useAuth } from '@shared/composables/use-auth'
export default async function () {
const { setMe } = useAuth()
try {
const response = await api.chad.authMe()
setMe(response.data)
}
catch (error) {
if (error.error?.statusCode !== 401) {
throw new Error('Authorization failed')
}
}
}

Some files were not shown because too many files have changed in this diff Show More