@comergehq/studio 0.1.1 → 0.1.3
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/index.js +255 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/src/components/chat/ChatComposer.tsx +277 -0
- package/src/components/chat/ChatHeader.tsx +31 -0
- package/src/components/chat/ChatMessageBubble.tsx +69 -0
- package/src/components/chat/ChatMessageList.tsx +137 -0
- package/src/components/chat/ChatPage.tsx +69 -0
- package/src/components/chat/ForkNoticeBanner.tsx +66 -0
- package/src/components/chat/MultilineTextInput.tsx +46 -0
- package/src/components/chat/ScrollToBottomButton.tsx +78 -0
- package/src/components/chat/TypingIndicator.tsx +54 -0
- package/src/components/chat/index.ts +28 -0
- package/src/components/comments/AppCommentsSheet.tsx +213 -0
- package/src/components/comments/CommentRow.tsx +63 -0
- package/src/components/comments/formatTimeAgo.ts +3 -0
- package/src/components/comments/index.ts +3 -0
- package/src/components/comments/useAppComments.ts +74 -0
- package/src/components/comments/useAppDetails.ts +35 -0
- package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
- package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
- package/src/components/dialogs/index.ts +4 -0
- package/src/components/draw/DrawColorPicker.tsx +77 -0
- package/src/components/draw/DrawModeOverlay.tsx +144 -0
- package/src/components/draw/DrawSurface.tsx +127 -0
- package/src/components/draw/DrawToolbar.tsx +253 -0
- package/src/components/draw/index.ts +15 -0
- package/src/components/draw/optionalHaptics.ts +15 -0
- package/src/components/draw/strokes.ts +21 -0
- package/src/components/draw/types.ts +9 -0
- package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
- package/src/components/floating-draggable-button/constants.ts +17 -0
- package/src/components/floating-draggable-button/index.ts +4 -0
- package/src/components/floating-draggable-button/types.ts +63 -0
- package/src/components/icons/MergeIcon.tsx +14 -0
- package/src/components/icons/StudioIcons.tsx +66 -0
- package/src/components/index.ts +17 -0
- package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
- package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
- package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
- package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
- package/src/components/merge-requests/index.ts +7 -0
- package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
- package/src/components/merge-requests/toIsoString.ts +9 -0
- package/src/components/merge-requests/useControlledExpansion.ts +16 -0
- package/src/components/models/index.ts +9 -0
- package/src/components/models/types.ts +43 -0
- package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
- package/src/components/overlays/index.ts +4 -0
- package/src/components/preview/PreviewHeroCard.tsx +58 -0
- package/src/components/preview/PreviewImage.tsx +22 -0
- package/src/components/preview/PreviewMetaRow.tsx +70 -0
- package/src/components/preview/PreviewPage.tsx +36 -0
- package/src/components/preview/PreviewPlaceholder.tsx +72 -0
- package/src/components/preview/PreviewStatusBadge.tsx +63 -0
- package/src/components/preview/StatsBar.tsx +109 -0
- package/src/components/preview/index.ts +22 -0
- package/src/components/primitives/Avatar.tsx +68 -0
- package/src/components/primitives/Button.tsx +102 -0
- package/src/components/primitives/Card.tsx +30 -0
- package/src/components/primitives/Divider.tsx +17 -0
- package/src/components/primitives/Icon.tsx +40 -0
- package/src/components/primitives/MarkdownText.tsx +72 -0
- package/src/components/primitives/Modal.tsx +53 -0
- package/src/components/primitives/Surface.tsx +42 -0
- package/src/components/primitives/Text.tsx +83 -0
- package/src/components/primitives/index.ts +35 -0
- package/src/components/primitives/types.ts +30 -0
- package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
- package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
- package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
- package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
- package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
- package/src/components/studio-sheet/index.ts +18 -0
- package/src/components/studio-sheet/types.ts +5 -0
- package/src/components/utils/color.ts +25 -0
- package/src/components/utils/formatTimeAgo.ts +19 -0
- package/src/core/logger.ts +42 -0
- package/src/core/services/http/baseUrl.ts +3 -0
- package/src/core/services/http/index.ts +128 -0
- package/src/core/services/http/public.ts +14 -0
- package/src/core/services/supabase/auth.ts +41 -0
- package/src/core/services/supabase/client.ts +43 -0
- package/src/core/services/supabase/index.ts +7 -0
- package/src/data/agent/remote.ts +30 -0
- package/src/data/agent/repository.ts +34 -0
- package/src/data/agent/types.ts +28 -0
- package/src/data/apps/bundles/remote.ts +47 -0
- package/src/data/apps/bundles/repository.ts +35 -0
- package/src/data/apps/bundles/types.ts +27 -0
- package/src/data/apps/images/remote.ts +61 -0
- package/src/data/apps/images/repository.ts +47 -0
- package/src/data/apps/remote.ts +97 -0
- package/src/data/apps/repository.ts +185 -0
- package/src/data/apps/types.ts +206 -0
- package/src/data/attachment/remote.ts +32 -0
- package/src/data/attachment/repository.ts +40 -0
- package/src/data/attachment/types.ts +42 -0
- package/src/data/base-remote.ts +3 -0
- package/src/data/base-repository.ts +11 -0
- package/src/data/comments/likes/remote.ts +87 -0
- package/src/data/comments/likes/repository.ts +61 -0
- package/src/data/comments/likes/types.ts +47 -0
- package/src/data/comments/remote.ts +71 -0
- package/src/data/comments/repository.ts +53 -0
- package/src/data/comments/types.ts +60 -0
- package/src/data/github/remote.ts +23 -0
- package/src/data/github/repository.ts +35 -0
- package/src/data/github/types.ts +23 -0
- package/src/data/home/remote.ts +24 -0
- package/src/data/home/repository.ts +28 -0
- package/src/data/home/types.ts +70 -0
- package/src/data/index.ts +3 -0
- package/src/data/likes/remote.ts +57 -0
- package/src/data/likes/repository.ts +47 -0
- package/src/data/likes/types.ts +46 -0
- package/src/data/me/remote.ts +28 -0
- package/src/data/me/repository.ts +30 -0
- package/src/data/me/types.ts +14 -0
- package/src/data/merge-requests/remote.ts +76 -0
- package/src/data/merge-requests/repository.ts +66 -0
- package/src/data/merge-requests/types.ts +33 -0
- package/src/data/messages/remote.ts +21 -0
- package/src/data/messages/repository.ts +104 -0
- package/src/data/messages/types.ts +20 -0
- package/src/data/public/studio-config/remote.ts +19 -0
- package/src/data/public/studio-config/repository.ts +23 -0
- package/src/data/public/studio-config/types.ts +6 -0
- package/src/data/ratings/remote.ts +76 -0
- package/src/data/ratings/repository.ts +63 -0
- package/src/data/ratings/types.ts +57 -0
- package/src/data/threads/remote.ts +40 -0
- package/src/data/threads/repository.ts +41 -0
- package/src/data/threads/types.ts +25 -0
- package/src/data/types.ts +8 -0
- package/src/data/users/remote.ts +31 -0
- package/src/data/users/repository.ts +45 -0
- package/src/data/users/types.ts +15 -0
- package/src/index.ts +6 -0
- package/src/studio/ComergeStudio.tsx +246 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
- package/src/studio/bootstrap/useStudioBootstrap.ts +51 -0
- package/src/studio/hooks/useApp.ts +83 -0
- package/src/studio/hooks/useAppStats.ts +111 -0
- package/src/studio/hooks/useAttachmentUpload.ts +59 -0
- package/src/studio/hooks/useBundleManager.ts +389 -0
- package/src/studio/hooks/useMergeRequests.ts +173 -0
- package/src/studio/hooks/useStudioActions.ts +96 -0
- package/src/studio/hooks/useThreadMessages.ts +85 -0
- package/src/studio/lib/chat.ts +34 -0
- package/src/studio/ui/ChatPanel.tsx +154 -0
- package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
- package/src/studio/ui/PreviewPanel.tsx +131 -0
- package/src/studio/ui/RuntimeRenderer.tsx +40 -0
- package/src/studio/ui/StudioOverlay.tsx +257 -0
- package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
- package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
- package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
- package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
- package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
- package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
- package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
- package/src/studio/ui/preview-panel/utils.ts +29 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/tokens.ts +118 -0
- package/src/theme/types.ts +90 -0
- package/src/theme/useTheme.ts +11 -0
- package/dist/assets/images/merge.svg +0 -3
- package/dist/merge-72UG27QV.svg +0 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../types';
|
|
3
|
+
import { BaseRemote } from '../base-remote';
|
|
4
|
+
import type {
|
|
5
|
+
MergeRequest,
|
|
6
|
+
MergeRequestStatus,
|
|
7
|
+
MergeRequestsByStatus,
|
|
8
|
+
OpenMergeRequestRequest,
|
|
9
|
+
UpdateMergeRequestRequest,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export interface MergeRequestsRemoteDataSource {
|
|
13
|
+
list(params: { sourceAppId?: string; targetAppId?: string; status?: MergeRequestStatus }): Promise<ServiceResponse<MergeRequest[]>>;
|
|
14
|
+
listByStatuses(params: {
|
|
15
|
+
sourceAppId?: string;
|
|
16
|
+
targetAppId?: string;
|
|
17
|
+
statuses: MergeRequestStatus[];
|
|
18
|
+
}): Promise<ServiceResponse<MergeRequestsByStatus | MergeRequest[]>>;
|
|
19
|
+
open(payload: OpenMergeRequestRequest): Promise<ServiceResponse<MergeRequest>>;
|
|
20
|
+
getById(mrId: string): Promise<ServiceResponse<MergeRequest>>;
|
|
21
|
+
update(mrId: string, payload: UpdateMergeRequestRequest): Promise<ServiceResponse<MergeRequest>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class MergeRequestsRemoteDataSourceImpl extends BaseRemote implements MergeRequestsRemoteDataSource {
|
|
25
|
+
async list(params: { sourceAppId?: string; targetAppId?: string; status?: MergeRequestStatus }): Promise<ServiceResponse<MergeRequest[]>> {
|
|
26
|
+
const query: Record<string, string> = {};
|
|
27
|
+
if (params.sourceAppId) query.sourceAppId = params.sourceAppId;
|
|
28
|
+
if (params.targetAppId) query.targetAppId = params.targetAppId;
|
|
29
|
+
if (params.status) query.status = params.status;
|
|
30
|
+
const { data } = await api.get<ServiceResponse<MergeRequest[]>>('/v1/merge-requests', { params: query });
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listByStatuses(params: {
|
|
35
|
+
sourceAppId?: string;
|
|
36
|
+
targetAppId?: string;
|
|
37
|
+
statuses: MergeRequestStatus[];
|
|
38
|
+
}): Promise<ServiceResponse<MergeRequestsByStatus | MergeRequest[]>> {
|
|
39
|
+
const query: Record<string, string | string[]> = {};
|
|
40
|
+
if (params.sourceAppId) query.sourceAppId = params.sourceAppId;
|
|
41
|
+
if (params.targetAppId) query.targetAppId = params.targetAppId;
|
|
42
|
+
query.status = params.statuses;
|
|
43
|
+
const { data } = await api.get<ServiceResponse<MergeRequestsByStatus | MergeRequest[]>>(
|
|
44
|
+
'/v1/merge-requests',
|
|
45
|
+
{
|
|
46
|
+
params: query,
|
|
47
|
+
paramsSerializer: {
|
|
48
|
+
indexes: null,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async open(payload: OpenMergeRequestRequest): Promise<ServiceResponse<MergeRequest>> {
|
|
56
|
+
const { data } = await api.post<ServiceResponse<MergeRequest>>('/v1/merge-requests', payload);
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getById(mrId: string): Promise<ServiceResponse<MergeRequest>> {
|
|
61
|
+
const { data } = await api.get<ServiceResponse<MergeRequest>>(`/v1/merge-requests/${encodeURIComponent(mrId)}`);
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async update(mrId: string, payload: UpdateMergeRequestRequest): Promise<ServiceResponse<MergeRequest>> {
|
|
66
|
+
const { data } = await api.patch<ServiceResponse<MergeRequest>>(
|
|
67
|
+
`/v1/merge-requests/${encodeURIComponent(mrId)}`,
|
|
68
|
+
payload
|
|
69
|
+
);
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const mergeRequestsRemoteDataSource: MergeRequestsRemoteDataSource = new MergeRequestsRemoteDataSourceImpl();
|
|
75
|
+
|
|
76
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { MergeRequestsRemoteDataSource } from './remote';
|
|
2
|
+
import { mergeRequestsRemoteDataSource } from './remote';
|
|
3
|
+
import type {
|
|
4
|
+
MergeRequest,
|
|
5
|
+
MergeRequestStatus,
|
|
6
|
+
MergeRequestsByStatus,
|
|
7
|
+
OpenMergeRequestRequest,
|
|
8
|
+
UpdateMergeRequestRequest,
|
|
9
|
+
} from './types';
|
|
10
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
11
|
+
|
|
12
|
+
export interface MergeRequestsRepository {
|
|
13
|
+
list(params: { sourceAppId?: string; targetAppId?: string; status?: MergeRequestStatus }): Promise<MergeRequest[]>;
|
|
14
|
+
listByStatuses(params: {
|
|
15
|
+
sourceAppId?: string;
|
|
16
|
+
targetAppId?: string;
|
|
17
|
+
statuses: MergeRequestStatus[];
|
|
18
|
+
}): Promise<MergeRequestsByStatus>;
|
|
19
|
+
open(payload: OpenMergeRequestRequest): Promise<MergeRequest>;
|
|
20
|
+
getById(mrId: string): Promise<MergeRequest>;
|
|
21
|
+
update(mrId: string, payload: UpdateMergeRequestRequest): Promise<MergeRequest>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class MergeRequestsRepositoryImpl extends BaseRepository implements MergeRequestsRepository {
|
|
25
|
+
constructor(private readonly remote: MergeRequestsRemoteDataSource) {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async list(params: { sourceAppId?: string; targetAppId?: string; status?: MergeRequestStatus }): Promise<MergeRequest[]> {
|
|
30
|
+
const res = await this.remote.list(params);
|
|
31
|
+
return this.unwrapOrThrow(res);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listByStatuses(params: {
|
|
35
|
+
sourceAppId?: string;
|
|
36
|
+
targetAppId?: string;
|
|
37
|
+
statuses: MergeRequestStatus[];
|
|
38
|
+
}): Promise<MergeRequestsByStatus> {
|
|
39
|
+
if (!params.statuses || params.statuses.length === 0) return {};
|
|
40
|
+
const res = await this.remote.listByStatuses(params);
|
|
41
|
+
const payload = this.unwrapOrThrow(res);
|
|
42
|
+
if (Array.isArray(payload)) {
|
|
43
|
+
return { [params.statuses[0]]: payload };
|
|
44
|
+
}
|
|
45
|
+
return payload;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async open(payload: OpenMergeRequestRequest): Promise<MergeRequest> {
|
|
49
|
+
const res = await this.remote.open(payload);
|
|
50
|
+
return this.unwrapOrThrow(res);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getById(mrId: string): Promise<MergeRequest> {
|
|
54
|
+
const res = await this.remote.getById(mrId);
|
|
55
|
+
return this.unwrapOrThrow(res);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async update(mrId: string, payload: UpdateMergeRequestRequest): Promise<MergeRequest> {
|
|
59
|
+
const res = await this.remote.update(mrId, payload);
|
|
60
|
+
return this.unwrapOrThrow(res);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const mergeRequestsRepository: MergeRequestsRepository = new MergeRequestsRepositoryImpl(mergeRequestsRemoteDataSource);
|
|
65
|
+
|
|
66
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type MergeRequestStatus = 'open' | 'approved' | 'rejected' | 'merged' | 'closed';
|
|
2
|
+
export type MergeRequestsByStatus = Partial<Record<MergeRequestStatus, MergeRequest[]>>;
|
|
3
|
+
|
|
4
|
+
export type MergeRequest = {
|
|
5
|
+
id: string;
|
|
6
|
+
sourceAppId: string;
|
|
7
|
+
sourceCommitId: string;
|
|
8
|
+
sourceTipCommitId: string | null;
|
|
9
|
+
targetAppId: string;
|
|
10
|
+
targetCommitId: string | null;
|
|
11
|
+
status: MergeRequestStatus;
|
|
12
|
+
title: string | null;
|
|
13
|
+
description: string | null;
|
|
14
|
+
createdBy: string;
|
|
15
|
+
reviewedBy: string | null;
|
|
16
|
+
mergedBy: string | null;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
mergedAt: string | null;
|
|
20
|
+
closedAt: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type OpenMergeRequestRequest = {
|
|
24
|
+
sourceAppId: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type UpdateMergeRequestRequest = {
|
|
28
|
+
title?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
status?: MergeRequestStatus;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../types';
|
|
3
|
+
import type { Message } from './types';
|
|
4
|
+
import { BaseRemote } from '../base-remote';
|
|
5
|
+
|
|
6
|
+
export interface MessagesRemoteDataSource {
|
|
7
|
+
list(threadId: string): Promise<ServiceResponse<Message[]>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class MessagesRemoteDataSourceImpl extends BaseRemote implements MessagesRemoteDataSource {
|
|
11
|
+
async list(threadId: string): Promise<ServiceResponse<Message[]>> {
|
|
12
|
+
const { data } = await api.get<ServiceResponse<Message[]>>(
|
|
13
|
+
`/v1/threads/${encodeURIComponent(threadId)}/messages`
|
|
14
|
+
);
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const messagesRemoteDataSource: MessagesRemoteDataSource = new MessagesRemoteDataSourceImpl();
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { MessagesRemoteDataSource } from './remote';
|
|
2
|
+
import { messagesRemoteDataSource } from './remote';
|
|
3
|
+
import type { Message } from './types';
|
|
4
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
5
|
+
import { getSupabaseClient } from '../../core/services/supabase';
|
|
6
|
+
|
|
7
|
+
type DbMessageRow = {
|
|
8
|
+
id: string;
|
|
9
|
+
app_id: string;
|
|
10
|
+
thread_id: string;
|
|
11
|
+
commit_id: string | null;
|
|
12
|
+
parent_message_id: string | null;
|
|
13
|
+
author_type: 'human' | 'ai' | 'system';
|
|
14
|
+
user_id: string | null;
|
|
15
|
+
payload: Record<string, unknown>;
|
|
16
|
+
reference_id: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function mapDbRowToMessage(row: DbMessageRow): Message {
|
|
22
|
+
return {
|
|
23
|
+
id: row.id,
|
|
24
|
+
appId: row.app_id,
|
|
25
|
+
threadId: row.thread_id,
|
|
26
|
+
commitId: row.commit_id,
|
|
27
|
+
parentMessageId: row.parent_message_id,
|
|
28
|
+
authorType: row.author_type === 'system' ? 'ai' : row.author_type,
|
|
29
|
+
userId: row.user_id,
|
|
30
|
+
payload: row.payload,
|
|
31
|
+
referenceId: row.reference_id,
|
|
32
|
+
createdAt: row.created_at,
|
|
33
|
+
updatedAt: row.updated_at,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MessagesRepository {
|
|
38
|
+
list(threadId: string): Promise<Message[]>;
|
|
39
|
+
subscribeThread(
|
|
40
|
+
threadId: string,
|
|
41
|
+
handlers: {
|
|
42
|
+
onInsert?: (m: Message) => void;
|
|
43
|
+
onUpdate?: (m: Message) => void;
|
|
44
|
+
onDelete?: (m: Message) => void;
|
|
45
|
+
}
|
|
46
|
+
): () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class MessagesRepositoryImpl extends BaseRepository implements MessagesRepository {
|
|
50
|
+
constructor(private readonly remote: MessagesRemoteDataSource) {
|
|
51
|
+
super();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async list(threadId: string): Promise<Message[]> {
|
|
55
|
+
const res = await this.remote.list(threadId);
|
|
56
|
+
return this.unwrapOrThrow(res);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
subscribeThread(
|
|
60
|
+
threadId: string,
|
|
61
|
+
handlers: {
|
|
62
|
+
onInsert?: (m: Message) => void;
|
|
63
|
+
onUpdate?: (m: Message) => void;
|
|
64
|
+
onDelete?: (m: Message) => void;
|
|
65
|
+
}
|
|
66
|
+
): () => void {
|
|
67
|
+
const supabase = getSupabaseClient();
|
|
68
|
+
const channel = supabase
|
|
69
|
+
.channel(`messages:thread:${threadId}`)
|
|
70
|
+
.on(
|
|
71
|
+
'postgres_changes',
|
|
72
|
+
{ event: 'INSERT', schema: 'public', table: 'message', filter: `thread_id=eq.${threadId}` },
|
|
73
|
+
(payload) => {
|
|
74
|
+
const row = payload.new as DbMessageRow;
|
|
75
|
+
handlers.onInsert?.(mapDbRowToMessage(row));
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
.on(
|
|
79
|
+
'postgres_changes',
|
|
80
|
+
{ event: 'UPDATE', schema: 'public', table: 'message', filter: `thread_id=eq.${threadId}` },
|
|
81
|
+
(payload) => {
|
|
82
|
+
const row = payload.new as DbMessageRow;
|
|
83
|
+
handlers.onUpdate?.(mapDbRowToMessage(row));
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
.on(
|
|
87
|
+
'postgres_changes',
|
|
88
|
+
{ event: 'DELETE', schema: 'public', table: 'message', filter: `thread_id=eq.${threadId}` },
|
|
89
|
+
(payload) => {
|
|
90
|
+
const row = payload.old as DbMessageRow;
|
|
91
|
+
handlers.onDelete?.(mapDbRowToMessage(row));
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
.subscribe();
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
supabase.removeChannel(channel);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const messagesRepository: MessagesRepository = new MessagesRepositoryImpl(messagesRemoteDataSource);
|
|
103
|
+
|
|
104
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type Message = {
|
|
2
|
+
id: string;
|
|
3
|
+
appId: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
commitId: string | null;
|
|
6
|
+
parentMessageId: string | null;
|
|
7
|
+
authorType: 'human' | 'ai';
|
|
8
|
+
userId: string | null;
|
|
9
|
+
payload: {
|
|
10
|
+
attachments?: AttachmentMeta[];
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
referenceId: string | null;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
import type { AttachmentMeta } from '../../data/attachment/types';
|
|
19
|
+
|
|
20
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { publicApi } from "../../../core/services/http/public";
|
|
2
|
+
import type { ServiceResponse } from "../../types";
|
|
3
|
+
import { BaseRemote } from "../../base-remote";
|
|
4
|
+
import type { StudioConfig } from "./types";
|
|
5
|
+
|
|
6
|
+
export interface StudioConfigRemoteDataSource {
|
|
7
|
+
get(): Promise<ServiceResponse<StudioConfig>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class StudioConfigRemoteDataSourceImpl extends BaseRemote implements StudioConfigRemoteDataSource {
|
|
11
|
+
async get(): Promise<ServiceResponse<StudioConfig>> {
|
|
12
|
+
const { data } = await publicApi.get<ServiceResponse<StudioConfig>>("/v1/public/studio-config");
|
|
13
|
+
return data;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const studioConfigRemoteDataSource: StudioConfigRemoteDataSource = new StudioConfigRemoteDataSourceImpl();
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseRepository } from "../../base-repository";
|
|
2
|
+
import type { StudioConfig } from "./types";
|
|
3
|
+
import type { StudioConfigRemoteDataSource } from "./remote";
|
|
4
|
+
import { studioConfigRemoteDataSource } from "./remote";
|
|
5
|
+
|
|
6
|
+
export interface StudioConfigRepository {
|
|
7
|
+
get(): Promise<StudioConfig>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class StudioConfigRepositoryImpl extends BaseRepository implements StudioConfigRepository {
|
|
11
|
+
constructor(private readonly remote: StudioConfigRemoteDataSource) {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async get(): Promise<StudioConfig> {
|
|
16
|
+
const res = await this.remote.get();
|
|
17
|
+
return this.unwrapOrThrow(res);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const studioConfigRepository: StudioConfigRepository = new StudioConfigRepositoryImpl(studioConfigRemoteDataSource);
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../types';
|
|
3
|
+
import { BaseRemote } from '../base-remote';
|
|
4
|
+
import type {
|
|
5
|
+
AppRating,
|
|
6
|
+
AppRatingList,
|
|
7
|
+
AppRatingMutationResult,
|
|
8
|
+
AppRatingStatsResult,
|
|
9
|
+
CreateAppRatingInput,
|
|
10
|
+
ListAppRatingsQuery,
|
|
11
|
+
UpdateAppRatingInput,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
export interface AppRatingsRemoteDataSource {
|
|
15
|
+
list(appId: string, query?: ListAppRatingsQuery): Promise<ServiceResponse<AppRatingList>>;
|
|
16
|
+
getById(appId: string, ratingId: string): Promise<ServiceResponse<AppRating>>;
|
|
17
|
+
create(appId: string, payload: CreateAppRatingInput): Promise<ServiceResponse<AppRatingMutationResult>>;
|
|
18
|
+
update(
|
|
19
|
+
appId: string,
|
|
20
|
+
ratingId: string,
|
|
21
|
+
payload: UpdateAppRatingInput
|
|
22
|
+
): Promise<ServiceResponse<AppRatingMutationResult>>;
|
|
23
|
+
remove(appId: string, ratingId: string): Promise<ServiceResponse<AppRatingStatsResult>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AppRatingsRemoteDataSourceImpl extends BaseRemote implements AppRatingsRemoteDataSource {
|
|
27
|
+
async list(appId: string, query?: ListAppRatingsQuery): Promise<ServiceResponse<AppRatingList>> {
|
|
28
|
+
const params = query ? { ...query } : undefined;
|
|
29
|
+
const { data } = await api.get<ServiceResponse<AppRatingList>>(
|
|
30
|
+
`/v1/apps/${encodeURIComponent(appId)}/ratings`,
|
|
31
|
+
{ params }
|
|
32
|
+
);
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getById(appId: string, ratingId: string): Promise<ServiceResponse<AppRating>> {
|
|
37
|
+
const { data } = await api.get<ServiceResponse<AppRating>>(
|
|
38
|
+
`/v1/apps/${encodeURIComponent(appId)}/ratings/${encodeURIComponent(ratingId)}`
|
|
39
|
+
);
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async create(
|
|
44
|
+
appId: string,
|
|
45
|
+
payload: CreateAppRatingInput
|
|
46
|
+
): Promise<ServiceResponse<AppRatingMutationResult>> {
|
|
47
|
+
const { data } = await api.post<ServiceResponse<AppRatingMutationResult>>(
|
|
48
|
+
`/v1/apps/${encodeURIComponent(appId)}/ratings`,
|
|
49
|
+
payload
|
|
50
|
+
);
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async update(
|
|
55
|
+
appId: string,
|
|
56
|
+
ratingId: string,
|
|
57
|
+
payload: UpdateAppRatingInput
|
|
58
|
+
): Promise<ServiceResponse<AppRatingMutationResult>> {
|
|
59
|
+
const { data } = await api.patch<ServiceResponse<AppRatingMutationResult>>(
|
|
60
|
+
`/v1/apps/${encodeURIComponent(appId)}/ratings/${encodeURIComponent(ratingId)}`,
|
|
61
|
+
payload
|
|
62
|
+
);
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async remove(appId: string, ratingId: string): Promise<ServiceResponse<AppRatingStatsResult>> {
|
|
67
|
+
const { data } = await api.delete<ServiceResponse<AppRatingStatsResult>>(
|
|
68
|
+
`/v1/apps/${encodeURIComponent(appId)}/ratings/${encodeURIComponent(ratingId)}`
|
|
69
|
+
);
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const appRatingsRemoteDataSource: AppRatingsRemoteDataSource = new AppRatingsRemoteDataSourceImpl();
|
|
75
|
+
|
|
76
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AppRatingsRemoteDataSource } from './remote';
|
|
2
|
+
import { appRatingsRemoteDataSource } from './remote';
|
|
3
|
+
import type {
|
|
4
|
+
AppRating,
|
|
5
|
+
AppRatingList,
|
|
6
|
+
AppRatingMutationResult,
|
|
7
|
+
AppRatingStatsResult,
|
|
8
|
+
CreateAppRatingInput,
|
|
9
|
+
ListAppRatingsQuery,
|
|
10
|
+
UpdateAppRatingInput,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
13
|
+
|
|
14
|
+
export interface AppRatingsRepository {
|
|
15
|
+
list(appId: string, query?: ListAppRatingsQuery): Promise<AppRatingList>;
|
|
16
|
+
getById(appId: string, ratingId: string): Promise<AppRating>;
|
|
17
|
+
create(appId: string, payload: CreateAppRatingInput): Promise<AppRatingMutationResult>;
|
|
18
|
+
update(
|
|
19
|
+
appId: string,
|
|
20
|
+
ratingId: string,
|
|
21
|
+
payload: UpdateAppRatingInput
|
|
22
|
+
): Promise<AppRatingMutationResult>;
|
|
23
|
+
remove(appId: string, ratingId: string): Promise<AppRatingStatsResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AppRatingsRepositoryImpl extends BaseRepository implements AppRatingsRepository {
|
|
27
|
+
constructor(private readonly remote: AppRatingsRemoteDataSource) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async list(appId: string, query?: ListAppRatingsQuery): Promise<AppRatingList> {
|
|
32
|
+
const res = await this.remote.list(appId, query);
|
|
33
|
+
return this.unwrapOrThrow(res);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getById(appId: string, ratingId: string): Promise<AppRating> {
|
|
37
|
+
const res = await this.remote.getById(appId, ratingId);
|
|
38
|
+
return this.unwrapOrThrow(res);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async create(appId: string, payload: CreateAppRatingInput): Promise<AppRatingMutationResult> {
|
|
42
|
+
const res = await this.remote.create(appId, payload);
|
|
43
|
+
return this.unwrapOrThrow(res);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async update(
|
|
47
|
+
appId: string,
|
|
48
|
+
ratingId: string,
|
|
49
|
+
payload: UpdateAppRatingInput
|
|
50
|
+
): Promise<AppRatingMutationResult> {
|
|
51
|
+
const res = await this.remote.update(appId, ratingId, payload);
|
|
52
|
+
return this.unwrapOrThrow(res);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async remove(appId: string, ratingId: string): Promise<AppRatingStatsResult> {
|
|
56
|
+
const res = await this.remote.remove(appId, ratingId);
|
|
57
|
+
return this.unwrapOrThrow(res);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const appRatingsRepository: AppRatingsRepository = new AppRatingsRepositoryImpl(appRatingsRemoteDataSource);
|
|
62
|
+
|
|
63
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type AppRating = {
|
|
2
|
+
id: string;
|
|
3
|
+
appId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
rating: number;
|
|
6
|
+
commentId: string | null;
|
|
7
|
+
title: string | null;
|
|
8
|
+
review: string | null;
|
|
9
|
+
metadata: Record<string, unknown>;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ListAppRatingsQuery = {
|
|
15
|
+
page?: number;
|
|
16
|
+
pageSize?: number;
|
|
17
|
+
userId?: string;
|
|
18
|
+
minRating?: number;
|
|
19
|
+
maxRating?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CreateAppRatingInput = {
|
|
23
|
+
rating: number;
|
|
24
|
+
commentId?: string | null;
|
|
25
|
+
title?: string | null;
|
|
26
|
+
review?: string | null;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type UpdateAppRatingInput = Partial<CreateAppRatingInput>;
|
|
31
|
+
|
|
32
|
+
export type AppRatingStats = {
|
|
33
|
+
total?: number;
|
|
34
|
+
average?: number | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type AppRatingList = {
|
|
38
|
+
items: AppRating[];
|
|
39
|
+
pageInfo: {
|
|
40
|
+
page: number;
|
|
41
|
+
pageSize: number;
|
|
42
|
+
total: number;
|
|
43
|
+
hasMore: boolean;
|
|
44
|
+
};
|
|
45
|
+
stats: AppRatingStats;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type AppRatingMutationResult = {
|
|
49
|
+
rating: AppRating;
|
|
50
|
+
stats?: AppRatingStats;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type AppRatingStatsResult = {
|
|
54
|
+
stats?: AppRatingStats;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../types';
|
|
3
|
+
import { BaseRemote } from '../base-remote';
|
|
4
|
+
import type { Thread, UpdateThreadRequest } from './types';
|
|
5
|
+
|
|
6
|
+
export interface ThreadsRemoteDataSource {
|
|
7
|
+
list(): Promise<ServiceResponse<Thread[]>>;
|
|
8
|
+
getById(threadId: string): Promise<ServiceResponse<Thread>>;
|
|
9
|
+
update(threadId: string, payload: UpdateThreadRequest): Promise<ServiceResponse<Thread>>;
|
|
10
|
+
delete(threadId: string): Promise<ServiceResponse<never>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class ThreadsRemoteDataSourceImpl extends BaseRemote implements ThreadsRemoteDataSource {
|
|
14
|
+
async list(): Promise<ServiceResponse<Thread[]>> {
|
|
15
|
+
const { data } = await api.get<ServiceResponse<Thread[]>>('/v1/threads');
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getById(threadId: string): Promise<ServiceResponse<Thread>> {
|
|
20
|
+
const { data } = await api.get<ServiceResponse<Thread>>(`/v1/threads/${encodeURIComponent(threadId)}`);
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async update(threadId: string, payload: UpdateThreadRequest): Promise<ServiceResponse<Thread>> {
|
|
25
|
+
const { data } = await api.patch<ServiceResponse<Thread>>(
|
|
26
|
+
`/v1/threads/${encodeURIComponent(threadId)}`,
|
|
27
|
+
payload
|
|
28
|
+
);
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async delete(threadId: string): Promise<ServiceResponse<never>> {
|
|
33
|
+
const { data } = await api.delete<ServiceResponse<never>>(`/v1/threads/${encodeURIComponent(threadId)}`);
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const threadsRemoteDataSource: ThreadsRemoteDataSource = new ThreadsRemoteDataSourceImpl();
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ThreadsRemoteDataSource } from './remote';
|
|
2
|
+
import { threadsRemoteDataSource } from './remote';
|
|
3
|
+
import type { Thread, UpdateThreadRequest } from './types';
|
|
4
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
5
|
+
|
|
6
|
+
export interface ThreadsRepository {
|
|
7
|
+
list(): Promise<Thread[]>;
|
|
8
|
+
getById(threadId: string): Promise<Thread>;
|
|
9
|
+
update(threadId: string, payload: UpdateThreadRequest): Promise<Thread>;
|
|
10
|
+
delete(threadId: string): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class ThreadsRepositoryImpl extends BaseRepository implements ThreadsRepository {
|
|
14
|
+
constructor(private readonly remote: ThreadsRemoteDataSource) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async list(): Promise<Thread[]> {
|
|
19
|
+
const res = await this.remote.list();
|
|
20
|
+
return this.unwrapOrThrow(res);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getById(threadId: string): Promise<Thread> {
|
|
24
|
+
const res = await this.remote.getById(threadId);
|
|
25
|
+
return this.unwrapOrThrow(res);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async update(threadId: string, payload: UpdateThreadRequest): Promise<Thread> {
|
|
29
|
+
const res = await this.remote.update(threadId, payload);
|
|
30
|
+
return this.unwrapOrThrow(res);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async delete(threadId: string): Promise<void> {
|
|
34
|
+
const res = await this.remote.delete(threadId);
|
|
35
|
+
this.unwrapOrThrow(res as any);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const threadsRepository: ThreadsRepository = new ThreadsRepositoryImpl(threadsRemoteDataSource);
|
|
40
|
+
|
|
41
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ThreadKind = 'chat' | 'review' | 'build' | 'system';
|
|
2
|
+
|
|
3
|
+
export type Thread = {
|
|
4
|
+
id: string;
|
|
5
|
+
createdBy: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
kind: ThreadKind;
|
|
8
|
+
isLocked: boolean;
|
|
9
|
+
isArchived: boolean;
|
|
10
|
+
metadata: Record<string, unknown> | null;
|
|
11
|
+
messageCount: number;
|
|
12
|
+
lastMessageAt: string | null;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type UpdateThreadRequest = {
|
|
18
|
+
title?: string;
|
|
19
|
+
kind?: ThreadKind;
|
|
20
|
+
isLocked?: boolean;
|
|
21
|
+
isArchived?: boolean;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|