FrederickSundeep commited on
Commit
c3a0082
·
1 Parent(s): cb9ad57

commit 0001

Browse files
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,12 +1,17 @@
1
  {
2
- "name": "react-template",
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
 
 
 
 
6
  "@testing-library/dom": "^10.4.0",
7
  "@testing-library/jest-dom": "^6.6.3",
8
  "@testing-library/react": "^16.3.0",
9
  "@testing-library/user-event": "^13.5.0",
 
10
  "react": "^19.1.0",
11
  "react-dom": "^19.1.0",
12
  "react-scripts": "5.0.1",
@@ -35,5 +40,9 @@
35
  "last 1 firefox version",
36
  "last 1 safari version"
37
  ]
 
 
 
 
38
  }
39
  }
 
1
  {
2
+ "name": "chat-mate",
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
6
+ "@emotion/react": "^11.14.0",
7
+ "@emotion/styled": "^11.14.1",
8
+ "@mui/icons-material": "^7.2.0",
9
+ "@mui/material": "^7.2.0",
10
  "@testing-library/dom": "^10.4.0",
11
  "@testing-library/jest-dom": "^6.6.3",
12
  "@testing-library/react": "^16.3.0",
13
  "@testing-library/user-event": "^13.5.0",
14
+ "prismjs": "^1.30.0",
15
  "react": "^19.1.0",
16
  "react-dom": "^19.1.0",
17
  "react-scripts": "5.0.1",
 
40
  "last 1 firefox version",
41
  "last 1 safari version"
42
  ]
43
+ },
44
+ "devDependencies": {
45
+ "@types/react": "^19.1.8",
46
+ "@types/react-dom": "^19.1.6"
47
  }
48
  }
public/index.html CHANGED
@@ -24,7 +24,7 @@
24
  work correctly both with client-side routing and a non-root public URL.
25
  Learn how to configure a non-root public URL by running `npm run build`.
26
  -->
27
- <title>React App</title>
28
  </head>
29
  <body>
30
  <noscript>You need to enable JavaScript to run this app.</noscript>
 
24
  work correctly both with client-side routing and a non-root public URL.
25
  Learn how to configure a non-root public URL by running `npm run build`.
26
  -->
27
+ <title>ChatMate App</title>
28
  </head>
29
  <body>
30
  <noscript>You need to enable JavaScript to run this app.</noscript>
src/App.css CHANGED
@@ -1,38 +1,186 @@
1
- .App {
2
- text-align: center;
 
 
 
 
 
 
 
 
 
3
  }
4
 
5
- .App-logo {
6
- height: 40vmin;
7
- pointer-events: none;
 
8
  }
9
 
10
- @media (prefers-reduced-motion: no-preference) {
11
- .App-logo {
12
- animation: App-logo-spin infinite 20s linear;
13
- }
 
 
 
 
 
 
 
14
  }
15
 
16
- .App-header {
17
- background-color: #282c34;
18
- min-height: 100vh;
19
- display: flex;
 
 
 
 
 
 
20
  flex-direction: column;
21
- align-items: center;
22
- justify-content: center;
23
- font-size: calc(10px + 2vmin);
24
- color: white;
 
 
 
 
 
 
 
25
  }
26
 
27
- .App-link {
28
- color: #61dafb;
 
 
 
 
29
  }
30
 
31
- @keyframes App-logo-spin {
32
- from {
33
- transform: rotate(0deg);
34
- }
35
- to {
36
- transform: rotate(360deg);
37
- }
38
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* App.css */
2
+ .chat-container {
3
+ max-width: 800px;
4
+ margin: auto;
5
+ display: flex;
6
+ flex-direction: column;
7
+ height: 100vh;
8
+ color: #ffffff;
9
+ background: #2a2a2a;
10
+ border-radius: 12px;
11
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
12
  }
13
 
14
+ body {
15
+ font-family: 'Roboto', sans-serif;
16
+ background: #121212;
17
+ color: #ffffff;
18
  }
19
 
20
+ h2 {
21
+
22
+ color: #bb86fc;
23
+ margin-bottom: 20px;
24
+ font-weight: 500;
25
+ }
26
+ .header-area {
27
+ display: flex;
28
+ justify-content: center;
29
+ text-align: center;
30
+ background: #383838;
31
  }
32
 
33
+ .chat-box {
34
+ flex-grow: 1;
35
+ max-height: 420px;
36
+ overflow-y: auto;
37
+ padding: 12px;
38
+ border: 1px solid #2d2d2d;
39
+ border-radius: 8px;
40
+ background: #2a2a2a;
41
+ margin-bottom: 16px;
42
+ display: flex;
43
  flex-direction: column;
44
+ }
45
+
46
+ .message {
47
+ display: block; /* ensures full-width layout */
48
+ width: fit-content;
49
+ max-width: 85%;
50
+ margin-bottom: 12px;
51
+ padding: 10px 14px;
52
+ border-radius: 16px;
53
+ line-height: 1.5;
54
+ word-wrap: break-word;
55
  }
56
 
57
+ .message.user {
58
+ align-self: flex-end;
59
+ margin-left: auto;
60
+ background: #333c4d;
61
+ color: #e3f2fd;
62
+ border-bottom-right-radius: 0;
63
  }
64
 
65
+ .message.assistant {
66
+ align-self: flex-start;
67
+ margin-right: auto;
68
+ background: #383838;
69
+ border-bottom-left-radius: 0;
 
 
70
  }
71
+
72
+ .input-area {
73
+ display: flex;
74
+ padding: 10px;
75
+ background: #383838;
76
+ }
77
+
78
+ textarea {
79
+ flex-grow: 1;
80
+ padding: 10px 12px;
81
+ resize: none;
82
+ border-radius: 6px;
83
+ background: #2a2a2a;
84
+ color: #fff;
85
+ border: 1px solid #444;
86
+ font-size: 14px;
87
+ }
88
+
89
+ button {
90
+ margin-left: 10px;
91
+ padding: 8px 16px;
92
+ }
93
+
94
+ .timestamp {
95
+ font-size: 0.75em;
96
+ color: #888;
97
+ margin-top: 4px;
98
+ }
99
+
100
+ .formatted-text {
101
+ white-space: pre-wrap;
102
+ line-height: 1.5;
103
+ margin-bottom: 8px;
104
+ }
105
+
106
+ .language-label {
107
+ font-weight: bold;
108
+ font-size: 12px;
109
+ padding: 4px 0;
110
+ }
111
+ pre.line-numbers {
112
+ padding-left: 3.8em; /* ensure room for line numbers */
113
+ }
114
+
115
+ .code-block-wrapper pre {
116
+ background: #2d2d2d; /* override if needed */
117
+ padding: 1em;
118
+ font-size: 0.9em;
119
+ line-height: 1.5;
120
+ }
121
+
122
+ .code-block-wrapper {
123
+ margin: 12px 0;
124
+ background-color: #2d2d2d;
125
+ border-radius: 8px;
126
+ overflow: auto;
127
+ }
128
+
129
+
130
+ .typing-indicator {
131
+ display: flex;
132
+ gap: 6px;
133
+ margin: 6px 0 12px;
134
+ }
135
+
136
+ .dot {
137
+ width: 8px;
138
+ height: 8px;
139
+ background-color: #bb86fc;
140
+ border-radius: 50%;
141
+ animation: blink 1.4s infinite both;
142
+ }
143
+
144
+ .dot:nth-child(2) {
145
+ animation-delay: 0.2s;
146
+ }
147
+
148
+ .dot:nth-child(3) {
149
+ animation-delay: 0.4s;
150
+ }
151
+
152
+ @keyframes blink {
153
+ 0%, 80%, 100% { opacity: 0.2; }
154
+ 40% { opacity: 1; }
155
+ }
156
+
157
+
158
+ /* Base style — minimal/invisible scrollbar */
159
+ * {
160
+ scrollbar-width: thin; /* Firefox */
161
+ scrollbar-color: transparent transparent;
162
+ }
163
+
164
+ /* Webkit browsers (Chrome, Edge, Safari) */
165
+ *::-webkit-scrollbar {
166
+ width: 4px;
167
+ }
168
+
169
+ *::-webkit-scrollbar-track {
170
+ background: transparent;
171
+ }
172
+
173
+ *::-webkit-scrollbar-thumb {
174
+ background-color: transparent;
175
+ border-radius: 8px;
176
+ transition: background-color 0.3s ease;
177
+ }
178
+
179
+ /* On hover — visible scrollbar */
180
+ *:hover::-webkit-scrollbar-thumb {
181
+ background-color: rgba(100, 100, 100, 0.5);
182
+ }
183
+
184
+ *:hover {
185
+ scrollbar-color: rgba(100, 100, 100, 0.5) transparent;
186
+ }
src/App.js CHANGED
@@ -1,25 +1,10 @@
1
- import logo from './logo.svg';
 
 
2
  import './App.css';
3
 
4
  function App() {
5
- return (
6
- <div className="App">
7
- <header className="App-header">
8
- <img src={logo} className="App-logo" alt="logo" />
9
- <p>
10
- Edit <code>src/App.js</code> and save to reload.
11
- </p>
12
- <a
13
- className="App-link"
14
- href="https://reactjs.org"
15
- target="_blank"
16
- rel="noopener noreferrer"
17
- >
18
- Learn React
19
- </a>
20
- </header>
21
- </div>
22
- );
23
  }
24
 
25
  export default App;
 
1
+ // App.js
2
+ import React from 'react';
3
+ import ChatApp from './components/ChatApp';
4
  import './App.css';
5
 
6
  function App() {
7
+ return <ChatApp />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  }
9
 
10
  export default App;
src/components/ChatApp.jsx ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // components/ChatApp.jsx
2
+ import React, { useState, useRef, useEffect } from 'react';
3
+ import MessageBubble from './MessageBubble';
4
+ import { Button , Typography, Box} from '@mui/material';
5
+ import { ChatBubbleOutline } from '@mui/icons-material';
6
+ export default function ChatApp() {
7
+ const [message, setMessage] = useState('');
8
+ const [history, setHistory] = useState([]);
9
+ const [loading, setLoading] = useState(false);
10
+ const chatEndRef = useRef(null);
11
+
12
+ const examples = [
13
+ "What is Python?",
14
+ "Write a JavaScript function to reverse a string.",
15
+ "Explain how transformers work.",
16
+ ];
17
+
18
+ const scrollToBottom = () => {
19
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
20
+ };
21
+
22
+ useEffect(scrollToBottom, [history]);
23
+
24
+ const sendMessage = async () => {
25
+ if (!message.trim()) return;
26
+
27
+ const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
28
+
29
+ const newHistory = [...history, { role: 'user', content: message, time }];
30
+ setHistory(newHistory);
31
+ setMessage('');
32
+ setLoading(true);
33
+
34
+ //const assistant = { role: 'assistant', content: '', time };
35
+ //setHistory(h => [...h, assistant]);
36
+
37
+ const response = await fetch('https://fredericksundeep-aichatmate.hf.space/chat-stream', {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({ message, history: newHistory }),
41
+ });
42
+
43
+ const reader = response.body.getReader();
44
+ const decoder = new TextDecoder();
45
+ let done = false;
46
+ let content = '';
47
+
48
+ while (!done) {
49
+ const { value, done: isDone } = await reader.read();
50
+ if (value) {
51
+ const chunk = decoder.decode(value);
52
+ content += chunk;
53
+
54
+ const currentContent = content; // capture safe reference
55
+ setHistory(h =>
56
+ h.map((msg, i) =>
57
+ i === newHistory.length
58
+ ? { ...msg, content: currentContent }
59
+ : msg
60
+ )
61
+ );
62
+ }
63
+ done = isDone;
64
+ }
65
+ setHistory(h => [...h, { role: 'assistant', content, time }]);
66
+ setLoading(false);
67
+ };
68
+
69
+ return (
70
+ <div className="chat-container">
71
+ <div className="header-area">
72
+ <Box display="flex" alignItems="center" gap={1} mb={2}>
73
+ <ChatBubbleOutline color="primary" />
74
+ <Typography variant="h5" component="h2">
75
+ Chat Mate
76
+ </Typography>
77
+ </Box>
78
+ </div>
79
+ <div className="chat-box">
80
+ {history.length === 0 && !loading && (
81
+ <Box
82
+ display="flex"
83
+ flexDirection="column"
84
+ alignItems="center"
85
+ gap={2}
86
+ pt={8}
87
+ >
88
+ {examples.map((example, i) => (
89
+ <Button
90
+ key={i}
91
+ variant="outlined"
92
+ onClick={() => {
93
+ const selected = example;
94
+ setMessage(selected);
95
+ setTimeout(() => {
96
+ sendMessage(selected);
97
+ }, 50);
98
+ }}
99
+ sx={{
100
+ color: '#fff',
101
+ borderColor: '#444',
102
+ backgroundColor: '#1e1e2f',
103
+ borderRadius: '12px',
104
+ width: '70%',
105
+ fontSize: '1rem',
106
+ fontWeight: 400,
107
+ textTransform: 'none',
108
+ '&:hover': {
109
+ backgroundColor: '#2c2c3e',
110
+ borderColor: '#666',
111
+ },
112
+ }}
113
+ >
114
+ {example}
115
+ </Button>
116
+ ))}
117
+ </Box>
118
+ )}
119
+
120
+ {history.map((msg, i) => (
121
+ <MessageBubble key={i} {...msg} />
122
+ ))}
123
+
124
+ {loading && (
125
+ <div className="typing-indicator">
126
+ <span className="dot"></span><span className="dot"></span><span className="dot"></span>
127
+ </div>
128
+ )}
129
+
130
+ <div ref={chatEndRef} />
131
+ </div>
132
+ <div className="input-area">
133
+ <textarea
134
+ value={message}
135
+ rows={2}
136
+ onChange={(e) => setMessage(e.target.value)}
137
+ placeholder="Ask something..."
138
+ />
139
+ <Button variant="contained"
140
+ color="primary"
141
+ sx={{ ml: 1, px: 2, py: 1 }} disabled={!message.trim() || loading} onClick={sendMessage}>Send</Button>
142
+ </div>
143
+ </div>
144
+ );
145
+ }
src/components/CodeBlock.jsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useRef } from 'react';
2
+ import Prism from 'prismjs';
3
+ import { detectLanguage } from '../utils/languageDetect';
4
+
5
+ // Prism core styles and plugins
6
+ import 'prismjs/themes/prism-tomorrow.css';
7
+ import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
8
+ import 'prismjs/plugins/line-numbers/prism-line-numbers';
9
+
10
+ // Required languages
11
+ import 'prismjs/components/prism-clike';
12
+ import 'prismjs/components/prism-markup-templating';
13
+ import 'prismjs/components/prism-python';
14
+ import 'prismjs/components/prism-javascript';
15
+ import 'prismjs/components/prism-typescript';
16
+ import 'prismjs/components/prism-java';
17
+ import 'prismjs/components/prism-c';
18
+ import 'prismjs/components/prism-cpp';
19
+ import 'prismjs/components/prism-bash';
20
+ import 'prismjs/components/prism-shell-session';
21
+ import 'prismjs/components/prism-sql';
22
+ import 'prismjs/components/prism-markup'; // HTML
23
+ import 'prismjs/components/prism-css';
24
+ import 'prismjs/components/prism-go';
25
+ import 'prismjs/components/prism-php';
26
+ import 'prismjs/components/prism-ruby';
27
+ import 'prismjs/components/prism-kotlin';
28
+ import 'prismjs/components/prism-swift';
29
+ import 'prismjs/components/prism-rust';
30
+ import 'prismjs/components/prism-scala';
31
+ import 'prismjs/components/prism-dart';
32
+
33
+ export default function CodeBlock({ content }) {
34
+ const codeRef = useRef(null);
35
+
36
+
37
+
38
+ const lines = content.split('\n');
39
+ const firstLine = lines[0].trim();
40
+ const hasLang = /^[a-zA-Z]+$/.test(firstLine);
41
+ const lang = hasLang ? firstLine.toLowerCase() : detectLanguage(content) || 'markdown';
42
+ const code = hasLang ? lines.slice(1).join('\n') : content;
43
+ //const escapedCode = escapeHTML(code);
44
+
45
+ useEffect(() => {
46
+ if (codeRef.current) {
47
+ Prism.highlightElement(codeRef.current);
48
+ }
49
+ }, [code, lang]);
50
+
51
+ const handleCopy = () => {
52
+ navigator.clipboard.writeText(code).then(() => {
53
+ alert('Copied to clipboard!');
54
+ });
55
+ };
56
+
57
+ return (
58
+ <div className="code-block-wrapper" style={{ position: 'relative', marginBottom: '1rem' }}>
59
+ <button
60
+ onClick={handleCopy}
61
+ style={{
62
+ position: 'absolute',
63
+ right: '10px',
64
+ top: '10px',
65
+ zIndex: 5,
66
+ fontSize: '12px',
67
+ padding: '4px 8px',
68
+ cursor: 'pointer',
69
+ }}
70
+ >
71
+ Copy
72
+ </button>
73
+ <div style={{ fontSize: '13px', color: '#ccc', paddingBottom: '5px' }}>
74
+ {lang.toUpperCase()}
75
+ </div>
76
+ <pre
77
+ className={`line-numbers language-${lang}`}
78
+ style={{ borderRadius: '8px', overflowX: 'auto' }}
79
+ >
80
+ <code
81
+ ref={codeRef}
82
+ className={`language-${lang}`}
83
+ >
84
+ {code}
85
+ </code>
86
+ </pre>
87
+ </div>
88
+ );
89
+
90
+ }
src/components/MessageBubble.jsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import CodeBlock from './CodeBlock';
3
+
4
+ const formatText = (text) => {
5
+ return text
6
+ .replace(/\n/g, "<br>")
7
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
8
+ .replace(/\*(.*?)\*/g, "<em>$1</em>");
9
+ };
10
+
11
+ export default function MessageBubble({ role, content, time }) {
12
+ return (
13
+ <div className={`message ${role}`}>
14
+ <div className="bubble">
15
+ <FormattedContent content={content} />
16
+ <div className="timestamp">{time}</div>
17
+ </div>
18
+ </div>
19
+ );
20
+ }
21
+
22
+ function FormattedContent({ content }) {
23
+ const blocks = content.split('```');
24
+
25
+ return (
26
+ <>
27
+ {blocks.map((block, i) =>
28
+ i % 2 === 1 ? (
29
+ <CodeBlock key={i} content={block} />
30
+ ) : (
31
+ <div
32
+ key={i}
33
+ className="formatted-text"
34
+ dangerouslySetInnerHTML={{ __html: formatText(block) }}
35
+ />
36
+ )
37
+ )}
38
+ </>
39
+ );
40
+ }
src/components/ThemeProviderWrapper.jsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // components/ThemeProviderWrapper.jsx
2
+ import React, { useMemo, useState, createContext } from 'react';
3
+ import { ThemeProvider, createTheme, CssBaseline } from '@mui/material';
4
+
5
+ export const ColorModeContext = createContext({ toggleColorMode: () => {} });
6
+
7
+ export default function ThemeProviderWrapper({ children }) {
8
+ const [mode, setMode] = useState('light');
9
+
10
+ const colorMode = useMemo(() => ({
11
+ toggleColorMode: () => {
12
+ setMode(prev => (prev === 'light' ? 'dark' : 'light'));
13
+ }
14
+ }), []);
15
+
16
+ const theme = useMemo(() =>
17
+ createTheme({
18
+ palette: {
19
+ mode,
20
+ primary: { main: '#1976d2' },
21
+ secondary: { main: '#9c27b0' }
22
+ }
23
+ }), [mode]);
24
+
25
+ return (
26
+ <ColorModeContext.Provider value={colorMode}>
27
+ <ThemeProvider theme={theme}>
28
+ <CssBaseline />
29
+ {children}
30
+ </ThemeProvider>
31
+ </ColorModeContext.Provider>
32
+ );
33
+ }
src/index.js CHANGED
@@ -3,6 +3,8 @@ import ReactDOM from 'react-dom/client';
3
  import './index.css';
4
  import App from './App';
5
  import reportWebVitals from './reportWebVitals';
 
 
6
 
7
  const root = ReactDOM.createRoot(document.getElementById('root'));
8
  root.render(
 
3
  import './index.css';
4
  import App from './App';
5
  import reportWebVitals from './reportWebVitals';
6
+ import 'prismjs/themes/prism-tomorrow.css';
7
+ import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
8
 
9
  const root = ReactDOM.createRoot(document.getElementById('root'));
10
  root.render(
src/styles/prism-lines.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ prism-lines.css
2
+ pre.line-numbers {
3
+ padding-left: 3.8em;
4
+ position: relative;
5
+ counter-reset: linenumber;
6
+ }
7
+
8
+ pre.line-numbers > code {
9
+ position: relative;
10
+ white-space: pre-wrap;
11
+ }
12
+
13
+ .line-numbers .line-numbers-rows {
14
+ position: absolute;
15
+ pointer-events: none;
16
+ top: 0;
17
+ font-size: 100%;
18
+ left: 0;
19
+ width: 3em;
20
+ letter-spacing: -1px;
21
+ border-right: 1px solid #999;
22
+ user-select: none;
23
+ padding: 0;
24
+ }
25
+
26
+ .line-numbers-rows > span {
27
+ display: block;
28
+ counter-increment: linenumber;
29
+ padding-left: 0.5em;
30
+ }
31
+
32
+ .line-numbers-rows > span:before {
33
+ content: counter(linenumber);
34
+ display: block;
35
+ color: #999;
36
+ }
src/utils/languageDetect.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // utils/languageDetect.js
2
+
3
+ const escapeRegExp = (string) =>
4
+ string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5
+
6
+ export const detectLanguage = (code = '') => {
7
+ const language_keywords = {
8
+ python: ['def ', 'print(', 'import ', 'class '],
9
+ javascript: ['function ', 'console.log(', 'let ', 'const ', 'document.getElementById'],
10
+ typescript: ['interface ', 'type ', 'let ', 'const ', ': string', ': number'],
11
+ java: ['import java.', 'ArrayList<', 'System.out', 'void main(', 'public class', 'new '],
12
+ c: ['#include <stdio.h>', 'printf(', 'scanf(', 'int main('],
13
+ cpp: ['#include', 'std::', 'cout <<', 'cin >>'],
14
+ bash: ['#!/bin/bash', 'echo ', 'cd ', 'ls', 'pwd'],
15
+ shell: ['#!/bin/sh', 'echo ', 'export ', 'fi'],
16
+ sql: ['SELECT ', 'INSERT ', 'UPDATE ', 'FROM ', 'WHERE ', 'JOIN ', 'DELETE '],
17
+ html: ['<!DOCTYPE html>', '<html>', '<div>', '<script>'],
18
+ css: ['color:', 'font-size:', 'margin:', 'padding:'],
19
+ go: ['package main', 'fmt.Println', 'func main()'],
20
+ php: ['<?php', 'echo ', '$_', '->'],
21
+ ruby: ['def ', 'puts ', 'end', 'class '],
22
+ kotlin: ['fun main(', 'val ', 'var ', 'println('],
23
+ swift: ['import SwiftUI', 'struct ', 'var body:', 'Text('],
24
+ rust: ['fn main()', 'println!', 'let mut'],
25
+ scala: ['object ', 'def ', 'val ', 'println('],
26
+ dart: ['void main()', 'print(', 'var ', 'class '],
27
+ };
28
+
29
+ let bestMatch = 'plaintext';
30
+ let maxScore = 0;
31
+
32
+ for (const [lang, keywords] of Object.entries(language_keywords)) {
33
+ let score = 0;
34
+
35
+ for (const keyword of keywords) {
36
+ const regex = new RegExp(escapeRegExp(keyword), 'g');
37
+ const matches = (code.match(regex) || []).length;
38
+ score += matches * Math.max(1, keyword.length / 4);
39
+ }
40
+
41
+ if (score > maxScore) {
42
+ maxScore = score;
43
+ bestMatch = lang;
44
+ }
45
+ }
46
+
47
+ return bestMatch;
48
+ };