Skip to content

Commit 499660c

Browse files
authored
Merge pull request #704 from n4ze3m/next
v1.5.29
2 parents 7df4ed1 + a4210d4 commit 499660c

File tree

14 files changed

+502
-197
lines changed

14 files changed

+502
-197
lines changed

src/assets/locale/en/knowledge.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@
2424
"addKnowledge": "Add Knowledge",
2525
"updateKnowledge": "Add Source",
2626
"form": {
27+
"tabs": {
28+
"upload": "Upload File",
29+
"text": "Text Input"
30+
},
2731
"title": {
28-
"label": "Knowledge Title",
32+
"label": "Knowledge Title (optional)",
2933
"placeholder": "Enter knowledge title",
34+
"placeholderOptional": "Optional title (defaults to first 50 characters)",
3035
"required": "Knowledge title is required"
3136
},
3237
"uploadFile": {
@@ -36,6 +41,19 @@
3641
"required": "File is required",
3742
"uploadError": "Unsupported file type"
3843
},
44+
"textInput": {
45+
"typeLabel": "Type",
46+
"type": {
47+
"plain": "Plain Text",
48+
"markdown": "Markdown",
49+
"code": "Code"
50+
},
51+
"contentLabel": "Content",
52+
"placeholder": "Paste or type your text here...",
53+
"required": "Text content is required",
54+
"tooLarge": "Content is too large. Please keep it under 500k characters.",
55+
"defaultTitle": "Untitled Text"
56+
},
3957
"submit": "Submit",
4058
"success": "Knowledge added successfully"
4159
},

src/components/Common/ProviderIcon.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { MoonshotIcon } from "../Icons/Moonshot"
2222
import { XAIIcon } from "../Icons/XAI"
2323
import { HuggingFaceIcon } from "../Icons/HuggingFaceIcon"
2424
import { VercelIcon } from "../Icons/VercelIcon"
25+
import { ChutesIcon } from "../Icons/ChutesIcon"
2526

2627
export const ProviderIcons = ({
2728
provider,
@@ -79,6 +80,8 @@ export const ProviderIcons = ({
7980
return <HuggingFaceIcon className={className} />
8081
case "vercel":
8182
return <VercelIcon className={className} />
83+
case "chutes":
84+
return <ChutesIcon className={className} />
8285
default:
8386
return <OllamaIcon className={className} />
8487
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react"
2+
3+
export const ChutesIcon = React.forwardRef<
4+
SVGSVGElement,
5+
React.SVGProps<SVGSVGElement>
6+
>((props, ref) => {
7+
return (
8+
<svg
9+
xmlns="http://www.w3.org/2000/svg"
10+
fill="none"
11+
className="size-6"
12+
viewBox="0 0 62 41"
13+
ref={ref}
14+
{...props}>
15+
<path
16+
fill="currentColor"
17+
d="M38.01 39.694c-.884 1.442-2.758 1.712-3.966.57l-5.37-5.074c-1.208-1.141-1.19-3.163.04-4.278l5.412-4.914c6.017-5.463 13.943-7.592 21.487-5.773l4.072.983c.146.035.28.109.392.214.59.557.26 1.597-.525 1.656l-.087.006c-7.45.557-14.284 4.907-18.49 11.77z"></path>
18+
<path
19+
fill="url(#paint0_linear_10244_130)"
20+
d="M15.296 36.591c-1.123 1.246-3.02 1.131-4.005-.242L.547 21.371c-.98-1.366-.602-3.344.8-4.189L22.772 4.275C29.603.158 37.73-.277 44.809 3.093l15.54 7.403c.24.114.45.291.61.515.856 1.192-.06 2.895-1.453 2.704l-9.258-1.268c-7.393-1.013-14.834 1.838-20.131 7.712z"></path>
21+
<defs>
22+
<linearGradient
23+
id="paint0_linear_10244_130"
24+
x1="33.853"
25+
x2="25.55"
26+
y1="0.174"
27+
y2="41.449"
28+
gradientUnits="userSpaceOnUse">
29+
<stop stopColor="currentColor"></stop>
30+
<stop offset="1" stopColor="currentColor"></stop>
31+
</linearGradient>
32+
</defs>
33+
</svg>
34+
)
35+
})

src/components/Layouts/SettingsOptionLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
8282
icon={OllamaIcon}
8383
current={location.pathname}
8484
/>
85-
{import.meta.env.BROWSER === "chrome" && (
85+
{import.meta.env.BROWSER !== "firefox" && (
8686
<LinkComponent
8787
href="/settings/chrome"
8888
name={t("chromeAiSettings.title")}

src/components/Option/Knowledge/AddKnowledge.tsx

Lines changed: 135 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { createKnowledge } from "@/db/dexie/knowledge"
22
import { Source } from "@/db/knowledge"
33
import { defaultEmbeddingModelForRag } from "@/services/ollama"
4-
import { convertToSource } from "@/utils/to-source"
4+
import { convertTextToSource, convertToSource } from "@/utils/to-source"
55
import { useMutation } from "@tanstack/react-query"
6-
import { Modal, Form, Input, Upload, message, UploadFile } from "antd"
6+
import { Modal, Form, Input, Upload, message, Tabs, Select } from "antd"
77
import { InboxIcon } from "lucide-react"
88
import { useTranslation } from "react-i18next"
99
import PubSub from "pubsub-js"
1010
import { KNOWLEDGE_QUEUE } from "@/queue"
1111
import { useStorage } from "@plasmohq/storage/hook"
1212
import { unsupportedTypes } from "./utils/unsupported-types"
13+
import React from "react"
1314

1415
type Props = {
1516
open: boolean
@@ -20,18 +21,16 @@ export const AddKnowledge = ({ open, setOpen }: Props) => {
2021
const { t } = useTranslation(["knowledge", "common"])
2122
const [form] = Form.useForm()
2223
const [totalFilePerKB] = useStorage("totalFilePerKB", 5)
24+
const [mode, setMode] = React.useState<"upload" | "text">("upload")
2325

24-
const onUploadHandler = async (data: {
25-
title: string
26-
file: UploadFile[]
27-
}) => {
26+
const onUploadHandler = async (data: any) => {
2827
const defaultEM = await defaultEmbeddingModelForRag()
2928

3029
if (!defaultEM) {
3130
throw new Error(t("noEmbeddingModel"))
3231
}
3332

34-
const source: Source[] = []
33+
const source: Source[] = []
3534

3635
const allowedTypes = [
3736
"application/pdf",
@@ -41,19 +40,57 @@ export const AddKnowledge = ({ open, setOpen }: Props) => {
4140
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
4241
]
4342

44-
for (const file of data.file) {
45-
let mime = file.type
46-
if (!allowedTypes.includes(mime)) {
47-
mime = "text/plain"
43+
if (mode === "upload") {
44+
for (const file of data.file || []) {
45+
let mime = file.type
46+
if (!allowedTypes.includes(mime)) {
47+
mime = "text/plain"
48+
}
49+
const _src = await convertToSource({ file, mime, sourceType: "file_upload" })
50+
source.push(_src)
51+
}
52+
} else {
53+
// Text mode validation
54+
const rawText: string = (data?.textContent || "").trim()
55+
const textType: string = data?.textType || "plain"
56+
if (!rawText) {
57+
throw new Error(t("form.textInput.required"))
58+
}
59+
// Prevent oversized content (e.g., > 500k chars)
60+
if (rawText.length > 500000) {
61+
throw new Error(t("form.textInput.tooLarge"))
62+
}
63+
64+
const asMarkdown = textType === "markdown"
65+
const filename = data?.title
66+
? `${data?.title}.txt`
67+
: `pasted_${new Date().getTime()}.txt`
68+
const _src = await convertTextToSource({
69+
text: rawText,
70+
filename,
71+
mime: asMarkdown ? "text/markdown" : "text/plain",
72+
asMarkdown,
73+
sourceType: "text_input"
74+
})
75+
source.push(_src)
76+
}
77+
78+
let title = data?.title?.trim()
79+
if (!title || title.length === 0) {
80+
if (mode === "text") {
81+
const text = (data?.textContent || "").trim()
82+
title = text.substring(0, 50) || t("form.textInput.defaultTitle")
83+
} else if ((data?.file || []).length > 0) {
84+
title = (data.file[0]?.name as string) || t("form.textInput.defaultTitle")
85+
} else {
86+
title = t("form.textInput.defaultTitle")
4887
}
49-
const data = await convertToSource({ file, mime })
50-
source.push(data)
5188
}
5289

5390
const knowledge = await createKnowledge({
5491
embedding_model: defaultEM,
5592
source,
56-
title: data.title
93+
title
5794
})
5895

5996
return knowledge.id
@@ -78,69 +115,94 @@ export const AddKnowledge = ({ open, setOpen }: Props) => {
78115
open={open}
79116
footer={null}
80117
onCancel={() => setOpen(false)}>
118+
<Tabs
119+
activeKey={mode}
120+
onChange={(key) => setMode(key as any)}
121+
items={[
122+
{ key: "upload", label: t("form.tabs.upload") },
123+
{ key: "text", label: t("form.tabs.text") }
124+
]}
125+
/>
81126
<Form onFinish={saveKnowledge} form={form} layout="vertical">
82-
<Form.Item
83-
rules={[
84-
{
85-
required: true,
86-
message: t("form.title.required")
87-
}
88-
]}
89-
name="title"
90-
label={t("form.title.label")}>
91-
<Input size="large" placeholder={t("form.title.placeholder")} />
127+
{/* Title is optional now */}
128+
<Form.Item name="title" label={t("form.title.label")}>
129+
<Input size="large" placeholder={t("form.title.placeholderOptional")} />
92130
</Form.Item>
93-
<Form.Item
94-
name="file"
95-
label={t("form.uploadFile.label")}
96-
rules={[
97-
{
98-
required: true,
99-
message: t("form.uploadFile.required")
100-
}
101-
]}
102-
getValueFromEvent={(e) => {
103-
if (Array.isArray(e)) {
104-
return e
105-
}
106-
return e?.fileList
107-
}}>
108-
<Upload.Dragger
109-
multiple={true}
110-
maxCount={totalFilePerKB}
111-
beforeUpload={(file) => {
112-
const allowedTypes = [
113-
"application/pdf",
114-
"text/csv",
115-
"text/plain",
116-
"text/markdown",
117-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
118-
]
119-
.map((type) => type.toLowerCase())
120-
.join(", ")
121-
122-
if (unsupportedTypes.includes(file.type.toLowerCase())) {
123-
message.error(
124-
t("form.uploadFile.uploadError", { allowedTypes })
125-
)
126-
return Upload.LIST_IGNORE
127-
}
128131

129-
return false
132+
{mode === "upload" ? (
133+
<Form.Item
134+
name="file"
135+
label={t("form.uploadFile.label")}
136+
rules={[
137+
{
138+
required: true,
139+
message: t("form.uploadFile.required")
140+
}
141+
]}
142+
getValueFromEvent={(e) => {
143+
if (Array.isArray(e)) {
144+
return e
145+
}
146+
return e?.fileList
130147
}}>
131-
<div className="p-3">
132-
<p className="flex justify-center ant-upload-drag-icon">
133-
<InboxIcon className="w-10 h-10 text-gray-400" />
134-
</p>
135-
<p className="ant-upload-text">
136-
{t("form.uploadFile.uploadText")}
137-
</p>
138-
{/* <p className="ant-upload-hint">
139-
{t("form.uploadFile.uploadHint")}
140-
</p> */}
141-
</div>
142-
</Upload.Dragger>
143-
</Form.Item>
148+
<Upload.Dragger
149+
multiple={true}
150+
maxCount={totalFilePerKB}
151+
beforeUpload={(file) => {
152+
const allowedTypes = [
153+
"application/pdf",
154+
"text/csv",
155+
"text/plain",
156+
"text/markdown",
157+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
158+
]
159+
.map((type) => type.toLowerCase())
160+
.join(", ")
161+
162+
if (unsupportedTypes.includes(file.type.toLowerCase())) {
163+
message.error(
164+
t("form.uploadFile.uploadError", { allowedTypes })
165+
)
166+
return Upload.LIST_IGNORE
167+
}
168+
169+
return false
170+
}}>
171+
<div className="p-3">
172+
<p className="flex justify-center ant-upload-drag-icon">
173+
<InboxIcon className="w-10 h-10 text-gray-400" />
174+
</p>
175+
<p className="ant-upload-text">
176+
{t("form.uploadFile.uploadText")}
177+
</p>
178+
</div>
179+
</Upload.Dragger>
180+
</Form.Item>
181+
) : (
182+
<>
183+
<Form.Item
184+
name="textType"
185+
label={t("form.textInput.typeLabel")}
186+
initialValue="plain">
187+
<Select
188+
options={[
189+
{ value: "plain", label: t("form.textInput.type.plain") },
190+
{ value: "markdown", label: t("form.textInput.type.markdown") },
191+
{ value: "code", label: t("form.textInput.type.code") }
192+
]}
193+
/>
194+
</Form.Item>
195+
<Form.Item
196+
name="textContent"
197+
label={t("form.textInput.contentLabel")}
198+
rules={[{ required: true, message: t("form.textInput.required") }]}>
199+
<Input.TextArea
200+
autoSize={{ minRows: 8, maxRows: 16 }}
201+
placeholder={t("form.textInput.placeholder")}
202+
/>
203+
</Form.Item>
204+
</>
205+
)}
144206

145207
<Form.Item>
146208
<button

0 commit comments

Comments
 (0)