mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'mgn-dv' of https://github.com/appwrite/console into mgn-dv
This commit is contained in:
@@ -258,7 +258,10 @@
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
margin-block-end: var(--space-7);
|
||||
margin-block-end: var(--space-4);
|
||||
margin-inline-start: calc(-1 * var(--space-4));
|
||||
border-radius: var(--border-radius-xs);
|
||||
width: calc(100% + 2 * var(--space-4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<script lang="ts">
|
||||
import Chat from '$lib/components/studio/chat/chat.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { conversation, showChat } from '$lib/stores/chat.js';
|
||||
import {
|
||||
disableBodySelect,
|
||||
enabledBodySelect,
|
||||
getChatWidthFromPrefs,
|
||||
saveChatWidthToPrefs
|
||||
} from '$lib/helpers/studioLayout.js';
|
||||
import { createStreamParser } from '$lib/components/studio/chat/parser.js';
|
||||
import { sdk } from '$lib/stores/sdk.js';
|
||||
import { synapse } from '$lib/components/studio/synapse.svelte.js';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { filesystem } from '$lib/components/editor/filesystem';
|
||||
import { previewFrameRef } from '$routes/(console)/project-[project]/store';
|
||||
|
||||
$effect(() => {
|
||||
if ($isSmallViewport || page.params.artifact) {
|
||||
showChat.set(true);
|
||||
}
|
||||
if (!page.params.artifact) {
|
||||
showChat.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
let resizer = $state(null);
|
||||
let resizerLeftPosition = $state(
|
||||
page.data?.subNavigation ? getChatWidthFromPrefs() + 24 : getChatWidthFromPrefs()
|
||||
);
|
||||
let resizerLeftOffset = $state(page.data?.subNavigation ? 52 : 9);
|
||||
let chatWidth = $derived(resizerLeftPosition - resizerLeftOffset);
|
||||
|
||||
$effect(() => {
|
||||
chatWidth = resizerLeftPosition - resizerLeftOffset;
|
||||
});
|
||||
|
||||
let isResizing = false;
|
||||
|
||||
function startResize() {
|
||||
isResizing = true;
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
window.addEventListener('touchmove', resize);
|
||||
window.addEventListener('touchend', stopResize);
|
||||
disableBodySelect();
|
||||
|
||||
if ($previewFrameRef) {
|
||||
$previewFrameRef.style.pointerEvents = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function resize(event: TouchEvent | MouseEvent) {
|
||||
if (!isResizing) return;
|
||||
if (resizer) {
|
||||
resizerLeftPosition =
|
||||
('touches' in event ? event.touches[0].clientX : event.clientX) - 60;
|
||||
const maxSize = page.data?.subNavigation
|
||||
? window.innerWidth - 660
|
||||
: window.innerWidth - 540;
|
||||
|
||||
const minSize = 320;
|
||||
if (resizerLeftPosition > maxSize) {
|
||||
resizerLeftPosition = maxSize;
|
||||
} else if (resizerLeftPosition < minSize) {
|
||||
if (resizerLeftPosition < minSize - 100) {
|
||||
showChat.set(false);
|
||||
}
|
||||
|
||||
resizerLeftPosition = minSize;
|
||||
} else if (resizerLeftPosition > minSize && !$showChat) {
|
||||
showChat.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
if (!$showChat) {
|
||||
resizerLeftPosition = 500;
|
||||
}
|
||||
const saveWidth = page.data?.subNavigation ? resizerLeftPosition - 24 : resizerLeftPosition;
|
||||
saveChatWidthToPrefs(saveWidth);
|
||||
|
||||
isResizing = false;
|
||||
window.removeEventListener('mousemove', resize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
window.removeEventListener('touchmove', resize);
|
||||
window.removeEventListener('touchend', stopResize);
|
||||
enabledBodySelect();
|
||||
|
||||
if ($previewFrameRef) {
|
||||
$previewFrameRef.style.pointerEvents = '';
|
||||
}
|
||||
}
|
||||
|
||||
const parser = createStreamParser();
|
||||
|
||||
async function getConversation(artifactId: string) {
|
||||
parser.reset();
|
||||
const { conversations } = await sdk.forProject.imagine.listConversations(artifactId);
|
||||
if (conversations.length === 0) {
|
||||
const convo = sdk.forProject.imagine.createConversation(
|
||||
artifactId,
|
||||
`Conversation ${new Date().getTime()}`
|
||||
);
|
||||
conversation.set(convo);
|
||||
} else {
|
||||
conversation.set(conversations[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (page.params.artifact && $conversation.data?.artifactId !== page.params.artifact) {
|
||||
getConversation(page.params.artifact);
|
||||
}
|
||||
});
|
||||
|
||||
conversation.subscribe(async (convo) => {
|
||||
if (!convo.data) return;
|
||||
const { messages } = await sdk.forProject.imagine.listMessages(
|
||||
convo.data.artifactId,
|
||||
convo.data.$id
|
||||
);
|
||||
const text = messages.reduce((curr, next) => {
|
||||
return curr + next.content;
|
||||
}, '');
|
||||
parser.init(text);
|
||||
});
|
||||
|
||||
parser.on('complete', async (action) => {
|
||||
switch (action.type) {
|
||||
case 'file':
|
||||
await synapse.dispatch('fs', {
|
||||
operation: 'updateFile',
|
||||
params: {
|
||||
filepath: action.src,
|
||||
content: action.content
|
||||
}
|
||||
});
|
||||
$filesystem = [...$filesystem, action.src];
|
||||
break;
|
||||
case 'shell':
|
||||
synapse.dispatch('terminal', {
|
||||
operation: 'createCommand',
|
||||
params: {
|
||||
command: action.content + '\n'
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Chat {parser} width={chatWidth} hasSubNavigation={false} />
|
||||
|
||||
{#if !$isSmallViewport && ($showChat || isResizing)}
|
||||
<div
|
||||
role="presentation"
|
||||
class="resizer"
|
||||
style:left={`${resizerLeftPosition}px`}
|
||||
class:hidden={!$showChat}
|
||||
bind:this={resizer}
|
||||
onmousedown={startResize}
|
||||
ontouchmove={startResize}>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.resizer {
|
||||
width: 16px;
|
||||
cursor: col-resize;
|
||||
margin-inline: 10px;
|
||||
position: absolute;
|
||||
height: calc(100vh - 72px);
|
||||
margin-block-start: calc(-1 * var(--base-8));
|
||||
margin-inline-start: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
cursor: col-resize;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
margin-left: -1px;
|
||||
left: 8px;
|
||||
background-color: var(--border-neutral);
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
width: 2px;
|
||||
background-color: var(--border-neutral-strong);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Button, Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { isStudio } from '$lib/system';
|
||||
import { page } from '$app/state';
|
||||
import { IconChevronLeft } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let size: 'small' | 'medium' | 'large' | 'xl' = null;
|
||||
export let blocksize = '152px';
|
||||
let {
|
||||
size = null,
|
||||
blocksize = '152px'
|
||||
}: { size?: 'small' | 'medium' | 'large' | 'xl'; blocksize?: string } = $props();
|
||||
|
||||
$: style = size
|
||||
? `--p-container-max-size: var(--container-max-size, var(--container-size-${size}))`
|
||||
: '';
|
||||
let style = $derived.by(() => {
|
||||
return size
|
||||
? `--p-container-max-size: var(--container-max-size, var(--container-size-${size}))`
|
||||
: '';
|
||||
});
|
||||
|
||||
let nestedLevel = $derived.by(() => {
|
||||
return page.url.pathname.split('/').length;
|
||||
});
|
||||
let paramCount = $derived.by(() => {
|
||||
return Object.keys(page.params).length;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if isStudio}
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if nestedLevel > 4}
|
||||
{#if (nestedLevel === 6 && paramCount === 2) || nestedLevel === 7}
|
||||
<a href="../" style:display="flex" style:margin-inline-end="-8px"
|
||||
><Icon icon={IconChevronLeft} /></a>
|
||||
{:else if paramCount >= 2}
|
||||
<a href="./" style:display="flex" style:margin-inline-end="-8px"
|
||||
><Icon icon={IconChevronLeft} /></a>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<slot name="header" />
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
</Button.Anchor>
|
||||
</span>
|
||||
{/if}
|
||||
<Typography.Title truncate color="--fgcolor-neutral-primary" size={$isSmallViewport ? 'm' : 'l'}>
|
||||
<Typography.Title
|
||||
truncate
|
||||
color="--fgcolor-neutral-primary"
|
||||
size={isStudio ? 's' : $isSmallViewport ? 'm' : 'l'}>
|
||||
<slot />
|
||||
</Typography.Title>
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
import { user } from '$lib/stores/user';
|
||||
import SidebarProject from '$lib/components/studio/sidebarProject.svelte';
|
||||
import SidebarOrganization from '$lib/components/studio/sidebarOrganization.svelte';
|
||||
import ChatWrapper from '$lib/components/studio/chatWrapper.svelte';
|
||||
import { BillingPlan } from '$lib/constants.js';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { tierToPlan } from '$lib/stores/billing.js';
|
||||
import type { NavbarProject } from '$lib/components/navbar.svelte';
|
||||
import Chat from '$lib/components/studio/chat/chat.svelte';
|
||||
import {
|
||||
IconChevronRight,
|
||||
IconLogoutRight,
|
||||
@@ -35,18 +35,7 @@
|
||||
import { logout } from '$lib/helpers/logout';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { conversation, showChat } from '$lib/stores/chat';
|
||||
import { createStreamParser } from '$lib/components/studio/chat/parser';
|
||||
import { type Snippet } from 'svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { filesystem } from '$lib/components/editor/filesystem';
|
||||
import { previewFrameRef } from '$routes/(console)/project-[project]/store';
|
||||
import {
|
||||
disableBodySelect,
|
||||
enabledBodySelect,
|
||||
getChatWidthFromPrefs,
|
||||
saveChatWidthToPrefs
|
||||
} from '$lib/helpers/studioLayout';
|
||||
import { synapse } from '$lib/components/studio/synapse.svelte';
|
||||
|
||||
let hasProjectSidebar = $state(false);
|
||||
|
||||
@@ -69,7 +58,9 @@
|
||||
|
||||
$effect(() => {
|
||||
isOnEditorPage =
|
||||
page.url.pathname.includes('studio') && page.url.pathname.includes('artifact');
|
||||
page.url.pathname.includes('studio') &&
|
||||
page.url.pathname.includes('artifact') &&
|
||||
'artifact' in page.params;
|
||||
});
|
||||
|
||||
function updateTheme(theme: 'light' | 'dark' | 'auto') {
|
||||
@@ -114,144 +105,6 @@
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if ($isSmallViewport || page.params.artifact) {
|
||||
showChat.set(true);
|
||||
}
|
||||
if (!page.params.artifact) {
|
||||
showChat.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
let resizer = $state(null);
|
||||
let resizerLeftPosition = $state(
|
||||
page.data?.subNavigation ? getChatWidthFromPrefs() + 24 : getChatWidthFromPrefs()
|
||||
);
|
||||
let resizerLeftOffset = $state(page.data?.subNavigation ? 52 : 9);
|
||||
let chatWidth = $derived(resizerLeftPosition - resizerLeftOffset);
|
||||
|
||||
$effect(() => {
|
||||
chatWidth = resizerLeftPosition - resizerLeftOffset;
|
||||
});
|
||||
|
||||
let isResizing = false;
|
||||
|
||||
function startResize() {
|
||||
isResizing = true;
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
window.addEventListener('touchmove', resize);
|
||||
window.addEventListener('touchend', stopResize);
|
||||
disableBodySelect();
|
||||
|
||||
if ($previewFrameRef) {
|
||||
$previewFrameRef.style.pointerEvents = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function resize(event: TouchEvent | MouseEvent) {
|
||||
if (!isResizing) return;
|
||||
if (resizer) {
|
||||
resizerLeftPosition =
|
||||
('touches' in event ? event.touches[0].clientX : event.clientX) - 60;
|
||||
const maxSize = page.data?.subNavigation
|
||||
? window.innerWidth - 660
|
||||
: window.innerWidth - 540;
|
||||
|
||||
const minSize = 320;
|
||||
if (resizerLeftPosition > maxSize) {
|
||||
resizerLeftPosition = maxSize;
|
||||
} else if (resizerLeftPosition < minSize) {
|
||||
if (resizerLeftPosition < minSize - 100) {
|
||||
showChat.set(false);
|
||||
}
|
||||
|
||||
resizerLeftPosition = minSize;
|
||||
} else if (resizerLeftPosition > minSize && !$showChat) {
|
||||
showChat.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
if (!$showChat) {
|
||||
resizerLeftPosition = 500;
|
||||
}
|
||||
const saveWidth = page.data?.subNavigation ? resizerLeftPosition - 24 : resizerLeftPosition;
|
||||
saveChatWidthToPrefs(saveWidth);
|
||||
|
||||
isResizing = false;
|
||||
window.removeEventListener('mousemove', resize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
window.removeEventListener('touchmove', resize);
|
||||
window.removeEventListener('touchend', stopResize);
|
||||
enabledBodySelect();
|
||||
|
||||
if ($previewFrameRef) {
|
||||
$previewFrameRef.style.pointerEvents = '';
|
||||
}
|
||||
}
|
||||
|
||||
const parser = createStreamParser();
|
||||
|
||||
async function getConversation(artifactId: string) {
|
||||
parser.reset();
|
||||
const { conversations } = await sdk.forProject.imagine.listConversations(artifactId);
|
||||
if (conversations.length === 0) {
|
||||
const convo = sdk.forProject.imagine.createConversation(
|
||||
artifactId,
|
||||
`Conversation ${new Date().getTime()}`
|
||||
);
|
||||
conversation.set(convo);
|
||||
} else {
|
||||
conversation.set(conversations[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (page.params.artifact && $conversation.data?.artifactId !== page.params.artifact) {
|
||||
getConversation(page.params.artifact);
|
||||
}
|
||||
});
|
||||
|
||||
conversation.subscribe(async (convo) => {
|
||||
if (!convo.data) return;
|
||||
const { messages } = await sdk.forProject.imagine.listMessages(
|
||||
convo.data.artifactId,
|
||||
convo.data.$id
|
||||
);
|
||||
let t = 0;
|
||||
for (const message of messages) {
|
||||
parser.chunk(message.content, message.role === 'assistant' ? 'system' : 'user', {
|
||||
silent: true
|
||||
});
|
||||
parser.end();
|
||||
}
|
||||
});
|
||||
|
||||
parser.on('complete', async (action) => {
|
||||
switch (action.type) {
|
||||
case 'file':
|
||||
await synapse.dispatch('fs', {
|
||||
operation: 'updateFile',
|
||||
params: {
|
||||
filepath: action.src,
|
||||
content: action.content
|
||||
}
|
||||
});
|
||||
$filesystem = [...$filesystem, action.src];
|
||||
break;
|
||||
case 'shell':
|
||||
synapse.dispatch('terminal', {
|
||||
operation: 'createCommand',
|
||||
params: {
|
||||
command: action.content + '\n'
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
@@ -373,9 +226,10 @@
|
||||
class:project-sidebar={hasProjectSidebar}
|
||||
class:sub-navigation={page.data.subNavigation}>
|
||||
{#if $isSmallViewport}
|
||||
{#if hasProjectSidebar}
|
||||
<Chat {parser} width={chatWidth} hasSubNavigation={page.data?.subNavigation} />
|
||||
{#if isOnEditorPage}
|
||||
<ChatWrapper />
|
||||
{/if}
|
||||
|
||||
<Card.Base padding="xxs" radius={$isSmallViewport ? 'none' : 'm'}>
|
||||
<Layout.Stack>
|
||||
{#if page.data?.subNavigation}
|
||||
@@ -389,19 +243,8 @@
|
||||
<div class="studio-wrapper">
|
||||
<Card.Base padding={isOnEditorPage ? 'xxs' : 's'}>
|
||||
<Layout.Stack direction="row" gap={$showChat ? 'l' : 'none'}>
|
||||
{#if hasProjectSidebar}
|
||||
<Chat {parser} width={chatWidth} hasSubNavigation={false} />
|
||||
{#if $showChat || isResizing}
|
||||
<div
|
||||
role="presentation"
|
||||
class="resizer"
|
||||
style:left={`${resizerLeftPosition}px`}
|
||||
class:hidden={!$showChat}
|
||||
bind:this={resizer}
|
||||
onmousedown={startResize}
|
||||
ontouchmove={startResize}>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isOnEditorPage}
|
||||
<ChatWrapper />
|
||||
{/if}
|
||||
|
||||
{@const Header = page.data.header}
|
||||
@@ -529,6 +372,12 @@
|
||||
|
||||
.studio-wrapper {
|
||||
position: relative;
|
||||
height: calc(100dvh - 64px);
|
||||
}
|
||||
|
||||
:global(.studio-wrapper > div) {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.studio-content {
|
||||
@@ -554,33 +403,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.resizer {
|
||||
width: 16px;
|
||||
cursor: col-resize;
|
||||
margin-inline: 10px;
|
||||
position: absolute;
|
||||
height: calc(100vh - 72px);
|
||||
margin-block-start: calc(-1 * var(--base-8));
|
||||
margin-inline-start: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
cursor: col-resize;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
margin-left: -1px;
|
||||
left: 8px;
|
||||
background-color: var(--border-neutral);
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
width: 2px;
|
||||
background-color: var(--border-neutral-strong);
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -619,6 +441,7 @@
|
||||
margin-inline-start: -17px;
|
||||
margin-block-start: 6px;
|
||||
border-top-left-radius: var(--border-radius-m);
|
||||
border-bottom-left-radius: var(--border-radius-m);
|
||||
height: calc(100% - 65px) !important;
|
||||
border-left-width: 1px;
|
||||
border-top-width: 1px;
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { Plan } from '$lib/sdk/billing';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.ARTIFACTS);
|
||||
sdk.forProject.project.client.setProject(params.project);
|
||||
|
||||
try {
|
||||
return {
|
||||
|
||||
@@ -172,8 +172,7 @@
|
||||
{#if !$showChat}
|
||||
<Button.Button
|
||||
variant="compact"
|
||||
color="--fgcolor-neutral-secondary"
|
||||
style="--p-button-padding-block:0"
|
||||
style="--p-button-padding-block:0; margin-inline-start: -2px; margin-inline-end: 4px; color:var(--fgcolor-neutral-tertiary)"
|
||||
on:click={() => {
|
||||
showChat.set(true);
|
||||
}}><Icon icon={IconChatLayout} size="l"></Icon></Button.Button>
|
||||
@@ -295,10 +294,10 @@
|
||||
}
|
||||
|
||||
.terminal-tabs {
|
||||
background-color: white;
|
||||
background-color: var(--bgcolor-neutral-primary);
|
||||
padding: var(--space-4) var(--space-4);
|
||||
margin-inline-start: calc(-1 * var(--space-1));
|
||||
width: calc(100% + 1px);
|
||||
margin-inline-start: calc(-1 * var(--space-1) + 1px);
|
||||
width: calc(100%);
|
||||
}
|
||||
|
||||
.divider-wrapper-artifacts {
|
||||
|
||||
Reference in New Issue
Block a user