diff --git a/app/components/MonacoEditor/index.tsx b/app/components/MonacoEditor/index.tsx index f4825d45..5178128c 100644 --- a/app/components/MonacoEditor/index.tsx +++ b/app/components/MonacoEditor/index.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'; -// eslint-disable-next-line import/no-extraneous-dependencies import * as monaco from 'monaco-editor'; import type { editor as TMonacoEditor } from 'monaco-editor'; import Editor, { useMonaco, loader, type Monaco } from '@monaco-editor/react'; diff --git a/app/config/locale/en-US.ts b/app/config/locale/en-US.ts index df3fb0f1..a6c9a4a6 100644 --- a/app/config/locale/en-US.ts +++ b/app/config/locale/en-US.ts @@ -564,5 +564,7 @@ export default { start: 'Start', aiImport: 'AI Import', pleaseConfigLLMFirst: 'Please configure LLM API first', + tokenTip: "The current token consumption is an estimated value, and there may be some differences from the actual consumption in the end.", + fileRequired: 'Please select the file', }, }; diff --git a/app/config/locale/zh-CN.ts b/app/config/locale/zh-CN.ts index ae1af2e6..88168e46 100644 --- a/app/config/locale/zh-CN.ts +++ b/app/config/locale/zh-CN.ts @@ -542,5 +542,8 @@ export default { previous: '上一步', start: '开始', aiImport: 'AI 导入', + pleaseConfigLLMFirst: '请先配置 LLM 接口地址', + tokenTip: '当前 token 消耗为预估值,最终消耗可能与实际消耗有所差异。', + fileRequired: '请先选择文件', }, }; diff --git a/app/pages/Console/OutputBox/index.tsx b/app/pages/Console/OutputBox/index.tsx index bfa7e368..29036171 100644 --- a/app/pages/Console/OutputBox/index.tsx +++ b/app/pages/Console/OutputBox/index.tsx @@ -329,7 +329,7 @@ const OutputBox = (props: IProps) => { children:
{message}
, }, ].filter(Boolean); - }, [gql, data, dataSource, columns, fullscreen]); + }, [gql, data, dataSource, columns, fullscreen, tab]); const onFullScreen = () => { setFullscreen(!fullscreen); }; diff --git a/app/pages/Import/AIImport/Create.tsx b/app/pages/Import/AIImport/Create.tsx index 09ba342a..8fa04de3 100644 --- a/app/pages/Import/AIImport/Create.tsx +++ b/app/pages/Import/AIImport/Create.tsx @@ -1,6 +1,6 @@ import { useStore } from '@app/stores'; import { useI18n } from '@vesoft-inc/i18n'; -import { Button, Form, Input, Modal, Radio, Select, message } from 'antd'; +import { Button, Form, Input, Modal, Radio, Select, Tooltip, message } from 'antd'; import { observer } from 'mobx-react-lite'; import Icon from '@app/components/Icon'; import { useEffect, useMemo, useState } from 'react'; @@ -62,6 +62,10 @@ const Create = observer((props: { visible: boolean; onCancel: () => void }) => { const onConfirm = async () => { const values = form.getFieldsValue(); + if (!values.file) { + message.error(intl.get('llm.fileRequired')); + return; + } post('/api/llm/import/job')({ type, ...values, @@ -85,20 +89,22 @@ const Create = observer((props: { visible: boolean; onCancel: () => void }) => { setStep(0); }} > - + {intl.get('llm.setup')} -
+
{intl.get('llm.confirm')}
- {tokens !== 0 && ( -
- 🅣 prompt token: ~ + {tokens !== 0 && type!=="filePath" && ( + +
+ 🅣 prompt token: ~ {Math.ceil(tokens / 10000)}w -
+
+ )}
diff --git a/app/pages/Import/AIImport/index.module.less b/app/pages/Import/AIImport/index.module.less index 7ebd3890..1267590f 100644 --- a/app/pages/Import/AIImport/index.module.less +++ b/app/pages/Import/AIImport/index.module.less @@ -64,4 +64,5 @@ gap: 5px; justify-content: center; color: #71717A; + z-index: 9; } \ No newline at end of file diff --git a/app/pages/Import/TaskList/TaskItem/AIImportItem.tsx b/app/pages/Import/TaskList/TaskItem/AIImportItem.tsx index fd9ded8a..443f7512 100644 --- a/app/pages/Import/TaskList/TaskItem/AIImportItem.tsx +++ b/app/pages/Import/TaskList/TaskItem/AIImportItem.tsx @@ -124,7 +124,7 @@ const AIImportItem = observer((props: IProps) => { > {intl.get('llm.aiImport')} - {llmJob.space} + {llmJob.job_id} {llmJob.status === ILLMStatus.Success && ( diff --git a/app/pages/Import/TaskList/TaskItem/LogModal/index.tsx b/app/pages/Import/TaskList/TaskItem/LogModal/index.tsx index 4c8e09c6..9ea4557f 100644 --- a/app/pages/Import/TaskList/TaskItem/LogModal/index.tsx +++ b/app/pages/Import/TaskList/TaskItem/LogModal/index.tsx @@ -49,7 +49,9 @@ const LogModal = (props: IProps) => { const handleLogDownload = () => currentLog && downloadTaskLog({ id, name: currentLog }); const readLog = async () => { - const data = await getLogDetail(task); + const data = await getLogDetail({ + id:task.id + }); handleLogData(data); }; diff --git a/app/pages/LLMBot/chat.module.less b/app/pages/LLMBot/chat.module.less index 390c1117..de4b5029 100644 --- a/app/pages/LLMBot/chat.module.less +++ b/app/pages/LLMBot/chat.module.less @@ -33,7 +33,12 @@ p { margin: 5px 0; line-height: 25px; - letter-spacing: 2px; + + pre { + margin: 0; + white-space: break-spaces; + max-width: 100%; + } } } } @@ -63,32 +68,6 @@ position: relative; min-width: 360px; - :global { - .CodeMirror { - background-color: #092332; - color: #AAAAAA - } - - .CodeMirror-gutters { - background-color: #092332; - color: #AAAAAA; - border: none; - color: #828282; - } - - .CodeMirror-cursor { - border-color: #828282; - } - - .cm-keyword { - color: #19DBFF; - } - - .cm-def { - color: #AE9AFF; - } - } - >div { margin: 10px 0; } diff --git a/app/pages/LLMBot/chat.tsx b/app/pages/LLMBot/chat.tsx index 50a00473..681365f4 100644 --- a/app/pages/LLMBot/chat.tsx +++ b/app/pages/LLMBot/chat.tsx @@ -1,5 +1,5 @@ -import { Button, Input } from 'antd'; -import { useEffect, useRef, useState } from 'react'; +import { Button, Input, message } from 'antd'; +import { useCallback, useEffect, useRef, useState } from 'react'; import ws from '@app/utils/websocket'; import { debounce } from 'lodash'; import rootStore from '@app/stores'; @@ -31,9 +31,12 @@ function Chat() { }); setMessages(newMessages); const callback = (res) => { + if (res.code !== 0) { + resetPending(newMessages,res.message); + return; + } if (res.message.done) { - newMessages[newMessages.length - 1].status = 'done'; - setPending(false); + resetPending(newMessages); return; } try { @@ -58,7 +61,7 @@ function Chat() { } setMessages([...newMessages]); } catch (e) { - setPending(false); + resetPending(newMessages,e.message+"\n response: \n"+JSON.stringify(res.message,null,2)); } }; const sendMessages = [ @@ -69,14 +72,26 @@ function Chat() { })), ]; const systemPrompt = await rootStore.llm.getDocPrompt(currentInput, sendMessages); - ws.runChat({ + await ws.runChat({ req: { stream: true, messages: [{ role: 'system', content: systemPrompt }, ...sendMessages, { role: 'user', content: currentInput }], }, callback, + }).catch((e) => { + message.error(e.message); + resetPending(newMessages,e.message); }); }, 200); + const resetPending = useCallback((messages,msg?:string) => { + setPending(false); + setMessages(messages.map((item) => { + if(item.status !== 'pending') return item; + item.status = 'done'; + item.content = msg ?(`[error] ${msg}`): item.content; + return item; + })) + },[]) useEffect(() => { if (contentRef.current) { @@ -96,7 +111,9 @@ function Chat() { const gqls = message.content.split(/```\w*\n([^`]+)```/); return gqls.map((item, index) => { if (index % 2 === 0) { - return

{item}

; + return

+

{item}
+

; } else { item = item.replace(/^(\n|ngql|gql|cypher)/g, '').replace(/\n$/g, ''); item = item.replace(/\n\n/, '\n'); diff --git a/app/stores/import.ts b/app/stores/import.ts index 14f092a1..0da85de1 100644 --- a/app/stores/import.ts +++ b/app/stores/import.ts @@ -341,7 +341,7 @@ export class ImportStore { trackEvent('import', 'download_task_log'); }; - getLogDetail = async (params: ITaskItem) => { + getLogDetail = async (params: any) => { const { code, data } = await service.getLogDetail(params); if (code === 0) { return data; diff --git a/package-lock.json b/package-lock.json index 5616e5ac..8df3072b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "@monaco-editor/react": "^4.6.0", "@vesoft-inc/force-graph": "2.0.7", "@vesoft-inc/i18n": "^1.0.1", - "@vesoft-inc/icons": "^1.2.0", - "@vesoft-inc/nebula-explain-graph": "^1.0.2-beta.2", + "@vesoft-inc/icons": "^1.7.0", + "@vesoft-inc/nebula-explain-graph": "^1.0.3", "@vesoft-inc/veditor": "^4.4.12", "antd": "^5.8.4", "axios": "^0.23.0", @@ -2058,14 +2058,14 @@ } }, "node_modules/@vesoft-inc/icons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vesoft-inc/icons/-/icons-1.2.0.tgz", - "integrity": "sha512-tqoAlsc1ofmaawQL15ok98wlAzD5NAWSPu5RBva85jvw7XREJM5+VjYeOg9lYAEWsdraUvw4iFNMBY9GvZtXhA==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@vesoft-inc/icons/-/icons-1.7.0.tgz", + "integrity": "sha512-862wYZP+oueH7WqykjV9xMqOP9lYQajqdrGjhTT18C57Y0g7HFikzrujArSk9q63qdo8viNO/27hl1iKULH4bQ==" }, "node_modules/@vesoft-inc/nebula-explain-graph": { - "version": "1.0.2-beta.2", - "resolved": "https://registry.npmmirror.com/@vesoft-inc/nebula-explain-graph/-/nebula-explain-graph-1.0.2-beta.2.tgz", - "integrity": "sha512-Pgcun3Zkq/pQ0dNzfStRmhRa2HH2hZOLB7878HZPnZ2cGSfRWM5XhuO5JsnrZMhgdbuoINQ1iuHK1T61+m2hvg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@vesoft-inc/nebula-explain-graph/-/nebula-explain-graph-1.0.3.tgz", + "integrity": "sha512-oP5SPYVVvkuw3LlCBiaZ/eLC5EnjQrRCNYyFWGSjT1ZJcmsqzee0Ts4wBuyiebgi2MyBX/TIZJNTRurCjrWrlg==", "peerDependencies": { "@vesoft-inc/veditor": "^4.4.11", "antd": "^4||^5", @@ -10495,14 +10495,14 @@ } }, "@vesoft-inc/icons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vesoft-inc/icons/-/icons-1.2.0.tgz", - "integrity": "sha512-tqoAlsc1ofmaawQL15ok98wlAzD5NAWSPu5RBva85jvw7XREJM5+VjYeOg9lYAEWsdraUvw4iFNMBY9GvZtXhA==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@vesoft-inc/icons/-/icons-1.7.0.tgz", + "integrity": "sha512-862wYZP+oueH7WqykjV9xMqOP9lYQajqdrGjhTT18C57Y0g7HFikzrujArSk9q63qdo8viNO/27hl1iKULH4bQ==" }, "@vesoft-inc/nebula-explain-graph": { - "version": "1.0.2-beta.2", - "resolved": "https://registry.npmmirror.com/@vesoft-inc/nebula-explain-graph/-/nebula-explain-graph-1.0.2-beta.2.tgz", - "integrity": "sha512-Pgcun3Zkq/pQ0dNzfStRmhRa2HH2hZOLB7878HZPnZ2cGSfRWM5XhuO5JsnrZMhgdbuoINQ1iuHK1T61+m2hvg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@vesoft-inc/nebula-explain-graph/-/nebula-explain-graph-1.0.3.tgz", + "integrity": "sha512-oP5SPYVVvkuw3LlCBiaZ/eLC5EnjQrRCNYyFWGSjT1ZJcmsqzee0Ts4wBuyiebgi2MyBX/TIZJNTRurCjrWrlg==", "requires": {} }, "@vesoft-inc/veditor": { diff --git a/package.json b/package.json index f1ac516d..e16a5cbe 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@monaco-editor/react": "^4.6.0", "@vesoft-inc/force-graph": "2.0.7", "@vesoft-inc/i18n": "^1.0.1", - "@vesoft-inc/icons": "^1.2.0", + "@vesoft-inc/icons": "^1.7.0", "@vesoft-inc/nebula-explain-graph": "^1.0.3", "@vesoft-inc/veditor": "^4.4.12", "antd": "^5.8.4", @@ -103,7 +103,7 @@ "lint-staged": { "./app/**/*.{js,jsx,ts,tsx}": [ "npx prettier --write", - "eslint './app/**/*.{js,jsx,ts,tsx}' --fix" + "eslint './app/**/*.{js,jsx,ts,tsx}' --fix --quiet" ] }, "ci": { diff --git a/server/api/studio/internal/service/llm/import.go b/server/api/studio/internal/service/llm/import.go index 03e414af..dbec123f 100644 --- a/server/api/studio/internal/service/llm/import.go +++ b/server/api/studio/internal/service/llm/import.go @@ -2,8 +2,10 @@ package llm import ( "fmt" + "hash/fnv" "os" "path/filepath" + "strconv" "time" "github.com/vesoft-inc/go-pkg/response" @@ -16,6 +18,11 @@ import ( "gorm.io/datatypes" ) +func hashString(s string) string { + h := fnv.New64a() + h.Write([]byte(s)) + return strconv.FormatUint(h.Sum64(), 8) +} func (g *llmService) AddImportJob(req *types.LLMImportRequest) (resp *types.LLMResponse, err error) { auth := g.ctx.Value(auth.CtxKeyUserInfo{}).(*auth.AuthData) config := db.LLMConfig{ @@ -40,7 +47,7 @@ func (g *llmService) AddImportJob(req *types.LLMImportRequest) (resp *types.LLMR Host: config.Host, UserName: config.UserName, UserPrompt: req.UserPrompt, - JobID: space + "_" + time.Now().Format("20060102150405000"), + JobID: time.Now().Format("20060102150405000") + "_" + hashString(space), } task := &db.TaskInfo{ BID: job.JobID, diff --git a/server/api/studio/pkg/llm/importjob.go b/server/api/studio/pkg/llm/importjob.go index 16196bb8..d73be879 100644 --- a/server/api/studio/pkg/llm/importjob.go +++ b/server/api/studio/pkg/llm/importjob.go @@ -467,7 +467,7 @@ func (i *ImportJob) MakeGQLFile(filePath string) ([]string, error) { for key, field := range typeSchema { value, ok := props[key] - if !ok { + if !ok || value == nil { if field.Nullable { continue } else { @@ -483,9 +483,13 @@ func (i *ImportJob) MakeGQLFile(filePath string) ([]string, error) { valueStr += "," } if strings.Contains(strings.ToLower(field.DataType), "string") { - valueStr += fmt.Sprintf(`"%v"`, value) + valueStr += fmt.Sprintf(`"%s"`, value) } else { - valueStr += fmt.Sprintf(`%v`, value) + if value == nil { + valueStr += "null" + } else { + valueStr += fmt.Sprintf(`%s`, value) + } } } @@ -580,11 +584,15 @@ func (i *ImportJob) RunGQLFile(gqls []string) error { return nil } +func replaceBackslash(s string) string { + return strings.ReplaceAll(s, "\\", "\\\\") +} + func (i *ImportJob) MakeSchema() error { schema := Schema{ Space: i.LLMJob.Space, } - gql := fmt.Sprintf("DESCRIBE SPACE `%s`", i.LLMJob.Space) + gql := fmt.Sprintf("DESCRIBE SPACE `%s`", replaceBackslash(i.LLMJob.Space)) spaceInfo, err := client.Execute(i.NSID, i.LLMJob.Space, []string{gql}) if err != nil { return err @@ -612,7 +620,7 @@ func (i *ImportJob) MakeSchema() error { tag := NodeType{ Type: row["Name"].(string), } - gql = fmt.Sprintf("DESCRIBE TAG `%s`", tag.Type) + gql = fmt.Sprintf("DESCRIBE TAG `%s`", replaceBackslash(tag.Type)) res, err := client.Execute(i.NSID, i.LLMJob.Space, []string{gql}) if err != nil { return err @@ -649,7 +657,7 @@ func (i *ImportJob) MakeSchema() error { edge := EdgeType{ Type: row["Name"].(string), } - gql = fmt.Sprintf("DESCRIBE EDGE `%s`", edge.Type) + gql = fmt.Sprintf("DESCRIBE EDGE `%s`", replaceBackslash(edge.Type)) res, err := client.Execute(i.NSID, i.LLMJob.Space, []string{gql}) if err != nil { return err diff --git a/server/api/studio/pkg/llm/transformer/qwen.go b/server/api/studio/pkg/llm/transformer/qwen.go index 92d104a3..dfd78660 100644 --- a/server/api/studio/pkg/llm/transformer/qwen.go +++ b/server/api/studio/pkg/llm/transformer/qwen.go @@ -89,8 +89,16 @@ func (o *Qwen) HandleResponse(resp *http.Response, callback func(str string)) (m return nil, fmt.Errorf("failed to parse response data: %s %v", string(bodyBytes), err) } - respData["choices"] = respData["output"].(map[string]any)["choices"] - usage := respData["usage"].(map[string]any) + output, ok := respData["output"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("output is not a map[string]interface{},data: %s", string(bodyBytes)) + } + respData["choices"] = output["choices"] + + usage, ok := respData["usage"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("usage is not a map[string]interface{}, data:%s", string(bodyBytes)) + } usage["completion_tokens"] = usage["output_tokens"] usage["prompt_tokens"] = usage["input_tokens"] return respData, nil