Skip to content

Commit 2daa934

Browse files
updated design scheme & context for chats
1 parent cf91f57 commit 2daa934

File tree

9 files changed

+217
-116
lines changed

9 files changed

+217
-116
lines changed

.prettierignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
pnpm-lock.yaml
2-
.astro

app/components/@settings/tabs/event-logs/EventLogsTab.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,7 @@ const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp
228228
<div className="text-sm font-medium text-gray-900 dark:text-white">{log.message}</div>
229229
{log.details && (
230230
<>
231-
<button
232-
onClick={() => setLocalExpanded(!localExpanded)}
233-
className="text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
234-
>
231+
<button onClick={() => setLocalExpanded(!localExpanded)}>
235232
{localExpanded ? 'Hide' : 'Show'} Details
236233
</button>
237234
{localExpanded && renderDetails(log.details)}

app/components/chat/AssistantMessage.tsx

Lines changed: 35 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { memo, Fragment } from 'react';
1+
import { memo } from 'react';
22
import { Markdown } from './Markdown';
33
import type { JSONValue } from 'ai';
4-
import Popover from '~/components/ui/Popover';
5-
import { workbenchStore } from '~/lib/stores/workbench';
6-
import { WORK_DIR } from '~/utils/constants';
4+
import { ContextIndicator } from './ContextIndicator';
75
import WithTooltip from '~/components/ui/Tooltip';
86

97
interface AssistantMessageProps {
@@ -14,30 +12,6 @@ interface AssistantMessageProps {
1412
onFork?: (messageId: string) => void;
1513
}
1614

17-
function openArtifactInWorkbench(filePath: string) {
18-
filePath = normalizedFilePath(filePath);
19-
20-
if (workbenchStore.currentView.get() !== 'code') {
21-
workbenchStore.currentView.set('code');
22-
}
23-
24-
workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);
25-
}
26-
27-
function normalizedFilePath(path: string) {
28-
let normalizedPath = path;
29-
30-
if (normalizedPath.startsWith(WORK_DIR)) {
31-
normalizedPath = path.replace(WORK_DIR, '');
32-
}
33-
34-
if (normalizedPath.startsWith('/')) {
35-
normalizedPath = normalizedPath.slice(1);
36-
}
37-
38-
return normalizedPath;
39-
}
40-
4115
export const AssistantMessage = memo(({ content, annotations, messageId, onRewind, onFork }: AssistantMessageProps) => {
4216
const filteredAnnotations = (annotations?.filter(
4317
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
@@ -63,78 +37,41 @@ export const AssistantMessage = memo(({ content, annotations, messageId, onRewin
6337

6438
return (
6539
<div className="overflow-hidden w-full">
66-
<>
67-
<div className=" flex gap-2 items-center text-sm text-codinit-elements-textSecondary mb-2">
68-
{(codeContext || chatSummary) && (
69-
<Popover side="right" align="start" trigger={<div className="i-ph:info" />}>
70-
{chatSummary && (
71-
<div className="max-w-chat">
72-
<div className="summary max-h-96 flex flex-col">
73-
<h2 className="border border-codinit-elements-borderColor rounded-md p4">Summary</h2>
74-
<div style={{ zoom: 0.7 }} className="overflow-y-auto m4">
75-
<Markdown>{chatSummary}</Markdown>
76-
</div>
77-
</div>
78-
{codeContext && (
79-
<div className="code-context flex flex-col p4 border border-codinit-elements-borderColor rounded-md">
80-
<h2>Context</h2>
81-
<div className="flex gap-4 mt-4 codinit" style={{ zoom: 0.6 }}>
82-
{codeContext.map((x) => {
83-
const normalized = normalizedFilePath(x);
84-
return (
85-
<Fragment key={normalized}>
86-
<code
87-
className="bg-codinit-elements-artifacts-inlineCode-background text-codinit-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md text-codinit-elements-item-contentAccent hover:underline cursor-pointer"
88-
onClick={(e) => {
89-
e.preventDefault();
90-
e.stopPropagation();
91-
openArtifactInWorkbench(normalized);
92-
}}
93-
>
94-
{normalized}
95-
</code>
96-
</Fragment>
97-
);
98-
})}
99-
</div>
100-
</div>
101-
)}
102-
</div>
103-
)}
104-
<div className="context"></div>
105-
</Popover>
40+
{(codeContext || chatSummary || usage) && (
41+
<ContextIndicator
42+
files={codeContext}
43+
summary={chatSummary}
44+
tokenCount={
45+
usage
46+
? {
47+
prompt: usage.promptTokens,
48+
completion: usage.completionTokens,
49+
total: usage.totalTokens,
50+
}
51+
: undefined
52+
}
53+
/>
54+
)}
55+
{(onRewind || onFork) && messageId && (
56+
<div className="flex gap-2 mb-2 justify-end">
57+
{onRewind && (
58+
<WithTooltip tooltip="Revert to this message">
59+
<button
60+
onClick={() => onRewind(messageId)}
61+
className="i-ph:arrow-u-up-left text-lg text-codinit-elements-textSecondary hover:text-codinit-elements-textPrimary transition-colors"
62+
/>
63+
</WithTooltip>
64+
)}
65+
{onFork && (
66+
<WithTooltip tooltip="Fork chat from this message">
67+
<button
68+
onClick={() => onFork(messageId)}
69+
className="i-ph:git-fork text-lg text-codinit-elements-textSecondary hover:text-codinit-elements-textPrimary transition-colors"
70+
/>
71+
</WithTooltip>
10672
)}
107-
<div className="flex w-full items-center justify-between">
108-
{usage && (
109-
<div>
110-
Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
111-
</div>
112-
)}
113-
{(onRewind || onFork) && messageId && (
114-
<div className="flex gap-2 flex-col lg:flex-row ml-auto">
115-
{onRewind && (
116-
<WithTooltip tooltip="Revert to this message">
117-
<button
118-
onClick={() => onRewind(messageId)}
119-
key="i-ph:arrow-u-up-left"
120-
className="i-ph:arrow-u-up-left text-xl text-codinit-elements-textSecondary hover:text-codinit-elements-textPrimary transition-colors"
121-
/>
122-
</WithTooltip>
123-
)}
124-
{onFork && (
125-
<WithTooltip tooltip="Fork chat from this message">
126-
<button
127-
onClick={() => onFork(messageId)}
128-
key="i-ph:git-fork"
129-
className="i-ph:git-fork text-xl text-codinit-elements-textSecondary hover:text-codinit-elements-textPrimary transition-colors"
130-
/>
131-
</WithTooltip>
132-
)}
133-
</div>
134-
)}
135-
</div>
13673
</div>
137-
</>
74+
)}
13875
<Markdown html>{content}</Markdown>
13976
</div>
14077
);

app/components/chat/BaseChat.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ChatHeader } from '~/components/header/ChatHeader';
1616
import { PreviewHeader } from '~/components/workbench/PreviewHeader';
1717
import { CodeModeHeader } from '~/components/workbench/CodeModeHeader';
1818
import { workbenchStore } from '~/lib/stores/workbench';
19+
import { TextShimmer } from '~/components/ui/text-shimmer';
1920
import { classNames } from '~/utils/classNames';
2021
import { PROVIDER_LIST } from '~/utils/constants';
2122
import { Messages } from './Messages.client';
@@ -632,7 +633,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
632633
<span className="text-codinit-elements-textPrimary"> Deploy</span>
633634
</h1>
634635
<p className="text-md lg:text-xl mb-8 text-codinit-elements-textSecondary animate-fade-in animation-delay-200">
635-
Let your imagination build your next startup idea.
636+
<TextShimmer>Let your imagination build your next startup idea</TextShimmer>
636637
</p>
637638
</div>
638639
)}

app/components/chat/ColorSchemeDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export const ColorSchemeDialog: React.FC<ColorSchemeDialogProps> = ({ setDesignS
136136
case 'none':
137137
return 'none';
138138
case 'sm':
139-
return '0 1px 2px 0 rgb(0 0 0 / 0.05)';
139+
return '0 1px 2px 0 rgb(0 0 0 / 0.1)';
140140
case 'md':
141141
return '0 4px 6px -1px rgb(0 0 0 / 0.1)';
142142
case 'lg':
@@ -913,7 +913,7 @@ export const ColorSchemeDialog: React.FC<ColorSchemeDialogProps> = ({ setDesignS
913913
className="py-4"
914914
style={{ borderBottom: `1px solid ${palette[mode].accent}` }}
915915
>
916-
<button className="flex w-full items-center justify-between text-left">
916+
<button>
917917
<span
918918
className="text-sm font-medium"
919919
style={{ color: palette[mode].primary, fontFamily: font.join(', ') }}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { memo, useState } from 'react';
2+
import { workbenchStore } from '~/lib/stores/workbench';
3+
import { WORK_DIR } from '~/utils/constants';
4+
import WithTooltip from '~/components/ui/Tooltip';
5+
import { motion, AnimatePresence } from 'framer-motion';
6+
7+
interface ContextIndicatorProps {
8+
files?: string[];
9+
summary?: string;
10+
tokenCount?: {
11+
prompt: number;
12+
completion: number;
13+
total: number;
14+
};
15+
}
16+
17+
function normalizedFilePath(path: string) {
18+
let normalizedPath = path;
19+
20+
if (normalizedPath.startsWith(WORK_DIR)) {
21+
normalizedPath = path.replace(WORK_DIR, '');
22+
}
23+
24+
if (normalizedPath.startsWith('/')) {
25+
normalizedPath = normalizedPath.slice(1);
26+
}
27+
28+
return normalizedPath;
29+
}
30+
31+
function openFileInWorkbench(filePath: string) {
32+
const normalized = normalizedFilePath(filePath);
33+
34+
if (workbenchStore.currentView.get() !== 'code') {
35+
workbenchStore.currentView.set('code');
36+
}
37+
38+
workbenchStore.setSelectedFile(`${WORK_DIR}/${normalized}`);
39+
}
40+
41+
function getFileIcon(filePath: string): string {
42+
const ext = filePath.split('.').pop()?.toLowerCase();
43+
44+
switch (ext) {
45+
case 'ts':
46+
case 'tsx':
47+
return 'i-vscode-icons:file-type-typescript';
48+
case 'js':
49+
case 'jsx':
50+
return 'i-vscode-icons:file-type-js';
51+
case 'css':
52+
return 'i-vscode-icons:file-type-css';
53+
case 'html':
54+
return 'i-vscode-icons:file-type-html';
55+
case 'json':
56+
return 'i-vscode-icons:file-type-json';
57+
case 'md':
58+
return 'i-vscode-icons:file-type-markdown';
59+
case 'sql':
60+
return 'i-vscode-icons:file-type-sql';
61+
default:
62+
return 'i-ph:file';
63+
}
64+
}
65+
66+
export const ContextIndicator = memo(({ files, summary, tokenCount }: ContextIndicatorProps) => {
67+
const [isExpanded, setIsExpanded] = useState(false);
68+
69+
if (!files?.length && !summary) {
70+
return null;
71+
}
72+
73+
const fileCount = files?.length || 0;
74+
75+
return (
76+
<div className="context-indicator mb-3">
77+
<button
78+
onClick={() => setIsExpanded(!isExpanded)}
79+
className="flex items-center gap-2 text-xs transition-colors"
80+
style={{ color: 'var(--codinit-elements-textSecondary)' }}
81+
onMouseEnter={(e) => (e.currentTarget.style.color = 'var(--codinit-elements-textPrimary)')}
82+
onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--codinit-elements-textSecondary)')}
83+
>
84+
<span className={`i-ph:caret-${isExpanded ? 'down' : 'right'} text-sm`} style={{ color: 'inherit' }} />
85+
<span className="i-ph:files text-sm" style={{ color: 'inherit' }} />
86+
<span>
87+
{fileCount} file{fileCount !== 1 ? 's' : ''} in context
88+
</span>
89+
{summary && (
90+
<>
91+
<span className="i-ph:chat-circle-text text-sm" style={{ color: 'inherit' }} />
92+
<span>Summarized</span>
93+
</>
94+
)}
95+
{tokenCount && (
96+
<>
97+
<span style={{ color: 'var(--codinit-elements-borderColor)' }}></span>
98+
<span className="i-ph:coins text-sm" style={{ color: 'inherit' }} />
99+
<span>{tokenCount.total.toLocaleString()} tokens</span>
100+
</>
101+
)}
102+
</button>
103+
104+
<AnimatePresence>
105+
{isExpanded && (
106+
<motion.div
107+
initial={{ height: 0, opacity: 0 }}
108+
animate={{ height: 'auto', opacity: 1 }}
109+
exit={{ height: 0, opacity: 0 }}
110+
transition={{ duration: 0.2 }}
111+
className="overflow-hidden"
112+
>
113+
<div
114+
className="mt-2 p-3 rounded-lg border border-codinit-elements-borderColor"
115+
style={{ backgroundColor: 'var(--codinit-elements-bg-depth-2)' }}
116+
>
117+
{files && files.length > 0 && (
118+
<div className="mb-3">
119+
<div className="text-xs font-medium text-codinit-elements-textSecondary mb-2">Files included:</div>
120+
<div className="flex flex-wrap gap-1.5">
121+
{files.map((file) => {
122+
const normalized = normalizedFilePath(file);
123+
const fileName = normalized.split('/').pop() || normalized;
124+
125+
return (
126+
<WithTooltip key={normalized} tooltip={normalized}>
127+
<button
128+
onClick={() => openFileInWorkbench(file)}
129+
className="inline-flex items-center gap-1 px-2 py-1 text-xs text-codinit-elements-textPrimary rounded hover:opacity-80 transition-colors"
130+
style={{ backgroundColor: 'var(--codinit-elements-bg-depth-3)' }}
131+
>
132+
<span className={`${getFileIcon(normalized)} text-sm`} />
133+
<span className="max-w-32 truncate">{fileName}</span>
134+
</button>
135+
</WithTooltip>
136+
);
137+
})}
138+
</div>
139+
</div>
140+
)}
141+
142+
{summary && (
143+
<div>
144+
<div className="text-xs font-medium text-codinit-elements-textSecondary mb-2">
145+
Conversation summary:
146+
</div>
147+
<div
148+
className="text-xs text-codinit-elements-textPrimary p-2 rounded max-h-32 overflow-y-auto"
149+
style={{ backgroundColor: 'var(--codinit-elements-bg-depth-3)' }}
150+
>
151+
{summary.length > 300 ? `${summary.substring(0, 300)}...` : summary}
152+
</div>
153+
</div>
154+
)}
155+
156+
{tokenCount && (
157+
<div className="mt-3 pt-3 border-t border-codinit-elements-borderColor">
158+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-codinit-elements-textSecondary">
159+
<span>Prompt: {tokenCount.prompt.toLocaleString()}</span>
160+
<span>Completion: {tokenCount.completion.toLocaleString()}</span>
161+
<span className="font-medium text-codinit-elements-textPrimary">
162+
Total: {tokenCount.total.toLocaleString()}
163+
</span>
164+
</div>
165+
</div>
166+
)}
167+
</div>
168+
</motion.div>
169+
)}
170+
</AnimatePresence>
171+
</div>
172+
);
173+
});

app/components/chat/StarterTemplates.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import type { Template } from '~/types/template';
33
import { STARTER_TEMPLATES } from '~/utils/constants';
4+
import { TextShimmer } from '~/components/ui/text-shimmer';
45

56
interface FrameworkLinkProps {
67
template: Template;
@@ -23,7 +24,9 @@ const FrameworkLink: React.FC<FrameworkLinkProps> = ({ template }) => (
2324
const StarterTemplates: React.FC = () => {
2425
return (
2526
<div className="flex flex-col items-center gap-4">
26-
<span className="text-sm text-gray-500 mt-6">Please select your favourite stack to begin...</span>
27+
<span className="text-sm text-gray-500 mt-6">
28+
<TextShimmer>Please select your favourite stack to begin...</TextShimmer>
29+
</span>
2730
<div className="flex justify-center">
2831
<div className="flex flex-wrap justify-center items-center gap-4 max-w-sm">
2932
{STARTER_TEMPLATES.map((template) => (

0 commit comments

Comments
 (0)