Skip to content

Commit 7670ba1

Browse files
authored
Merge pull request #687 from n4ze3m/next
v1.5.26
2 parents 85cf43f + d8110c0 commit 7670ba1

File tree

10 files changed

+198
-125
lines changed

10 files changed

+198
-125
lines changed

bun.lockb

704 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"marked": "^15.0.12",
5454
"mermaid": "^11.4.1",
5555
"ml-distance": "^4.0.1",
56+
"ollama": "^0.5.17",
5657
"openai": "^4.95.1",
5758
"pa-tesseract.js": "^5.1.1",
5859
"pdfjs-dist": "4.0.379",
Lines changed: 140 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { programmingLanguages } from "@/utils/langauge-extension"
2-
import { Tooltip, Modal, ConfigProvider, Button } from "antd"
2+
import { Tooltip } from "antd"
33
import {
44
CopyCheckIcon,
55
CopyIcon,
66
DownloadIcon,
7-
InfoIcon,
8-
ExternalLinkIcon
7+
EyeIcon,
8+
CodeIcon
99
} from "lucide-react"
10-
import { FC, useState, useRef } from "react"
10+
import { FC, useState, useRef, useEffect, useCallback } from "react"
1111
import { useTranslation } from "react-i18next"
1212
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
1313
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"
@@ -20,9 +20,37 @@ interface Props {
2020

2121
export const CodeBlock: FC<Props> = ({ language, value }) => {
2222
const [isBtnPressed, setIsBtnPressed] = useState(false)
23-
const [previewVisible, setPreviewVisible] = useState(false)
23+
const [previewValue, setPreviewValue] = useState(value)
24+
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null)
25+
26+
const computeKey = () => {
27+
const base = `${language}::${value?.slice(0, 200)}`
28+
let hash = 0
29+
for (let i = 0; i < base.length; i++) {
30+
hash = (hash * 31 + base.charCodeAt(i)) >>> 0
31+
}
32+
return hash.toString(36)
33+
}
34+
const keyRef = useRef<string>(computeKey())
35+
const mapRef = useRef<Map<string, boolean> | null>(null)
36+
if (!mapRef.current) {
37+
if (typeof window !== "undefined") {
38+
// @ts-ignore
39+
if (!window.__codeBlockPreviewState) {
40+
// @ts-ignore
41+
window.__codeBlockPreviewState = new Map()
42+
}
43+
// @ts-ignore
44+
mapRef.current = window.__codeBlockPreviewState as Map<string, boolean>
45+
} else {
46+
mapRef.current = new Map()
47+
}
48+
}
49+
const globalStateMap = mapRef.current!
50+
const [showPreview, setShowPreview] = useState<boolean>(
51+
() => globalStateMap.get(keyRef.current) || false
52+
)
2453
const { t } = useTranslation("common")
25-
const iframeRef = useRef<HTMLIFrameElement>(null)
2654

2755
const handleCopy = () => {
2856
navigator.clipboard.writeText(value)
@@ -32,9 +60,21 @@ export const CodeBlock: FC<Props> = ({ language, value }) => {
3260
}, 4000)
3361
}
3462

35-
const handlePreviewClose = () => {
36-
setPreviewVisible(false)
37-
}
63+
const isPreviewable = ["html", "svg", "xml"].includes(
64+
(language || "").toLowerCase()
65+
)
66+
67+
const buildPreviewDoc = useCallback(() => {
68+
const code = previewValue || ""
69+
if ((language || "").toLowerCase() === "svg") {
70+
const hasSvgTag = /<svg[\s>]/i.test(code)
71+
const svgMarkup = hasSvgTag
72+
? code
73+
: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>${code}</svg>`
74+
return `<!doctype html><html><head><meta charset='utf-8'/><style>html,body{margin:0;padding:0;display:flex;align-items:center;justify-content:center;background:#fff;height:100%;}</style></head><body>${svgMarkup}</body></html>`
75+
}
76+
return `<!doctype html><html><head><meta charset='utf-8'/></head><body>${code}</body></html>`
77+
}, [previewValue, language])
3878

3979
const handleDownload = () => {
4080
const blob = new Blob([value], { type: "text/plain" })
@@ -48,30 +88,75 @@ export const CodeBlock: FC<Props> = ({ language, value }) => {
4888
window.URL.revokeObjectURL(url)
4989
}
5090

51-
const handleOpenInNewTab = () => {
52-
const blob = new Blob([value], { type: "text/html" })
53-
const url = URL.createObjectURL(blob)
54-
window.open(url, "_blank")
55-
}
91+
useEffect(() => {
92+
globalStateMap.set(keyRef.current, showPreview)
93+
}, [showPreview])
94+
95+
useEffect(() => {
96+
if (debounceTimeoutRef.current) {
97+
clearTimeout(debounceTimeoutRef.current)
98+
}
99+
100+
debounceTimeoutRef.current = setTimeout(() => {
101+
setPreviewValue(value)
102+
}, 300)
103+
104+
return () => {
105+
if (debounceTimeoutRef.current) {
106+
clearTimeout(debounceTimeoutRef.current)
107+
}
108+
}
109+
}, [value])
110+
111+
useEffect(() => {
112+
const newKey = computeKey()
113+
if (newKey !== keyRef.current) {
114+
keyRef.current = newKey
115+
if (globalStateMap.has(newKey)) {
116+
const prev = globalStateMap.get(newKey)!
117+
if (prev !== showPreview) setShowPreview(prev)
118+
}
119+
}
120+
}, [language, value])
121+
122+
useEffect(() => {
123+
if (!isPreviewable && showPreview) setShowPreview(false)
124+
}, [isPreviewable])
56125

57126
return (
58127
<>
59128
<div className="not-prose">
60129
<div className=" [&_div+div]:!mt-0 my-4 bg-zinc-950 rounded-xl">
61-
<div className="flex flex-row px-4 py-2 rounded-t-xl bg-gray-800 ">
130+
<div className="flex flex-row px-4 py-2 rounded-t-xl gap-3 bg-gray-800 ">
131+
{isPreviewable && (
132+
<div className="flex rounded-md overflow-hidden border border-gray-700">
133+
<button
134+
onClick={() => setShowPreview(false)}
135+
className={`px-2 flex items-center gap-1 text-xs transition-colors ${
136+
!showPreview
137+
? "bg-gray-700 text-white"
138+
: "bg-transparent text-gray-300 hover:bg-gray-700/60"
139+
}`}
140+
aria-label={t("showCode") || "Code"}>
141+
<CodeIcon className="size-3" />
142+
</button>
143+
<button
144+
onClick={() => setShowPreview(true)}
145+
className={`px-2 flex items-center gap-1 text-xs transition-colors ${
146+
showPreview
147+
? "bg-gray-700 text-white"
148+
: "bg-transparent text-gray-300 hover:bg-gray-700/60"
149+
}`}
150+
aria-label={t("preview") || "Preview"}>
151+
<EyeIcon className="size-3" />
152+
</button>
153+
</div>
154+
)}
155+
62156
<span className="font-mono text-xs">{language || "text"}</span>
63157
</div>
64158
<div className="sticky top-9 md:top-[5.75rem]">
65-
<div className="absolute bottom-0 right-2 flex h-9 items-center">
66-
{/* {language === "html" && (
67-
<Tooltip title={t("preview")}>
68-
<button
69-
onClick={() => setPreviewVisible(true)}
70-
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none">
71-
<InfoIcon className="size-4" />
72-
</button>
73-
</Tooltip>
74-
)} */}
159+
<div className="absolute bottom-0 right-2 flex h-9 items-center gap-1">
75160
<Tooltip title={t("downloadCode")}>
76161
<button
77162
onClick={handleDownload}
@@ -93,97 +178,41 @@ export const CodeBlock: FC<Props> = ({ language, value }) => {
93178
</div>
94179
</div>
95180

96-
<SyntaxHighlighter
97-
language={language}
98-
style={coldarkDark}
99-
PreTag="div"
100-
customStyle={{
101-
margin: 0,
102-
width: "100%",
103-
background: "transparent",
104-
padding: "1.5rem 1rem"
105-
}}
106-
lineNumberStyle={{
107-
userSelect: "none"
108-
}}
109-
codeTagProps={{
110-
style: {
111-
fontSize: "0.9rem",
112-
fontFamily: "var(--font-mono)"
113-
}
114-
}}>
115-
{value}
116-
</SyntaxHighlighter>
117-
</div>
118-
</div>
119-
{previewVisible && (
120-
<ConfigProvider
121-
theme={{
122-
components: {
123-
Modal: {
124-
contentBg: "#1e1e1e",
125-
headerBg: "#1e1e1e",
126-
titleColor: "#ffffff"
127-
}
128-
}
129-
}}>
130-
<Modal
131-
title={
132-
<div className="flex items-center text-white">
133-
<InfoIcon className="mr-2 size-5" />
134-
<span>HTML Preview</span>
135-
</div>
136-
}
137-
open={previewVisible}
138-
onCancel={handlePreviewClose}
139-
footer={
140-
<div className="flex justify-end gap-2">
141-
<Button
142-
icon={<ExternalLinkIcon className="size-4" />}
143-
onClick={handleOpenInNewTab}>
144-
Open in new tab
145-
</Button>
146-
147-
<Button
148-
icon={<DownloadIcon className="size-4" />}
149-
onClick={handleDownload}>
150-
{t("downloadCode")}
151-
</Button>
152-
</div>
153-
}
154-
width={"80%"}
155-
zIndex={999999}
156-
centered
157-
styles={{
158-
body: {
159-
padding: 0,
160-
backgroundColor: "#f5f5f5",
161-
borderRadius: "0 0 8px 8px"
162-
},
163-
header: {
164-
borderBottom: "1px solid #333",
165-
padding: "12px 24px"
166-
},
167-
mask: {
168-
backdropFilter: "blur(4px)",
169-
backgroundColor: "rgba(0, 0, 0, 0.6)"
170-
},
171-
content: {
172-
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)"
173-
}
174-
}}>
175-
<div className={`relative w-full h-[70vh] bg-white`}>
181+
{!showPreview && (
182+
<SyntaxHighlighter
183+
language={language}
184+
style={coldarkDark}
185+
PreTag="div"
186+
customStyle={{
187+
margin: 0,
188+
width: "100%",
189+
background: "transparent",
190+
padding: "1.5rem 1rem"
191+
}}
192+
lineNumberStyle={{
193+
userSelect: "none"
194+
}}
195+
codeTagProps={{
196+
style: {
197+
fontSize: "0.9rem",
198+
fontFamily: "var(--font-mono)"
199+
}
200+
}}>
201+
{value}
202+
</SyntaxHighlighter>
203+
)}
204+
{showPreview && isPreviewable && (
205+
<div className="w-full h-[420px] bg-white rounded-b-xl overflow-hidden border-t border-gray-800">
176206
<iframe
177-
ref={iframeRef}
178-
srcDoc={value}
179-
title="HTML Preview"
207+
title="Preview"
208+
srcDoc={buildPreviewDoc()}
180209
className="w-full h-full border-0"
181210
sandbox="allow-scripts allow-same-origin"
182211
/>
183212
</div>
184-
</Modal>
185-
</ConfigProvider>
186-
)}
213+
)}
214+
</div>
215+
</div>
187216
</>
188217
)
189218
}

src/components/Common/ProviderIcon.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { NovitaIcon } from "../Icons/Novita"
2020
import { VllmLogo } from "../Icons/VllmLogo"
2121
import { MoonshotIcon } from "../Icons/Moonshot"
2222
import { XAIIcon } from "../Icons/XAI"
23+
import { HuggingFaceIcon } from "../Icons/HuggingFaceIcon"
2324

2425
export const ProviderIcons = ({
2526
provider,
@@ -73,6 +74,8 @@ export const ProviderIcons = ({
7374
return <MoonshotIcon className={className} />
7475
case "xai":
7576
return <XAIIcon className={className} />
77+
case "huggingface":
78+
return <HuggingFaceIcon className={className} />
7679
default:
7780
return <OllamaIcon className={className} />
7881
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react"
2+
3+
export const HuggingFaceIcon = React.forwardRef<
4+
SVGSVGElement,
5+
React.SVGProps<SVGSVGElement>
6+
>((props, ref) => {
7+
return (
8+
<svg
9+
xmlns="http://www.w3.org/2000/svg"
10+
viewBox="0 0 24 24"
11+
{...props}
12+
ref={ref}
13+
>
14+
<path
15+
fill="#FF9D0B"
16+
d="M2.25 11.535c0-3.407 1.847-6.554 4.844-8.258a9.82 9.82 0 0 1 9.687 0c2.997 1.704 4.844 4.851 4.844 8.258 0 5.266-4.337 9.535-9.687 9.535S2.25 16.8 2.25 11.535"></path>
17+
<path
18+
fill="#FFD21E"
19+
d="M11.938 20.086c4.797 0 8.687-3.829 8.687-8.551s-3.89-8.55-8.687-8.55c-4.798 0-8.688 3.828-8.688 8.55s3.89 8.55 8.688 8.55z"></path>
20+
<path
21+
fill="#FF323D"
22+
d="M11.875 15.113c2.457 0 3.25-2.156 3.25-3.263 0-.576-.393-.394-1.023-.089-.582.283-1.365.675-2.224.675-1.798 0-3.25-1.693-3.25-.586s.79 3.263 3.25 3.263z"></path>
23+
<path
24+
fill="#3A3B45"
25+
d="M14.76 9.21c.32.108.445.753.767.585.447-.233.707-.708.659-1.204a1.235 1.235 0 0 0-.879-1.059 1.26 1.26 0 0 0-1.33.394c-.322.384-.377.92-.14 1.36.153.283.638-.177.925-.079zm-5.887 0c-.32.108-.448.753-.768.585a1.23 1.23 0 0 1-.658-1.204c.048-.495.395-.913.878-1.059a1.26 1.26 0 0 1 1.33.394c.322.384.377.92.14 1.36-.152.283-.64-.177-.925-.079zm1.12 5.34a2.17 2.17 0 0 1 1.325-1.106c.07-.02.144.06.219.171l.192.306c.069.1.139.175.209.175.074 0 .15-.074.223-.172l.205-.302c.08-.11.157-.188.234-.165.537.168.986.536 1.25 1.026.932-.724 1.275-1.905 1.275-2.633 0-.508-.306-.426-.81-.19l-.616.296c-.52.24-1.148.48-1.824.48s-1.302-.24-1.823-.48l-.589-.283c-.52-.248-.838-.342-.838.177 0 .703.32 1.831 1.187 2.56z"></path>
26+
<path
27+
fill="#FF9D0B"
28+
d="M17.812 10.366a.806.806 0 0 0 .813-.8c0-.441-.364-.8-.813-.8a.806.806 0 0 0-.812.8c0 .442.364.8.812.8m-11.624 0a.806.806 0 0 0 .812-.8c0-.441-.364-.8-.812-.8a.806.806 0 0 0-.813.8c0 .442.364.8.813.8m-1.673 2.707c-.405 0-.765.162-1.017.46a1.46 1.46 0 0 0-.333.925 1.8 1.8 0 0 0-.485-.074c-.387 0-.737.146-.985.409a1.41 1.41 0 0 0-.2 1.722 1.3 1.3 0 0 0-.447.694c-.06.222-.12.69.2 1.166a1.27 1.27 0 0 0-.093 1.236c.238.533.81.958 1.89 1.405l.24.096c.768.3 1.473.492 1.478.494.89.243 1.808.375 2.732.394 1.465 0 2.513-.443 3.115-1.314.93-1.342.842-2.575-.274-3.763l-.151-.154c-.692-.684-1.155-1.69-1.25-1.912-.195-.655-.71-1.383-1.562-1.383-.46.007-.889.233-1.15.605-.25-.31-.495-.553-.715-.694a1.87 1.87 0 0 0-.993-.312m14.97 0a1.3 1.3 0 0 1 1.017.46c.216.262.333.588.333.925q.238-.07.487-.074c.388 0 .738.146.985.409a1.41 1.41 0 0 1 .2 1.722c.22.178.377.422.445.694.06.222.12.69-.2 1.166.244.37.279.836.093 1.236-.238.533-.81.958-1.889 1.405l-.239.096c-.77.3-1.475.492-1.48.494-.89.243-1.808.375-2.732.394-1.465 0-2.513-.443-3.115-1.314-.93-1.342-.842-2.575.274-3.763l.151-.154c.695-.684 1.157-1.69 1.252-1.912.195-.655.708-1.383 1.56-1.383.46.007.889.233 1.15.605.25-.31.495-.553.718-.694.244-.162.523-.265.814-.3z"></path>
29+
<path
30+
fill="#FFD21E"
31+
d="M9.785 20.132c.688-.994.638-1.74-.305-2.667-.945-.928-1.495-2.288-1.495-2.288s-.205-.788-.672-.714-.81 1.25.17 1.971c.977.721-.195 1.21-.573.534-.375-.677-1.405-2.416-1.94-2.751-.532-.332-.907-.148-.782.541.125.687 2.357 2.35 2.14 2.707-.218.362-.983-.42-.983-.42S2.953 14.9 2.43 15.46c-.52.558.398 1.026 1.7 1.803 1.308.778 1.41.985 1.225 1.28-.187.295-3.07-2.1-3.34-1.083-.27 1.011 2.943 1.304 2.745 2.006-.2.7-2.265-1.324-2.685-.537-.425.79 2.913 1.718 2.94 1.725 1.075.276 3.813.859 4.77-.522m4.432 0c-.687-.994-.64-1.74.305-2.667.943-.928 1.493-2.288 1.493-2.288s.205-.788.675-.714c.465.074.807 1.25-.17 1.971-.98.721.195 1.21.57.534.377-.677 1.407-2.416 1.94-2.751.532-.332.91-.148.782.541-.125.687-2.355 2.35-2.137 2.707.215.362.98-.42.98-.42S21.05 14.9 21.57 15.46c.52.558-.395 1.026-1.7 1.803-1.308.778-1.408.985-1.225 1.28.187.295 3.07-2.1 3.34-1.083.27 1.011-2.94 1.304-2.743 2.006.2.7 2.263-1.324 2.685-.537.423.79-2.912 1.718-2.94 1.725-1.077.276-3.815.859-4.77-.522"></path>
32+
</svg>
33+
)
34+
})

src/hooks/useMessage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ export const useMessage = () => {
469469
setIsProcessing(false)
470470
setStreaming(false)
471471
} catch (e) {
472+
console.log(e)
472473
const errorSave = await saveMessageOnError({
473474
e,
474475
botMessage: fullText,

src/models/OAIEmbedding.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type ClientOptions, OpenAI as OpenAIClient } from "openai"
22
import { Embeddings, EmbeddingsParams } from "@langchain/core/embeddings"
33
import { chunkArray } from "@langchain/core/utils/chunk_array"
4-
import { OpenAICoreRequestOptions, LegacyOpenAIInput } from "./types"
4+
import { LegacyOpenAIInput } from "./types"
55
import { wrapOpenAIClientError } from "./utils/openai"
66

77
export interface OpenAIEmbeddingsParams extends EmbeddingsParams {
@@ -19,16 +19,9 @@ export class OAIEmbedding
1919
modelName = "text-embedding-ada-002"
2020

2121
model = "text-embedding-ada-002"
22-
2322
batchSize = 512
24-
25-
// TODO: Update to `false` on next minor release (see: https://github.com/langchain-ai/langchainjs/pull/3612)
2623
stripNewLines = true
2724

28-
/**
29-
* The number of dimensions the resulting output embeddings should have.
30-
* Only supported in `text-embedding-3` and later models.
31-
*/
3225
dimensions?: number
3326

3427
timeout?: number

0 commit comments

Comments
 (0)