Skip to content

Commit 6268ae9

Browse files
committed
feat: import deepchat to nowledge-mem
1 parent f1c7dde commit 6268ae9

File tree

14 files changed

+1277
-11
lines changed

14 files changed

+1277
-11
lines changed

src/main/events.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export const CONFIG_EVENTS = {
3939
THEME_CHANGED: 'config:theme-changed', // 主题变更事件
4040
FONT_SIZE_CHANGED: 'config:font-size-changed', // 字体大小变更事件
4141
DEFAULT_SYSTEM_PROMPT_CHANGED: 'config:default-system-prompt-changed', // Default system prompt changed event
42-
CUSTOM_PROMPTS_CHANGED: 'config:custom-prompts-changed' // 自定义提示词变更事件
42+
CUSTOM_PROMPTS_CHANGED: 'config:custom-prompts-changed', // 自定义提示词变更事件
43+
NOWLEDGE_MEM_CONFIG_UPDATED: 'config:nowledge-mem-config-updated' // Nowledge-mem configuration updated event
4344
}
4445

4546
// Provider DB(聚合 JSON)相关事件

src/main/presenter/configPresenter/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,42 @@ export class ConfigPresenter implements IConfigPresenter {
15691569
async findMcpServerByPackage(packageName: string): Promise<string | null> {
15701570
return this.mcpConfHelper.findServerByPackage(packageName)
15711571
}
1572+
1573+
// ===================== Nowledge-mem configuration methods =====================
1574+
async getNowledgeMemConfig(): Promise<{
1575+
baseUrl: string
1576+
apiKey?: string
1577+
timeout: number
1578+
} | null> {
1579+
try {
1580+
return this.store.get('nowledgeMemConfig', null) as {
1581+
baseUrl: string
1582+
apiKey?: string
1583+
timeout: number
1584+
} | null
1585+
} catch (error) {
1586+
console.error('[Config] Failed to get nowledge-mem config:', error)
1587+
return null
1588+
}
1589+
}
1590+
1591+
async setNowledgeMemConfig(config: {
1592+
baseUrl: string
1593+
apiKey?: string
1594+
timeout: number
1595+
}): Promise<void> {
1596+
try {
1597+
this.store.set('nowledgeMemConfig', config)
1598+
eventBus.sendToRenderer(
1599+
CONFIG_EVENTS.NOWLEDGE_MEM_CONFIG_UPDATED,
1600+
SendTarget.ALL_WINDOWS,
1601+
config
1602+
)
1603+
} catch (error) {
1604+
console.error('[Config] Failed to set nowledge-mem config:', error)
1605+
throw error
1606+
}
1607+
}
15721608
}
15731609

15741610
export { defaultShortcutKey } from './shortcutKeySettings'
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { IConfigPresenter } from '@shared/presenter'
2+
import { NowledgeMemThread } from '@shared/types/nowledgeMem'
3+
import logger from '../../../shared/logger'
4+
5+
export interface NowledgeMemConfig {
6+
baseUrl: string
7+
apiKey?: string
8+
timeout: number
9+
}
10+
11+
export interface NowledgeMemApiResponse<T = unknown> {
12+
success: boolean
13+
data?: T
14+
error?: string
15+
status?: number
16+
}
17+
18+
// Use same interface as NowledgeMemThread for consistency
19+
export type NowledgeMemThreadSubmission = NowledgeMemThread
20+
21+
export class NowledgeMemPresenter {
22+
private config: NowledgeMemConfig
23+
private configPresenter: IConfigPresenter
24+
private configLoaded = false
25+
26+
constructor(configPresenter: IConfigPresenter) {
27+
this.configPresenter = configPresenter
28+
this.config = {
29+
baseUrl: 'http://127.0.0.1:14242',
30+
timeout: 30000 // 30 seconds
31+
}
32+
// Best-effort async load; do not block constructor
33+
void this.loadConfig()
34+
.then(() => {
35+
this.configLoaded = true
36+
})
37+
.catch((err) => {
38+
logger.error('Failed to load persisted nowledge-mem config on init:', err)
39+
})
40+
}
41+
42+
/**
43+
* Update nowledge-mem configuration
44+
*/
45+
async updateConfig(config: Partial<NowledgeMemConfig>): Promise<void> {
46+
this.config = { ...this.config, ...config }
47+
48+
// Save configuration
49+
await this.configPresenter.setNowledgeMemConfig(this.config)
50+
}
51+
52+
/**
53+
* Load nowledge-mem configuration
54+
*/
55+
async loadConfig(): Promise<NowledgeMemConfig> {
56+
const savedConfig = await this.configPresenter.getNowledgeMemConfig()
57+
if (savedConfig) {
58+
this.config = { ...this.config, ...savedConfig }
59+
}
60+
return this.config
61+
}
62+
63+
private async ensureConfigLoaded() {
64+
if (!this.configLoaded) {
65+
await this.loadConfig().catch((err) => {
66+
logger.error('Failed to load nowledge-mem config:', err)
67+
})
68+
this.configLoaded = true
69+
}
70+
}
71+
72+
/**
73+
* Test connection to nowledge-mem API
74+
*/
75+
async testConnection(): Promise<NowledgeMemApiResponse<{ message: string }>> {
76+
try {
77+
await this.ensureConfigLoaded()
78+
const response = await fetch(`${this.config.baseUrl}/api/health`, {
79+
method: 'GET',
80+
headers: {
81+
'Content-Type': 'application/json',
82+
...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` })
83+
},
84+
signal: AbortSignal.timeout(this.config.timeout)
85+
})
86+
87+
return {
88+
success: response.ok,
89+
status: response.status,
90+
data: response.ok ? { message: 'Connection successful' } : undefined,
91+
error: response.ok ? undefined : `HTTP ${response.status}: ${response.statusText}`
92+
}
93+
} catch (error) {
94+
return {
95+
success: false,
96+
error: error instanceof Error ? error.message : 'Unknown error'
97+
}
98+
}
99+
}
100+
101+
/**
102+
* Submit thread to nowledge-mem API
103+
*/
104+
async submitThread(
105+
thread: NowledgeMemThread
106+
): Promise<NowledgeMemApiResponse<NowledgeMemThread>> {
107+
try {
108+
await this.ensureConfigLoaded()
109+
// Log thread data being sent for debugging
110+
logger.info('Submitting thread to nowledge-mem', {
111+
threadId: thread.thread_id,
112+
messageCount: thread.messages.length,
113+
source: thread.source
114+
})
115+
116+
const response = await fetch(`${this.config.baseUrl}/threads`, {
117+
method: 'POST',
118+
headers: {
119+
'Content-Type': 'application/json',
120+
...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` })
121+
},
122+
body: JSON.stringify(thread),
123+
signal: AbortSignal.timeout(this.config.timeout)
124+
})
125+
126+
let responseData
127+
let rawText = ''
128+
129+
if (!response.ok) {
130+
// Try to get raw response text first for debugging
131+
try {
132+
rawText = await response.text()
133+
logger.info(`HTTP ${response.status} Response:`, rawText)
134+
} catch (textError) {
135+
logger.error('Failed to read response text:', textError)
136+
}
137+
138+
// Then try to parse JSON
139+
try {
140+
responseData = JSON.parse(rawText)
141+
} catch (jsonError) {
142+
logger.error('Failed to parse response as JSON:', jsonError)
143+
responseData = { error: rawText || `HTTP ${response.status}: ${response.statusText}` }
144+
}
145+
} else {
146+
responseData = await response.json().catch(() => ({}))
147+
logger.info('Success response:', responseData)
148+
}
149+
150+
return {
151+
success: response.ok,
152+
status: response.status,
153+
data: response.ok ? responseData : undefined,
154+
error: response.ok
155+
? undefined
156+
: responseData.error ||
157+
responseData.message ||
158+
rawText ||
159+
`HTTP ${response.status}: ${response.statusText}`
160+
}
161+
} catch (error) {
162+
logger.error('Error submitting thread to nowledge-mem:', error)
163+
return {
164+
success: false,
165+
error: error instanceof Error ? error.message : 'Unknown error'
166+
}
167+
}
168+
}
169+
170+
/**
171+
* Get current configuration
172+
*/
173+
getConfig(): NowledgeMemConfig {
174+
// Return current snapshot (constructor defaults or loaded values)
175+
return { ...this.config }
176+
}
177+
178+
/**
179+
* Validate thread before submission
180+
*/
181+
validateThreadForSubmission(thread: NowledgeMemThread): {
182+
valid: boolean
183+
errors: string[]
184+
warnings: string[]
185+
} {
186+
const errors: string[] = []
187+
const warnings: string[] = []
188+
189+
// Required fields
190+
if (!thread.thread_id || thread.thread_id.trim().length === 0) {
191+
errors.push('Thread ID is required')
192+
}
193+
194+
if (!thread.messages || thread.messages.length === 0) {
195+
errors.push('Thread must have at least one message')
196+
}
197+
198+
// Message validation
199+
if (thread.messages) {
200+
thread.messages.forEach((message, index) => {
201+
if (!message.role || !['user', 'assistant', 'system'].includes(message.role)) {
202+
errors.push(`Message ${index + 1} has invalid role: ${message.role}`)
203+
}
204+
205+
if (!message.content || message.content.trim().length === 0) {
206+
errors.push(`Message ${index + 1} has empty content`)
207+
}
208+
209+
// Check content size (warn if too large)
210+
if (message.content && message.content.length > 50000) {
211+
warnings.push(
212+
`Message ${index + 1} content is very large (${message.content.length} characters)`
213+
)
214+
}
215+
})
216+
}
217+
218+
// Size warnings
219+
const jsonSize = JSON.stringify(thread).length
220+
if (jsonSize > 10000000) {
221+
// 10MB
222+
errors.push(
223+
`Thread data is too large (${Math.round(jsonSize / 1024 / 1024)}MB). Maximum size is 10MB`
224+
)
225+
} else if (jsonSize > 5000000) {
226+
// 5MB
227+
warnings.push(
228+
`Thread data is large (${Math.round(jsonSize / 1024 / 1024)}MB). Upload may take some time`
229+
)
230+
}
231+
232+
return {
233+
valid: errors.length === 0,
234+
errors,
235+
warnings
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)