diff --git a/packages/libro-prompt-cell/package.json b/packages/libro-prompt-cell/package.json index b59861e7..381e7f9c 100644 --- a/packages/libro-prompt-cell/package.json +++ b/packages/libro-prompt-cell/package.json @@ -51,7 +51,10 @@ "@difizen/libro-codemirror": "^0.1.14", "@difizen/libro-common": "^0.1.14", "@difizen/libro-core": "^0.1.14", + "@ant-design/icons": "^5.1.0", + "@difizen/mana-l10n": "latest", "@difizen/mana-app": "latest", + "classnames": "^2.3.2", "highlight.js": "^11.8.0", "marked": "^5.1.1", "marked-highlight": "^2.0.1", diff --git a/packages/libro-prompt-cell/src/index.less b/packages/libro-prompt-cell/src/index.less index d4c58df5..b2f1c278 100644 --- a/packages/libro-prompt-cell/src/index.less +++ b/packages/libro-prompt-cell/src/index.less @@ -1,3 +1,8 @@ +.libro-prompt-cell-header { + display: flex; + align-items: center; +} + .libro-llm-hljs { overflow-x: auto; white-space: pre-wrap !important; diff --git a/packages/libro-prompt-cell/src/libro-formatter-prompt-magic-contribution.ts b/packages/libro-prompt-cell/src/libro-formatter-prompt-magic-contribution.ts index 4fd92b32..a891bf09 100644 --- a/packages/libro-prompt-cell/src/libro-formatter-prompt-magic-contribution.ts +++ b/packages/libro-prompt-cell/src/libro-formatter-prompt-magic-contribution.ts @@ -10,6 +10,7 @@ import { singleton } from '@difizen/mana-app'; export interface PromptDecodedFormatter extends DefaultDecodedFormatter { modelType?: string; + variableName?: string; } @singleton({ contrib: FormatterContribution }) @@ -29,6 +30,7 @@ export class FormatterPromptMagicContribution const promptObj = { model_name: source.modelType || 'chatgpt', prompt: source.value, + variable_name: source.variableName, }; const encodeValue = `%%prompt \n${JSON.stringify(promptObj)}`; return { @@ -41,14 +43,15 @@ export class FormatterPromptMagicContribution decode = (formatterValue: DefaultEncodedFormatter) => { const value = concatMultilineString(formatterValue.source); - if (value.startsWith('%%prompt \n')) { const run = value.split('%%prompt \n')[1]; const runValue = JSON.parse(run); const codeValue = runValue.prompt; const modelType = runValue.model_name; + const variableName = runValue.variable_name; return { value: codeValue, + variableName, modelType, }; } diff --git a/packages/libro-prompt-cell/src/prompt-cell-model.ts b/packages/libro-prompt-cell/src/prompt-cell-model.ts index b8557385..0d9aa373 100644 --- a/packages/libro-prompt-cell/src/prompt-cell-model.ts +++ b/packages/libro-prompt-cell/src/prompt-cell-model.ts @@ -37,6 +37,10 @@ export class LibroPromptCellModel kernelExecuting = false; modelType: string; + + @prop() + variableName: string; + @prop() executing: boolean; @prop() diff --git a/packages/libro-prompt-cell/src/prompt-cell-view.tsx b/packages/libro-prompt-cell/src/prompt-cell-view.tsx index 56127a84..08a576e1 100644 --- a/packages/libro-prompt-cell/src/prompt-cell-view.tsx +++ b/packages/libro-prompt-cell/src/prompt-cell-view.tsx @@ -38,8 +38,10 @@ import { Deferred } from '@difizen/mana-app'; import { Select, Tag } from 'antd'; import React, { useEffect, useState } from 'react'; -import type { LibroPromptCellModel } from './prompt-cell-model.js'; +import { LibroPromptCellModel } from './prompt-cell-model.js'; import { PromptScript } from './prompt-cell-script.js'; +import { VariableNameInput } from './variable-handler/index.js'; +import './index.less'; export interface ChatItem { name: string; @@ -99,7 +101,7 @@ const SelectionItemLabel: React.FC<{ item: ChatItem }> = (props: { ); }; -const CellEditor: React.FC = () => { +const CellEditorRaw: React.FC = () => { const instance = useInject(ViewInstance); useEffect(() => { if (instance.editorView?.editor) { @@ -109,13 +111,15 @@ const CellEditor: React.FC = () => { return <>{instance.editorView && }; }; -export const CellEditorMemo = React.memo(CellEditor); +export const CellEditor = React.memo(CellEditorRaw); const PropmtEditorViewComponent = React.forwardRef( function MaxPropmtEditorViewComponent(props, ref) { const instance = useInject(ViewInstance); const [selectedModel, setSelectedModel] = useState('暂无内置模型'); useEffect(() => { + // TODO: Data initialization should not depend on view initialization, which causes limitations in usage scenarios and multiple renderings. + instance.model.variableName = instance.model.decodeObject['variableName']; instance .updateChatList() .then(() => { @@ -138,11 +142,7 @@ const PropmtEditorViewComponent = React.forwardRef( }, []); const handleChange = (value: string) => { - instance.model.modelType = value; - instance.model.decodeObject = { - ...instance.model.decodeObject, - modelType: value, - }; + instance.handleModelNameChange(value); setSelectedModel(value); }; @@ -166,8 +166,13 @@ const PropmtEditorViewComponent = React.forwardRef( }} /> + - + ); }, @@ -464,7 +469,10 @@ export class LibroPromptCellView extends LibroExecutableCellView { updateChatList = async () => { return this.fetch( - { code: this.promptScript.toList, store_history: false }, + { + code: this.promptScript.toList, + store_history: false, + }, this.handleQueryResponse, ); }; @@ -505,4 +513,28 @@ export class LibroPromptCellView extends LibroExecutableCellView { break; } }; + + checkVariableNameAvailable = (variableName: string) => { + return ( + this.parent.model.cells.findIndex( + (cell) => + cell.model instanceof LibroPromptCellModel && + cell.model.variableName === variableName, + ) > -1 + ); + }; + handleModelNameChange = (key: string) => { + this.model.decodeObject = { + ...this.model.decodeObject, + modelType: key, + }; + this.model.modelType = key; + }; + handleVariableNameChange = (variableName: string) => { + this.model.decodeObject = { + ...this.model.decodeObject, + variableName: variableName, + }; + this.model.variableName = variableName; + }; } diff --git a/packages/libro-prompt-cell/src/variable-handler/index.less b/packages/libro-prompt-cell/src/variable-handler/index.less new file mode 100644 index 00000000..2ad4f22d --- /dev/null +++ b/packages/libro-prompt-cell/src/variable-handler/index.less @@ -0,0 +1,46 @@ +.libro-variable-name-input { + border-left: 1px solid var(--mana-color-border); + + svg { + margin-left: 4px; + } + + &-label { + padding-left: 16px; + color: var(--mana-libro-cell-header-title); + font-weight: 400; + font-size: 14px; + font-family: SFProText; + line-height: 36px; + letter-spacing: 0; + } + + &-input { + width: 120px; + height: 32px; + border: 1px solid #d6d8da; + border-radius: 6px; + box-shadow: unset; + } + + &-popover { + vertical-align: middle; + } + + &-warning-text { + margin-top: 4px; + color: #faad14; + } + + &-actions { + padding: 2px 0; + + span + span { + margin-left: 8px; + } + + span { + color: #1890ff; + } + } +} diff --git a/packages/libro-prompt-cell/src/variable-handler/index.ts b/packages/libro-prompt-cell/src/variable-handler/index.ts new file mode 100644 index 00000000..b1d484cd --- /dev/null +++ b/packages/libro-prompt-cell/src/variable-handler/index.ts @@ -0,0 +1 @@ +export * from './variable-name-input.js'; diff --git a/packages/libro-prompt-cell/src/variable-handler/variable-name-input.tsx b/packages/libro-prompt-cell/src/variable-handler/variable-name-input.tsx new file mode 100644 index 00000000..96fd3840 --- /dev/null +++ b/packages/libro-prompt-cell/src/variable-handler/variable-name-input.tsx @@ -0,0 +1,121 @@ +import { EditFilled } from '@ant-design/icons'; +import { LirboContextKey } from '@difizen/libro-core'; +import { useInject } from '@difizen/mana-app'; +import { l10n } from '@difizen/mana-l10n'; +import { Input, Popover } from 'antd'; +import classNames from 'classnames'; +import type { FC } from 'react'; +import { useEffect } from 'react'; +import { useRef } from 'react'; +import { useCallback, useState } from 'react'; +import './index.less'; + +interface VariableNameInputPopoverContentProps { + value: string; + handleVariableNameChange: (variableName: string) => void; + checkVariableNameAvailable: (variableName: string) => boolean; + cancel: () => void; +} + +export const VariableNameInputPopoverContent: FC< + VariableNameInputPopoverContentProps +> = (props: VariableNameInputPopoverContentProps) => { + const { value, handleVariableNameChange, checkVariableNameAvailable, cancel } = props; + const [variableNameAvailable, setVariableNameAvailable] = useState(true); + const [variableName, setVariableName] = useState(value); + + useEffect(() => { + setVariableName(value); + }, [value]); + + const handleValueChange = useCallback( + (e: React.ChangeEvent) => { + if (checkVariableNameAvailable(e.target.value)) { + setVariableNameAvailable(false); + } else { + setVariableNameAvailable(true); + } + setVariableName(e.target.value); + }, + [checkVariableNameAvailable], + ); + + const handValueSave = useCallback(() => { + handleVariableNameChange(variableName); + cancel(); + }, [variableName, handleVariableNameChange, cancel]); + + return ( + <> + + + {!variableNameAvailable && ( + + {l10n.t('当前变量名已存在')} + + )} + +
+ {l10n.t('取消')} + {l10n.t('保存')} +
+ + ); +}; + +interface VariableNameInputProps { + value: string; + handleVariableNameChange: (variableName: string) => void; + checkVariableNameAvailable: (variableName: string) => boolean; + classname?: string; +} +const variableNameInputCls = 'libro-variable-name-input'; +export const VariableNameInput: FC = ( + props: VariableNameInputProps, +) => { + const { value } = props; + const [popoverVisible, setPopoverVisible] = useState(false); + const contextKey = useInject(LirboContextKey); + const ref = useRef(null); + return ( +
+ Save: + {value || '...'} + + { + setPopoverVisible(false); + }} + /> + } + placement="bottomLeft" + open={popoverVisible} + onOpenChange={(visible) => { + if (visible) { + contextKey.disableCommandMode(); + } else { + contextKey.enableCommandMode(); + } + setPopoverVisible(visible); + }} + getPopupContainer={() => { + return ref.current?.getElementsByClassName( + variableNameInputCls, + )[0] as HTMLElement; + }} + trigger="click" + > + + + +
+ ); +};