forked from Leaf/amber-ui
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
29f650c83d | ||
af70f11e9b | |||
07547899ef | |||
|
9aba890b46 | ||
38b870438e | |||
|
1f8238aabe | ||
1cf70e6bb4 | |||
|
b0f52ca420 | ||
|
25190adaaf | ||
|
6cde2335f5 | ||
0e8b8aad9b | |||
|
99a1736d26 | ||
75d5f8db66 | |||
0bcb10c810 | |||
|
d2889a8bcc | ||
629ae9300f | |||
5e5f7cd76e | |||
|
84f643f271 | ||
66290ea1d0 | |||
bbfb41f706 | |||
|
3f60f29806 | ||
fc604a9117 | |||
74a35b1f11 | |||
|
19e51a6bf3 | ||
1826dab66b | |||
6d676afac6 | |||
f3c7c40f1e | |||
a53f1b1710 | |||
|
ea7a2aa883 | ||
3935e8c179 | |||
65bbced587 | |||
|
b133042d77 | ||
71cfbb722a | |||
809405f3d9 | |||
|
deab452156 | ||
|
7fc0dac915 | ||
|
fb922e24ae | ||
|
79b7687b09 | ||
f46254b323 | |||
3d259b7331 | |||
81a5e85a3a |
@ -68,6 +68,9 @@
|
||||
"onWatcherCleanup": true,
|
||||
"useId": true,
|
||||
"useModel": true,
|
||||
"useTemplateRef": true
|
||||
"useTemplateRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true
|
||||
}
|
||||
}
|
||||
|
200
api/swagger.yaml
200
api/swagger.yaml
@ -9,6 +9,8 @@ definitions:
|
||||
type: boolean
|
||||
disable_memory:
|
||||
type: boolean
|
||||
disable_web_browsing:
|
||||
type: boolean
|
||||
enable_memory_for_assistant_api:
|
||||
type: boolean
|
||||
id:
|
||||
@ -245,6 +247,24 @@ definitions:
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
entity.ScenePrompt:
|
||||
properties:
|
||||
assistant:
|
||||
$ref: '#/definitions/entity.Assistant'
|
||||
assistant_id:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
id:
|
||||
description: Id schema.EntityId `gorm:"primarykey" json:"id,string"`
|
||||
type: integer
|
||||
label:
|
||||
type: string
|
||||
prompt:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
entity.Tool:
|
||||
properties:
|
||||
api_key:
|
||||
@ -267,8 +287,31 @@ definitions:
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
page.PagedResult-entity_ChatMessageList:
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/entity.ChatMessageList'
|
||||
type: array
|
||||
page:
|
||||
description: 当前页码
|
||||
type: integer
|
||||
page_size:
|
||||
description: 每页大小
|
||||
type: integer
|
||||
total_count:
|
||||
description: 数据总条数
|
||||
type: integer
|
||||
total_pages:
|
||||
description: 总页数
|
||||
type: integer
|
||||
type: object
|
||||
page.PagedResult-schema_AssistantPublic:
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/schema.AssistantPublic'
|
||||
@ -374,6 +417,11 @@ definitions:
|
||||
- true
|
||||
- false
|
||||
type: boolean
|
||||
disable_web_browsing:
|
||||
enum:
|
||||
- true
|
||||
- false
|
||||
type: boolean
|
||||
enable_memory_for_assistant_api:
|
||||
enum:
|
||||
- true
|
||||
@ -488,6 +536,18 @@ definitions:
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
schema.CreateAssistantScenePromptRequest:
|
||||
properties:
|
||||
label:
|
||||
maxLength: 20
|
||||
type: string
|
||||
prompt:
|
||||
maxLength: 512
|
||||
type: string
|
||||
required:
|
||||
- label
|
||||
- prompt
|
||||
type: object
|
||||
schema.CurrentUserResponse:
|
||||
properties:
|
||||
ip:
|
||||
@ -1074,6 +1134,104 @@ paths:
|
||||
summary: 绑定资料库
|
||||
tags:
|
||||
- assistant
|
||||
/api/v1/assistants/{id}/scene_prompts:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.ResponseBody'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/entity.ScenePrompt'
|
||||
type: array
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 列出当前助理的场景 Prompt
|
||||
tags:
|
||||
- assistant
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: CreateAssistantScenePromptRequest
|
||||
in: body
|
||||
name: CreateAssistantScenePromptRequest
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.CreateAssistantScenePromptRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.ResponseBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/entity.ScenePrompt'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 创建场景 Prompt
|
||||
tags:
|
||||
- assistant
|
||||
/api/v1/assistants/{id}/scene_prompts/{scene_id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- in: path
|
||||
name: scene_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 删除场景 Prompt
|
||||
tags:
|
||||
- assistant
|
||||
/api/v1/assistants/{id}/tools:
|
||||
get:
|
||||
consumes:
|
||||
@ -1913,6 +2071,48 @@ paths:
|
||||
summary: 添加聊天记录
|
||||
tags:
|
||||
- chat_message
|
||||
/api/v1/chats/{id}/messages/paginate:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取一个对话的所有聊天记录
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.ResponseBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/page.PagedResult-entity_ChatMessageList'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/schema.ResponseBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 分页获取聊天记录
|
||||
tags:
|
||||
- chat_message
|
||||
/api/v1/files/download/{hash}:
|
||||
get:
|
||||
consumes:
|
||||
|
@ -17,7 +17,7 @@ spec:
|
||||
- name: leaf
|
||||
containers:
|
||||
- name: amber
|
||||
image: leafdev.top/leaf/amber-ui:dbdc39f
|
||||
image: leafdev.top/leaf/amber-ui:af70f11
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
@ -11,18 +11,19 @@
|
||||
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kangc/v-md-editor": "^2.3.18",
|
||||
"@iktakahiro/markdown-it-katex": "^4.0.1",
|
||||
"@notable/html2markdown": "^2.0.3",
|
||||
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.7.7",
|
||||
"highlight.js": "^11.10.0",
|
||||
"js-base64": "^3.7.7",
|
||||
"katex": "^0.16.11",
|
||||
"lottie-web": "^5.12.2",
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^2.2.2",
|
||||
"pinia-plugin-persistedstate": "^4.0.0",
|
||||
"vooks": "^0.2.12"
|
||||
"vooks": "^0.2.12",
|
||||
"vue-i18n": "10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.18.10",
|
||||
@ -47,6 +48,7 @@
|
||||
"naive-ui": "^2.39.0",
|
||||
"postcss": "^8.4.45",
|
||||
"prettier": "^3.3.3",
|
||||
"sass-embedded": "^1.79.5",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.2",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
|
534
src/api/api.ts
534
src/api/api.ts
@ -147,6 +147,68 @@ export interface ApiV1AssistantsIdKeysPost200Response {
|
||||
*/
|
||||
'success'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ApiV1AssistantsIdScenePromptsGet200Response
|
||||
*/
|
||||
export interface ApiV1AssistantsIdScenePromptsGet200Response {
|
||||
/**
|
||||
*
|
||||
* @type {Array<EntityScenePrompt>}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsGet200Response
|
||||
*/
|
||||
'data'?: Array<EntityScenePrompt>;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsGet200Response
|
||||
*/
|
||||
'error'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsGet200Response
|
||||
*/
|
||||
'message'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsGet200Response
|
||||
*/
|
||||
'success'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ApiV1AssistantsIdScenePromptsPost200Response
|
||||
*/
|
||||
export interface ApiV1AssistantsIdScenePromptsPost200Response {
|
||||
/**
|
||||
*
|
||||
* @type {EntityScenePrompt}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsPost200Response
|
||||
*/
|
||||
'data'?: EntityScenePrompt;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsPost200Response
|
||||
*/
|
||||
'error'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsPost200Response
|
||||
*/
|
||||
'message'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ApiV1AssistantsIdScenePromptsPost200Response
|
||||
*/
|
||||
'success'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -476,6 +538,37 @@ export interface ApiV1ChatsIdMessagesGet200Response {
|
||||
*/
|
||||
'success'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ApiV1ChatsIdMessagesPaginateGet200Response
|
||||
*/
|
||||
export interface ApiV1ChatsIdMessagesPaginateGet200Response {
|
||||
/**
|
||||
*
|
||||
* @type {PagePagedResultEntityChatMessageList}
|
||||
* @memberof ApiV1ChatsIdMessagesPaginateGet200Response
|
||||
*/
|
||||
'data'?: PagePagedResultEntityChatMessageList;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1ChatsIdMessagesPaginateGet200Response
|
||||
*/
|
||||
'error'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ApiV1ChatsIdMessagesPaginateGet200Response
|
||||
*/
|
||||
'message'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ApiV1ChatsIdMessagesPaginateGet200Response
|
||||
*/
|
||||
'success'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -754,6 +847,12 @@ export interface EntityAssistant {
|
||||
* @memberof EntityAssistant
|
||||
*/
|
||||
'disable_memory'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof EntityAssistant
|
||||
*/
|
||||
'disable_web_browsing'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
@ -1377,6 +1476,55 @@ export interface EntityMemory {
|
||||
*/
|
||||
'user_id'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface EntityScenePrompt
|
||||
*/
|
||||
export interface EntityScenePrompt {
|
||||
/**
|
||||
*
|
||||
* @type {EntityAssistant}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'assistant'?: EntityAssistant;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'assistant_id'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'created_at'?: string;
|
||||
/**
|
||||
* Id schema.EntityId `gorm:\"primarykey\" json:\"id,string\"`
|
||||
* @type {number}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'id'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'label'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'prompt'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof EntityScenePrompt
|
||||
*/
|
||||
'updated_at'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1438,12 +1586,61 @@ export interface EntityTool {
|
||||
*/
|
||||
'user_id'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
export interface PagePagedResultEntityChatMessageList {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'count'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<EntityChatMessageList>}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'data'?: Array<EntityChatMessageList>;
|
||||
/**
|
||||
* 当前页码
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'page'?: number;
|
||||
/**
|
||||
* 每页大小
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'page_size'?: number;
|
||||
/**
|
||||
* 数据总条数
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'total_count'?: number;
|
||||
/**
|
||||
* 总页数
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultEntityChatMessageList
|
||||
*/
|
||||
'total_pages'?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PagePagedResultSchemaAssistantPublic
|
||||
*/
|
||||
export interface PagePagedResultSchemaAssistantPublic {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PagePagedResultSchemaAssistantPublic
|
||||
*/
|
||||
'count'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<SchemaAssistantPublic>}
|
||||
@ -1618,6 +1815,12 @@ export interface SchemaAssistantUpdateRequest {
|
||||
* @memberof SchemaAssistantUpdateRequest
|
||||
*/
|
||||
'disable_memory'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof SchemaAssistantUpdateRequest
|
||||
*/
|
||||
'disable_web_browsing'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
@ -1818,6 +2021,25 @@ export interface SchemaChatUpdateRequest {
|
||||
*/
|
||||
'prompt'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SchemaCreateAssistantScenePromptRequest
|
||||
*/
|
||||
export interface SchemaCreateAssistantScenePromptRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SchemaCreateAssistantScenePromptRequest
|
||||
*/
|
||||
'label': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SchemaCreateAssistantScenePromptRequest
|
||||
*/
|
||||
'prompt': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -2769,6 +2991,127 @@ export const AssistantApiAxiosParamCreator = function (configuration?: Configura
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 列出当前助理的场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsGet: async (id: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('apiV1AssistantsIdScenePromptsGet', 'id', id)
|
||||
const localVarPath = `/api/v1/assistants/{id}/scene_prompts`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication ApiKeyAuth required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 创建场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {SchemaCreateAssistantScenePromptRequest} createAssistantScenePromptRequest CreateAssistantScenePromptRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsPost: async (id: number, createAssistantScenePromptRequest: SchemaCreateAssistantScenePromptRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('apiV1AssistantsIdScenePromptsPost', 'id', id)
|
||||
// verify required parameter 'createAssistantScenePromptRequest' is not null or undefined
|
||||
assertParamExists('apiV1AssistantsIdScenePromptsPost', 'createAssistantScenePromptRequest', createAssistantScenePromptRequest)
|
||||
const localVarPath = `/api/v1/assistants/{id}/scene_prompts`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication ApiKeyAuth required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration)
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(createAssistantScenePromptRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 删除场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {number} sceneId
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsSceneIdDelete: async (id: number, sceneId: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('apiV1AssistantsIdScenePromptsSceneIdDelete', 'id', id)
|
||||
// verify required parameter 'sceneId' is not null or undefined
|
||||
assertParamExists('apiV1AssistantsIdScenePromptsSceneIdDelete', 'sceneId', sceneId)
|
||||
const localVarPath = `/api/v1/assistants/{id}/scene_prompts/{scene_id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)))
|
||||
.replace(`{${"scene_id"}}`, encodeURIComponent(String(sceneId)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication ApiKeyAuth required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 获取 Assistant 所绑定的 Tool
|
||||
@ -3181,6 +3524,47 @@ export const AssistantApiFp = function(configuration?: Configuration) {
|
||||
const localVarOperationServerBasePath = operationServerMap['AssistantApi.apiV1AssistantsIdPut']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 列出当前助理的场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async apiV1AssistantsIdScenePromptsGet(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ApiV1AssistantsIdScenePromptsGet200Response>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.apiV1AssistantsIdScenePromptsGet(id, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['AssistantApi.apiV1AssistantsIdScenePromptsGet']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 创建场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {SchemaCreateAssistantScenePromptRequest} createAssistantScenePromptRequest CreateAssistantScenePromptRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async apiV1AssistantsIdScenePromptsPost(id: number, createAssistantScenePromptRequest: SchemaCreateAssistantScenePromptRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ApiV1AssistantsIdScenePromptsPost200Response>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.apiV1AssistantsIdScenePromptsPost(id, createAssistantScenePromptRequest, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['AssistantApi.apiV1AssistantsIdScenePromptsPost']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 删除场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {number} sceneId
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async apiV1AssistantsIdScenePromptsSceneIdDelete(id: number, sceneId: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.apiV1AssistantsIdScenePromptsSceneIdDelete(id, sceneId, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['AssistantApi.apiV1AssistantsIdScenePromptsSceneIdDelete']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 获取 Assistant 所绑定的 Tool
|
||||
@ -3386,6 +3770,38 @@ export const AssistantApiFactory = function (configuration?: Configuration, base
|
||||
apiV1AssistantsIdPut(id: number, assistantUpdateRequest: SchemaAssistantUpdateRequest, options?: RawAxiosRequestConfig): AxiosPromise<ApiV1AssistantsPost200Response> {
|
||||
return localVarFp.apiV1AssistantsIdPut(id, assistantUpdateRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 列出当前助理的场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsGet(id: number, options?: RawAxiosRequestConfig): AxiosPromise<ApiV1AssistantsIdScenePromptsGet200Response> {
|
||||
return localVarFp.apiV1AssistantsIdScenePromptsGet(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 创建场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {SchemaCreateAssistantScenePromptRequest} createAssistantScenePromptRequest CreateAssistantScenePromptRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsPost(id: number, createAssistantScenePromptRequest: SchemaCreateAssistantScenePromptRequest, options?: RawAxiosRequestConfig): AxiosPromise<ApiV1AssistantsIdScenePromptsPost200Response> {
|
||||
return localVarFp.apiV1AssistantsIdScenePromptsPost(id, createAssistantScenePromptRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 删除场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {number} sceneId
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1AssistantsIdScenePromptsSceneIdDelete(id: number, sceneId: number, options?: RawAxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.apiV1AssistantsIdScenePromptsSceneIdDelete(id, sceneId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary 获取 Assistant 所绑定的 Tool
|
||||
@ -3590,6 +4006,44 @@ export class AssistantApi extends BaseAPI {
|
||||
return AssistantApiFp(this.configuration).apiV1AssistantsIdPut(id, assistantUpdateRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary 列出当前助理的场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssistantApi
|
||||
*/
|
||||
public apiV1AssistantsIdScenePromptsGet(id: number, options?: RawAxiosRequestConfig) {
|
||||
return AssistantApiFp(this.configuration).apiV1AssistantsIdScenePromptsGet(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary 创建场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {SchemaCreateAssistantScenePromptRequest} createAssistantScenePromptRequest CreateAssistantScenePromptRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssistantApi
|
||||
*/
|
||||
public apiV1AssistantsIdScenePromptsPost(id: number, createAssistantScenePromptRequest: SchemaCreateAssistantScenePromptRequest, options?: RawAxiosRequestConfig) {
|
||||
return AssistantApiFp(this.configuration).apiV1AssistantsIdScenePromptsPost(id, createAssistantScenePromptRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary 删除场景 Prompt
|
||||
* @param {number} id
|
||||
* @param {number} sceneId
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssistantApi
|
||||
*/
|
||||
public apiV1AssistantsIdScenePromptsSceneIdDelete(id: number, sceneId: number, options?: RawAxiosRequestConfig) {
|
||||
return AssistantApiFp(this.configuration).apiV1AssistantsIdScenePromptsSceneIdDelete(id, sceneId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary 获取 Assistant 所绑定的 Tool
|
||||
@ -4286,6 +4740,48 @@ export const ChatMessageApiAxiosParamCreator = function (configuration?: Configu
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 获取一个对话的所有聊天记录
|
||||
* @summary 分页获取聊天记录
|
||||
* @param {number} id
|
||||
* @param {number} [page]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1ChatsIdMessagesPaginateGet: async (id: number, page?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('apiV1ChatsIdMessagesPaginateGet', 'id', id)
|
||||
const localVarPath = `/api/v1/chats/{id}/messages/paginate`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication ApiKeyAuth required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration)
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -4427,6 +4923,20 @@ export const ChatMessageApiFp = function(configuration?: Configuration) {
|
||||
const localVarOperationServerBasePath = operationServerMap['ChatMessageApi.apiV1ChatsIdMessagesGet']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 获取一个对话的所有聊天记录
|
||||
* @summary 分页获取聊天记录
|
||||
* @param {number} id
|
||||
* @param {number} [page]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async apiV1ChatsIdMessagesPaginateGet(id: number, page?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ApiV1ChatsIdMessagesPaginateGet200Response>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.apiV1ChatsIdMessagesPaginateGet(id, page, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['ChatMessageApi.apiV1ChatsIdMessagesPaginateGet']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 添加一条消息
|
||||
* @summary 添加聊天记录
|
||||
@ -4496,6 +5006,17 @@ export const ChatMessageApiFactory = function (configuration?: Configuration, ba
|
||||
apiV1ChatsIdMessagesGet(id: number, options?: RawAxiosRequestConfig): AxiosPromise<ApiV1ChatsIdMessagesGet200Response> {
|
||||
return localVarFp.apiV1ChatsIdMessagesGet(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 获取一个对话的所有聊天记录
|
||||
* @summary 分页获取聊天记录
|
||||
* @param {number} id
|
||||
* @param {number} [page]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
apiV1ChatsIdMessagesPaginateGet(id: number, page?: number, options?: RawAxiosRequestConfig): AxiosPromise<ApiV1ChatsIdMessagesPaginateGet200Response> {
|
||||
return localVarFp.apiV1ChatsIdMessagesPaginateGet(id, page, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 添加一条消息
|
||||
* @summary 添加聊天记录
|
||||
@ -4565,6 +5086,19 @@ export class ChatMessageApi extends BaseAPI {
|
||||
return ChatMessageApiFp(this.configuration).apiV1ChatsIdMessagesGet(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个对话的所有聊天记录
|
||||
* @summary 分页获取聊天记录
|
||||
* @param {number} id
|
||||
* @param {number} [page]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ChatMessageApi
|
||||
*/
|
||||
public apiV1ChatsIdMessagesPaginateGet(id: number, page?: number, options?: RawAxiosRequestConfig) {
|
||||
return ChatMessageApiFp(this.configuration).apiV1ChatsIdMessagesPaginateGet(id, page, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一条消息
|
||||
* @summary 添加聊天记录
|
||||
|
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@ -9,7 +9,6 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AccountSettings: typeof import('./components/settings/AccountSettings.vue')['default']
|
||||
AssistantMenu: typeof import('./components/AssistantMenu.vue')['default']
|
||||
AssistantPublistSettings: typeof import('./components/settings/AssistantPublistSettings.vue')['default']
|
||||
AssistantSettings: typeof import('./components/settings/AssistantSettings.vue')['default']
|
||||
Chat: typeof import('./components/chat/Chat.vue')['default']
|
||||
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
|
||||
@ -18,6 +17,7 @@ declare module 'vue' {
|
||||
LeftSettings: typeof import('./components/settings/LeftSettings.vue')['default']
|
||||
LibrarySettings: typeof import('./components/settings/LibrarySettings.vue')['default']
|
||||
Lottie: typeof import('./components/Lottie.vue')['default']
|
||||
Markdown: typeof import('./components/markdown/index.vue')['default']
|
||||
Mask: typeof import('./components/Mask.vue')['default']
|
||||
MemorySettings: typeof import('./components/settings/MemorySettings.vue')['default']
|
||||
Menu: typeof import('./components/Menu.vue')['default']
|
||||
|
@ -27,8 +27,8 @@
|
||||
<n-text depth="3">
|
||||
我们有一些预设提示词,您可以点击来一键输入。
|
||||
</n-text>
|
||||
<n-scrollbar style="max-height: 400px">
|
||||
<n-grid cols="1 s:2 l:3" responsive="screen">
|
||||
<n-scrollbar style="max-height: 400px" class="mt-3">
|
||||
<n-grid cols="1 m:2 l:3" responsive="screen">
|
||||
<n-gi v-for="p in prompts" class="rounded-lg p-2">
|
||||
<n-card
|
||||
:title="p.act"
|
||||
@ -674,11 +674,36 @@ function streamChat(streamId: String, redirect = false) {
|
||||
});
|
||||
break;
|
||||
case "tool_calling":
|
||||
// toolCalling.value = true;
|
||||
if (data.tool_call_message.function_name.startsWith("builtin_")) {
|
||||
if (data.tool_call_message.function_name === "builtin_browser") {
|
||||
let url = data.tool_call_message.args.query_or_url;
|
||||
// 如果是 URL
|
||||
if (url.startsWith("http")) {
|
||||
chatStore.toolName = "正在浏览 " + url;
|
||||
} else {
|
||||
chatStore.toolName = "正在搜索 " + url;
|
||||
}
|
||||
} else if (data.tool_call_message.function_name === "builtin_calculator") {
|
||||
chatStore.toolName = "正在计算";
|
||||
} else if (
|
||||
data.tool_call_message.function_name === "builtin_generate_image"
|
||||
) {
|
||||
chatStore.toolName = "生成图片中";
|
||||
} else if (
|
||||
data.tool_call_message.function_name === "builtin_describe_image"
|
||||
) {
|
||||
chatStore.toolName = "正在理解图片";
|
||||
}
|
||||
} else {
|
||||
chatStore.toolName =
|
||||
"正在执行 " +
|
||||
data.tool_call_message.tool_name +
|
||||
" 中的 " +
|
||||
data.tool_call_message.function_name;
|
||||
}
|
||||
|
||||
// toolCalling.value = true;
|
||||
|
||||
// toolName.value =
|
||||
// data.tool_call_message.tool_name +
|
||||
// " 中的 " +
|
||||
|
@ -36,7 +36,7 @@
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
<div v-else>
|
||||
<div v-else class="mt-8">
|
||||
<n-result
|
||||
status="404"
|
||||
title="你还没有对话"
|
||||
@ -99,6 +99,9 @@ const deleteChat = async (chatId: number) => {
|
||||
onPositiveClick: async () => {
|
||||
await getApi().Chat.apiV1ChatsIdDelete(chatId);
|
||||
await getChats();
|
||||
if (parseInt(router.currentRoute.value.params.id) === chatId) {
|
||||
router.push("/")
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -35,7 +35,14 @@
|
||||
</div>
|
||||
</n-flex>
|
||||
</div>
|
||||
<div v-else-if="(message.role === 'user' || message.role === 'user_hide' || message.role === 'user_later') && message.content">
|
||||
<div
|
||||
v-else-if="
|
||||
(message.role === 'user' ||
|
||||
message.role === 'user_hide' ||
|
||||
message.role === 'user_later') &&
|
||||
message.content
|
||||
"
|
||||
>
|
||||
<!-- 用户消息 -->
|
||||
<n-flex justify="end">
|
||||
<div class="flex items-center flex-nowrap">
|
||||
@ -51,11 +58,14 @@
|
||||
{{ userStore.user.name }}
|
||||
</n-divider>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
v-if="mdInited"
|
||||
class="break-all break-words markdown-body"
|
||||
v-html="mdIt.render(message.content)"
|
||||
></div>
|
||||
></div> -->
|
||||
<div>
|
||||
<Markdown :typing="false" :content="message.content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative h-full">
|
||||
@ -103,11 +113,10 @@
|
||||
{{ message.assistant?.name }}
|
||||
</n-divider>
|
||||
</div>
|
||||
<div
|
||||
v-if="mdInited"
|
||||
class="break-all break-words markdown-body"
|
||||
v-html="mdIt.render(message.content)"
|
||||
></div>
|
||||
|
||||
<div>
|
||||
<Markdown :typing="false" :content="message.content" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div v-html="mdIt.render(message.content)"></div> -->
|
||||
|
||||
@ -126,50 +135,9 @@ import { Ref } from "vue";
|
||||
import { EntityChatMessage } from "@/api";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import leaflowPng from "@/assets/images/leaflow.png";
|
||||
import markdownKatex from "@traptitech/markdown-it-katex";
|
||||
import markdownIt from "markdown-it";
|
||||
// highlightjs
|
||||
import hljs from "highlight.js";
|
||||
import Markdown from "@/components/markdown/index.vue";
|
||||
import config from "@/config/config";
|
||||
|
||||
const mdIt = markdownIt();
|
||||
const mdInited = ref(true);
|
||||
|
||||
const unsupportedLanguages = ["assembly", "blade", "vue"];
|
||||
mdIt.options.highlight = function (str: string, lang: string) {
|
||||
// TODO: 前面的区域以后再来探索吧
|
||||
lang = "text"
|
||||
if (!lang || unsupportedLanguages.includes(lang)) {
|
||||
// return str;
|
||||
lang = "text"
|
||||
}
|
||||
|
||||
return hljs.highlight(str, { language: lang }).value;
|
||||
};
|
||||
mdIt.use(markdownKatex, {
|
||||
throwOnError: false,
|
||||
errorColor: "#cc0000",
|
||||
output: "html",
|
||||
});
|
||||
|
||||
// async function initMD() {
|
||||
// mdIt.use(
|
||||
// await Shiki({
|
||||
// themes: {
|
||||
// light: "vitesse-light",
|
||||
// dark: "vitesse-dark",
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
// mdIt.use(markdownKatex, {
|
||||
// throwOnError: false,
|
||||
// errorColor: "#cc0000",
|
||||
// output: "html",
|
||||
// });
|
||||
|
||||
// mdInited.value = true;
|
||||
// }
|
||||
const userStore = useUserStore();
|
||||
|
||||
const props = defineProps({
|
||||
@ -182,7 +150,4 @@ const props = defineProps({
|
||||
const chat_messages = toRef(props, "chat_messages") as Ref<EntityChatMessage[]>;
|
||||
const fileBaseUrl = config.backend + "/api/v1/files";
|
||||
|
||||
onMounted(() => {
|
||||
// initMD();
|
||||
});
|
||||
</script>
|
||||
|
17
src/components/markdown/aPlugin/index.ts
Normal file
17
src/components/markdown/aPlugin/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
|
||||
export interface APluginOptions {}
|
||||
|
||||
export const aPlugin: PluginWithOptions<APluginOptions> = (
|
||||
md,
|
||||
options = {}
|
||||
): void => {
|
||||
md.renderer.rules.link_open = (tokens, idx, options, env, slf) => {
|
||||
const aIndex = tokens[idx].attrIndex('href')
|
||||
|
||||
if (aIndex !== -1) {
|
||||
tokens[idx].attrs.push(['target', '_blank'])
|
||||
}
|
||||
return slf.renderToken(tokens, idx, options)
|
||||
}
|
||||
}
|
52
src/components/markdown/codePlugin/index.ts
Normal file
52
src/components/markdown/codePlugin/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
|
||||
import { resolveLanguage } from './resolveLanguage.js'
|
||||
import { resolveLineNumbers } from './resolveLineNumbers.js'
|
||||
|
||||
export interface CodePluginOptions {
|
||||
lineNumbers?: boolean | number
|
||||
}
|
||||
|
||||
export const codePlugin: PluginWithOptions<CodePluginOptions> = (
|
||||
md,
|
||||
{ lineNumbers = true } = {}
|
||||
): void => {
|
||||
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
|
||||
const token = tokens[idx]
|
||||
const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''
|
||||
const language = resolveLanguage(info)
|
||||
const languageClass = `${options.langPrefix}${language.name}`
|
||||
|
||||
const code =
|
||||
options.highlight?.(token.content, language.name, '') ||
|
||||
md.utils.escapeHtml(token.content)
|
||||
|
||||
token.attrJoin('class', languageClass)
|
||||
let result = code.startsWith('<pre')
|
||||
? code
|
||||
: `<pre${slf.renderAttrs(token)}><code>${code}</code></pre>`
|
||||
result = `<div data-ext="${language.ext}" v-pre class="code-copy-line">
|
||||
<div class="code-copy-btn">复制代码</div>
|
||||
</div>${result}`
|
||||
const lines = code.split('\n').slice(0, -1)
|
||||
|
||||
const useLineNumbers =
|
||||
resolveLineNumbers(info) ??
|
||||
(typeof lineNumbers === 'number'
|
||||
? lines.length >= lineNumbers
|
||||
: lineNumbers)
|
||||
if (useLineNumbers) {
|
||||
const lineNumbersCode = lines
|
||||
.map(() => `<div class="line-number"></div>`)
|
||||
.join('')
|
||||
|
||||
result = `<div class="line-numbers" aria-hidden="true">${lineNumbersCode}</div>${result}`
|
||||
}
|
||||
|
||||
result = `<div><div class="${languageClass}${
|
||||
useLineNumbers ? ' line-numbers-mode' : ''
|
||||
}"><div class="pre-code-scroll">${result}</div></div></div>`
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
105
src/components/markdown/codePlugin/languages.ts
Normal file
105
src/components/markdown/codePlugin/languages.ts
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Language type for syntax highlight
|
||||
*/
|
||||
export interface HighlightLanguage {
|
||||
/**
|
||||
* Name of the language
|
||||
*
|
||||
* The name to be used for the class name,
|
||||
* e.g. `class="language-typescript"`
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Extension of the language
|
||||
*
|
||||
* The file extension, which will be used for the
|
||||
* class name, e.g. `class="ext-ts"`
|
||||
*/
|
||||
ext: string
|
||||
|
||||
/**
|
||||
* Aliases that point to this language
|
||||
*
|
||||
* Do not conflict with other languages
|
||||
*/
|
||||
aliases: string[]
|
||||
}
|
||||
|
||||
export const languageBash: HighlightLanguage = {
|
||||
name: 'bash',
|
||||
ext: 'sh',
|
||||
aliases: ['bash', 'sh', 'shell', 'zsh']
|
||||
}
|
||||
|
||||
export const languageCsharp: HighlightLanguage = {
|
||||
name: 'csharp',
|
||||
ext: 'cs',
|
||||
aliases: ['cs', 'csharp']
|
||||
}
|
||||
|
||||
export const languageDocker: HighlightLanguage = {
|
||||
name: 'docker',
|
||||
ext: 'docker',
|
||||
aliases: ['docker', 'dockerfile']
|
||||
}
|
||||
|
||||
export const languageFsharp: HighlightLanguage = {
|
||||
name: 'fsharp',
|
||||
ext: 'fs',
|
||||
aliases: ['fs', 'fsharp']
|
||||
}
|
||||
|
||||
export const languageJavascript: HighlightLanguage = {
|
||||
name: 'javascript',
|
||||
ext: 'js',
|
||||
aliases: ['javascript', 'js']
|
||||
}
|
||||
|
||||
export const languageKotlin: HighlightLanguage = {
|
||||
name: 'kotlin',
|
||||
ext: 'kt',
|
||||
aliases: ['kotlin', 'kt']
|
||||
}
|
||||
|
||||
export const languageMarkdown: HighlightLanguage = {
|
||||
name: 'markdown',
|
||||
ext: 'md',
|
||||
aliases: ['markdown', 'md']
|
||||
}
|
||||
|
||||
export const languagePython: HighlightLanguage = {
|
||||
name: 'python',
|
||||
ext: 'py',
|
||||
aliases: ['py', 'python']
|
||||
}
|
||||
|
||||
export const languageRuby: HighlightLanguage = {
|
||||
name: 'ruby',
|
||||
ext: 'rb',
|
||||
aliases: ['rb', 'ruby']
|
||||
}
|
||||
|
||||
export const languageRust: HighlightLanguage = {
|
||||
name: 'rust',
|
||||
ext: 'rs',
|
||||
aliases: ['rs', 'rust']
|
||||
}
|
||||
|
||||
export const languageStylus: HighlightLanguage = {
|
||||
name: 'stylus',
|
||||
ext: 'styl',
|
||||
aliases: ['styl', 'stylus']
|
||||
}
|
||||
|
||||
export const languageTypescript: HighlightLanguage = {
|
||||
name: 'typescript',
|
||||
ext: 'ts',
|
||||
aliases: ['ts', 'typescript']
|
||||
}
|
||||
|
||||
export const languageYaml: HighlightLanguage = {
|
||||
name: 'yaml',
|
||||
ext: 'yml',
|
||||
aliases: ['yaml', 'yml']
|
||||
}
|
46
src/components/markdown/codePlugin/resolveLanguage.ts
Normal file
46
src/components/markdown/codePlugin/resolveLanguage.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import * as languages from './languages.js'
|
||||
import type { HighlightLanguage } from './languages.js'
|
||||
|
||||
type LanguagesMap = Record<string, HighlightLanguage>
|
||||
|
||||
/**
|
||||
* A key-value map to get language info from alias
|
||||
*
|
||||
* - key: alias
|
||||
* - value: language
|
||||
*/
|
||||
let languagesMap: LanguagesMap
|
||||
|
||||
/**
|
||||
* Lazy generate languages map
|
||||
*/
|
||||
const getLanguagesMap = (): LanguagesMap => {
|
||||
if (!languagesMap) {
|
||||
languagesMap = Object.values(languages).reduce((result, item) => {
|
||||
item.aliases.forEach((alias) => {
|
||||
result[alias] = item
|
||||
})
|
||||
return result
|
||||
}, {} as LanguagesMap)
|
||||
}
|
||||
|
||||
return languagesMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve language for highlight from token info
|
||||
*/
|
||||
export const resolveLanguage = (info: string): HighlightLanguage => {
|
||||
// get user-defined language alias
|
||||
const alias = info.match(/^([^ :[{]+)/)?.[1] || ''
|
||||
|
||||
// if the alias does not have a match in the map
|
||||
// fallback to the alias itself
|
||||
return (
|
||||
getLanguagesMap()[alias] ?? {
|
||||
name: alias,
|
||||
ext: alias,
|
||||
aliases: [alias]
|
||||
}
|
||||
)
|
||||
}
|
14
src/components/markdown/codePlugin/resolveLineNumbers.ts
Normal file
14
src/components/markdown/codePlugin/resolveLineNumbers.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Resolve the `:line-numbers` / `:no-line-numbers` mark from token info
|
||||
*/
|
||||
export const resolveLineNumbers = (info: string): boolean | null => {
|
||||
if (/:line-numbers\b/.test(info)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (/:no-line-numbers\b/.test(info)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
76
src/components/markdown/customLink/index.ts
Normal file
76
src/components/markdown/customLink/index.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
|
||||
const defaultMarker = '#'
|
||||
interface CustomLinkPluginOptions {
|
||||
marker: string
|
||||
}
|
||||
export const customLinkPlugin: PluginWithOptions<
|
||||
Partial<CustomLinkPluginOptions>
|
||||
> = (md, options = {}): void => {
|
||||
const marker = options.marker || defaultMarker
|
||||
|
||||
md.block.ruler.before(
|
||||
'paragraph',
|
||||
'custom_link',
|
||||
function (state, startLine, endLine, silent) {
|
||||
const pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
const max = state.eMarks[startLine]
|
||||
|
||||
if (pos >= max) {
|
||||
return false
|
||||
}
|
||||
if (silent) {
|
||||
return true
|
||||
}
|
||||
const text = state.src.substring(pos, max)
|
||||
const start = text.indexOf(marker)
|
||||
const end = text.lastIndexOf(marker)
|
||||
if (start < 0 || end < 0 || start == end) {
|
||||
return false
|
||||
}
|
||||
//#...#规则前面的内容
|
||||
const startContent = text.substring(0, start)
|
||||
//#...#规则后面的内容
|
||||
const endContent = text.substring(end + 1)
|
||||
//#...#规则中间的内容
|
||||
const content = text.substring(start + 1, end)
|
||||
|
||||
//插入<div>
|
||||
const token_div_o = state.push('div_open', 'div', 1)
|
||||
token_div_o.map = [startLine, state.line]
|
||||
|
||||
//插入#...#规则前面的内容
|
||||
const token_s = state.push('inline', '', 0)
|
||||
token_s.content = startContent
|
||||
token_s.children = []
|
||||
|
||||
//插入<a class="markdown-custom-link">
|
||||
const token_a_o = state.push('link_open', 'a', 1)
|
||||
token_a_o.attrs = [['class', 'markdown-custom-link']]
|
||||
token_a_o.map = [startLine, state.line]
|
||||
|
||||
//插入#...#规则中间的内容
|
||||
const token = state.push('inline', '', 0)
|
||||
token.content = content
|
||||
token.children = []
|
||||
|
||||
//闭合a标签
|
||||
state.push('link_close', 'a', -1)
|
||||
const token_e = state.push('inline', '', 0)
|
||||
|
||||
//插入#...#规则后面的内容
|
||||
token_e.content = endContent
|
||||
token_e.children = []
|
||||
|
||||
//闭合div标签
|
||||
state.push('div_close', 'div', -1)
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
},
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote']
|
||||
}
|
||||
)
|
||||
}
|
1102
src/components/markdown/github-markdown.css
Normal file
1102
src/components/markdown/github-markdown.css
Normal file
File diff suppressed because it is too large
Load Diff
166
src/components/markdown/index.vue
Normal file
166
src/components/markdown/index.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="markdown-it-container">
|
||||
<div
|
||||
ref="markdownBodyRef"
|
||||
class="markdown-body"
|
||||
@click="handleClick($event)"
|
||||
v-html="result"
|
||||
/>
|
||||
<div v-if="typing" class="markdown-typing" :style="cursorPosition"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import hljs from 'highlight.js'
|
||||
// import 'highlight.js/styles/atom-one-dark.css'
|
||||
// import './github-markdown.css'
|
||||
// @ts-ignore
|
||||
import markdownItMath from '@iktakahiro/markdown-it-katex'
|
||||
import type {CodePluginOptions} from './codePlugin'
|
||||
import {codePlugin} from './codePlugin'
|
||||
import {customLinkPlugin} from './customLink'
|
||||
import {aPlugin} from './aPlugin'
|
||||
|
||||
interface Options extends Partial<MarkdownIt.Options> {
|
||||
lineNumbers?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
content: string
|
||||
html?: boolean
|
||||
breaks?: boolean
|
||||
linkify?: boolean
|
||||
typographer?: boolean
|
||||
// 是否显示代码行
|
||||
lineNumbers?: boolean
|
||||
// 是否显示打字效果
|
||||
typing: boolean
|
||||
}>(),
|
||||
{
|
||||
content: '',
|
||||
html: true,
|
||||
breaks: true,
|
||||
typographer: true,
|
||||
linkify: true,
|
||||
lineNumbers: true,
|
||||
typing: false
|
||||
}
|
||||
)
|
||||
const emit = defineEmits<{
|
||||
(event: 'click-custom-link', value: string): void
|
||||
}>()
|
||||
const result = ref('')
|
||||
const markdownBodyRef = shallowRef<HTMLDivElement>()
|
||||
const createMarkdown = (options: Options) => {
|
||||
const md = new MarkdownIt({
|
||||
...options,
|
||||
langPrefix: 'language-',
|
||||
highlight(str: any, lang: any) {
|
||||
try {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(lang, str, true).value
|
||||
}
|
||||
return hljs.highlightAuto(str).value
|
||||
} catch (error) {
|
||||
return str
|
||||
}
|
||||
}
|
||||
})
|
||||
md.use<CodePluginOptions>(codePlugin, {
|
||||
lineNumbers: options.lineNumbers
|
||||
})
|
||||
md.use(markdownItMath, {
|
||||
output: 'mathml'
|
||||
})
|
||||
md.use(aPlugin)
|
||||
md.use(customLinkPlugin)
|
||||
|
||||
return md
|
||||
}
|
||||
let md: MarkdownIt
|
||||
|
||||
const findLastTextNode = (parent: Node): Node | undefined => {
|
||||
const children = parent.childNodes
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
const node = children[i]
|
||||
if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue!)) {
|
||||
node.nodeValue?.replace(/\S+$/, '')
|
||||
return node
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const lastNode = findLastTextNode(node)
|
||||
if (lastNode) {
|
||||
return lastNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const cursorPosition = reactive({
|
||||
top: 'auto',
|
||||
left: 'auto'
|
||||
})
|
||||
const updateCursor = () => {
|
||||
if (markdownBodyRef.value) {
|
||||
const lastTextNode = findLastTextNode(markdownBodyRef.value)
|
||||
const textNode = document.createTextNode('\u200B')
|
||||
if (lastTextNode) {
|
||||
lastTextNode.parentElement?.appendChild(textNode)
|
||||
} else {
|
||||
markdownBodyRef.value?.appendChild(textNode)
|
||||
}
|
||||
const range = document.createRange()
|
||||
range.setStart(textNode, 0)
|
||||
range.setEnd(textNode, 0)
|
||||
const textRect = range.getBoundingClientRect()
|
||||
const markdownBodyRect = markdownBodyRef.value?.getBoundingClientRect()
|
||||
cursorPosition.left = `${textRect.left - markdownBodyRect.left}px`
|
||||
cursorPosition.top = `${textRect.top - markdownBodyRect.top}px`
|
||||
textNode.remove()
|
||||
}
|
||||
}
|
||||
|
||||
const preprocessContent = (content: string) => {
|
||||
return content.replace(/\n(#.*#)/g, '\n\n$1')
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
md = createMarkdown({
|
||||
html: props.html,
|
||||
breaks: props.breaks,
|
||||
typographer: props.typographer,
|
||||
linkify: props.linkify,
|
||||
lineNumbers: props.lineNumbers
|
||||
})
|
||||
result.value = md.render(preprocessContent(props.content))
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.content,
|
||||
async (value) => {
|
||||
result.value = md?.render(preprocessContent(value))
|
||||
await nextTick()
|
||||
updateCursor()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
const handleClick = async (e: any) => {
|
||||
console.log(e.target)
|
||||
const target: HTMLElement = e.target
|
||||
if (target.className === 'code-copy-btn') {
|
||||
const text = e.target.parentElement.nextElementSibling.textContent
|
||||
// copy text to clipboard
|
||||
await navigator.clipboard.writeText(text)
|
||||
}
|
||||
if (target.className === 'markdown-custom-link') {
|
||||
emit('click-custom-link', target.textContent!)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'markdown.scss';
|
||||
</style>
|
212
src/components/markdown/markdown.scss
Normal file
212
src/components/markdown/markdown.scss
Normal file
@ -0,0 +1,212 @@
|
||||
$line-height: 1.375;
|
||||
:root {
|
||||
--code-bg-color: #35373f;
|
||||
--code-hl-bg-color: rgba(0, 0, 0, 0.66);
|
||||
--code-ln-color: #6e6e7f;
|
||||
--code-ln-wrapper-width: 3.5rem;
|
||||
}
|
||||
@keyframes blink {
|
||||
from,
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.markdown-it-container {
|
||||
.markdown-body {
|
||||
background-color: transparent;
|
||||
font-size: 15px;
|
||||
.markdown-custom-link {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
.markdown-typing {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 14px;
|
||||
transform: translate(4px, 2px) scaleY(1.3);
|
||||
color: #1a202c;
|
||||
background-color: currentColor;
|
||||
animation: blink 0.6s infinite;
|
||||
}
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
code {
|
||||
padding: 0.25rem;
|
||||
margin: 0;
|
||||
font-size: 0.85em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
color: #ccc;
|
||||
background: none;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*='language-'] {
|
||||
padding: 20px 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
font-size: 0.85em;
|
||||
background: #35373f;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*='language-'] {
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
pre,
|
||||
pre[class*='language-'] {
|
||||
line-height: $line-height;
|
||||
border-radius: 6px;
|
||||
overflow: visible;
|
||||
display: inline-block;
|
||||
padding: 20px;
|
||||
code {
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
background-color: transparent !important;
|
||||
border-radius: 0;
|
||||
overflow-wrap: unset;
|
||||
-webkit-font-smoothing: auto;
|
||||
-moz-osx-font-smoothing: auto;
|
||||
}
|
||||
}
|
||||
|
||||
div[class*='language-'] {
|
||||
position: relative;
|
||||
background-color: var(--code-bg-color);
|
||||
border-radius: 6px;
|
||||
padding-top: 32px;
|
||||
margin: 0.85rem 0;
|
||||
overflow: hidden;
|
||||
.code-copy-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #595b63;
|
||||
color: #e0e0e0;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 12px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0 12px;
|
||||
|
||||
&::before {
|
||||
content: attr(data-ext);
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
font-size: 0.75rem;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.code-copy-btn {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
right: 1rem;
|
||||
font-size: 0.75rem;
|
||||
color: #e0e0e0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
pre,
|
||||
pre[class*='language-'] {
|
||||
// force override the background color to be compatible with shiki
|
||||
background: transparent !important;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.pre-code-scroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&:not(.line-numbers-mode) {
|
||||
.line-numbers {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.line-numbers-mode {
|
||||
padding-left: var(--code-ln-wrapper-width);
|
||||
pre {
|
||||
vertical-align: middle;
|
||||
padding: 20px 20px 20px 0;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: var(--code-ln-wrapper-width);
|
||||
text-align: center;
|
||||
color: var(--code-ln-color);
|
||||
padding-top: 52px;
|
||||
line-height: $line-height;
|
||||
counter-reset: line-number;
|
||||
|
||||
.line-number {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
user-select: none;
|
||||
height: #{$line-height - 0.2}em;
|
||||
&::before {
|
||||
display: block;
|
||||
counter-increment: line-number;
|
||||
content: counter(line-number);
|
||||
font-size: 0.8em;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// &::after {
|
||||
// content: "";
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// width: var(--code-ln-wrapper-width);
|
||||
// height: 100%;
|
||||
// border-radius: 6px 0 0 6px;
|
||||
// border-right: 1px solid var(--code-hl-bg-color);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
@ -93,6 +93,14 @@
|
||||
禁用默认提示词
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<n-switch
|
||||
v-model:value="currentAssistant.disable_web_browsing"
|
||||
>
|
||||
</n-switch>
|
||||
禁用联网
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<n-switch v-model:value="currentAssistant.disable_memory">
|
||||
</n-switch>
|
||||
@ -110,10 +118,10 @@
|
||||
允许助理 API 读取记忆
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- <div>
|
||||
<n-switch v-model:value="currentAssistant.public"> </n-switch>
|
||||
公开分享助理(弃用)
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="mt-3">
|
||||
话语随机性 (Temperature)
|
||||
@ -160,6 +168,82 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-divider v-if="!scenePrompts.length" />
|
||||
<div v-else class="mt-10"></div>
|
||||
<div>
|
||||
<div class="flex justify-between align-middle items-center">
|
||||
<div>
|
||||
<n-h3 class="inline">场景提示词</n-h3>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="16"><HelpCircleOutline /></n-icon>
|
||||
</template>
|
||||
<div>
|
||||
<n-text
|
||||
>Amber 可以根据不同的上下文场景来自动附加系统提示词</n-text
|
||||
>
|
||||
</div>
|
||||
</n-popover>
|
||||
</div>
|
||||
<div>
|
||||
<n-button
|
||||
tertiary
|
||||
@click="showCreateScenePromptForm = !showCreateScenePromptForm"
|
||||
>
|
||||
新场景提示词
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="showCreateScenePromptForm" class="mt-3">
|
||||
<n-card title="新场景提示词" role="dialog">
|
||||
<n-input
|
||||
v-model:value="newScenePrompt.label"
|
||||
type="text"
|
||||
placeholder="标签"
|
||||
/>
|
||||
<n-input
|
||||
class="mt-3"
|
||||
v-model:value="newScenePrompt.prompt"
|
||||
type="textarea"
|
||||
placeholder="提示词"
|
||||
/>
|
||||
|
||||
<div class="text-right mt-3">
|
||||
<n-button type="primary" @click="createAssistantScenePrompt"
|
||||
>创建</n-button
|
||||
>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<n-list hoverable clickable v-if="scenePrompts.length" class="mt-3">
|
||||
<n-list-item v-for="c in scenePrompts" :key="c.id">
|
||||
<n-thing :title="c.label">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>{{ c.prompt }}</div>
|
||||
<div>
|
||||
<n-popconfirm
|
||||
@positive-click="deleteAssistantScenePrompt(c.id ?? 0)"
|
||||
>
|
||||
<template #trigger>
|
||||
<n-button quaternary circle type="warning">
|
||||
<template #icon>
|
||||
<n-icon size="16" class="cursor-pointer">
|
||||
<TrashBinOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<div>删除后,这条规则将不再生效</div>
|
||||
</n-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-divider v-if="!userTools.length" />
|
||||
<div v-else class="mt-10"></div>
|
||||
<div>
|
||||
@ -178,7 +262,8 @@
|
||||
<n-text>
|
||||
我们的 API 端点是:{{
|
||||
config.backend
|
||||
}}/api/openai-compatible/v1 。密钥为下方的 API Key,OpenAI 格式不支持智能上下文(不支持近乎无限的上下文)
|
||||
}}/api/openai-compatible/v1 。密钥为下方的 API Key,OpenAI
|
||||
格式不支持智能上下文(不支持近乎无限的上下文)
|
||||
</n-text>
|
||||
</div>
|
||||
</n-popover>
|
||||
@ -194,11 +279,20 @@
|
||||
等做一个请求限制),来防止 API Key 滥用。
|
||||
<br />
|
||||
当然,如果您在自己的私有应用中使用,可以忽略此建议。
|
||||
<br />
|
||||
!! 注意: Amber Assistant Public API 不需要在前面加 sk- !!
|
||||
</div>
|
||||
</n-popconfirm>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<n-p
|
||||
>我们更新了计费系统,在使用此功能之前,请前往
|
||||
<n-a target="_blank" href="https://auth.leaflow.cn/balances"
|
||||
>UserLand</n-a
|
||||
>
|
||||
来添加余额。我们正在且长期会处于测试阶段,不会对您发起真实付费(也就是说完全免费!)</n-p
|
||||
>
|
||||
<n-list hoverable clickable v-if="assistantApiKeys.length">
|
||||
<n-list-item v-for="c in assistantApiKeys" :key="c.id">
|
||||
<n-thing>
|
||||
@ -282,10 +376,11 @@ import {
|
||||
EntityAssistantKey,
|
||||
EntityAssistantTool,
|
||||
EntityLibrary,
|
||||
EntityScenePrompt,
|
||||
EntityTool,
|
||||
} from "@/api";
|
||||
import { useIsMobile } from "@/utils/composables";
|
||||
import { AxiosError } from "axios";
|
||||
import { Axios, AxiosError } from "axios";
|
||||
import config from "@/config/config";
|
||||
|
||||
const dialog = useDialog();
|
||||
@ -300,6 +395,13 @@ const assistants: Ref<EntityAssistant[]> = ref([]);
|
||||
const librarySelects: any = ref([]);
|
||||
const libraries: Ref<EntityLibrary[]> = ref([]);
|
||||
const assistantApiKeys: Ref<EntityAssistantKey[]> = ref([]);
|
||||
const showCreateScenePromptForm = ref(false);
|
||||
const scenePrompts: Ref<EntityScenePrompt[]> = ref([]);
|
||||
|
||||
const newScenePrompt = ref({
|
||||
label: "",
|
||||
prompt: "",
|
||||
});
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const drawerWidth = computed(() => {
|
||||
@ -338,6 +440,7 @@ const showEditAssistant = async (id: number) => {
|
||||
await getAssistantsKeys();
|
||||
|
||||
getTools();
|
||||
getAssistantScenePrompts();
|
||||
};
|
||||
|
||||
const editAssistant = async () => {
|
||||
@ -463,7 +566,83 @@ const bindOrUnbind = async (id: number) => {
|
||||
getTools();
|
||||
};
|
||||
|
||||
const getAssistantScenePrompts = async () => {
|
||||
scenePrompts.value =
|
||||
(
|
||||
await getApi().Assistant.apiV1AssistantsIdScenePromptsGet(
|
||||
currentAssistantId.value
|
||||
)
|
||||
).data.data ?? [];
|
||||
};
|
||||
|
||||
const createAssistantScenePrompt = async () => {
|
||||
getApi()
|
||||
.Assistant.apiV1AssistantsIdScenePromptsPost(
|
||||
currentAssistantId.value,
|
||||
newScenePrompt.value
|
||||
)
|
||||
.then(() => {
|
||||
getAssistantScenePrompts();
|
||||
newScenePrompt.value = {
|
||||
label: "",
|
||||
prompt: "",
|
||||
};
|
||||
})
|
||||
.catch((e: AxiosError) => {
|
||||
console.log(e);
|
||||
dialog.error({
|
||||
title: "创建失败",
|
||||
// @ts-ignore
|
||||
content: e.response?.data?.error ?? e.response?.data?.message,
|
||||
positiveText: "好",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAssistantScenePrompt = async (id: number) => {
|
||||
await getApi().Assistant.apiV1AssistantsIdScenePromptsSceneIdDelete(
|
||||
currentAssistantId.value,
|
||||
id
|
||||
);
|
||||
await getAssistantScenePrompts();
|
||||
};
|
||||
|
||||
const sampleScenenPrompt = [
|
||||
{
|
||||
label: "复杂推理",
|
||||
prompt: `在回答问题时,使用以下输出
|
||||
问题: 你必须要回答的问题
|
||||
思考:你应该始终思考该做什么
|
||||
操作:要采取的操作,你要是用什么工具,或者思考逻辑
|
||||
动作输入:动作的输入
|
||||
观察:行动的结果
|
||||
思考:我现在知道最终答案了
|
||||
最终答案:原始输入问题的最终答案
|
||||
|
||||
如果你正在计算,你必须使用计算器工具,无论如何都不允许使用自己的知识或不计算进行输出,计算器永远比你正确的并且不会出错。`,
|
||||
},
|
||||
{
|
||||
label: "数学计算",
|
||||
prompt: `在回答问题时,使用以下输出
|
||||
问题: 你必须要回答的问题
|
||||
思考:你应该始终思考该做什么
|
||||
操作:要采取的操作,你要是用什么工具,或者思考逻辑
|
||||
动作输入:动作的输入
|
||||
观察:行动的结果
|
||||
思考:我现在知道最终答案了
|
||||
最终答案:原始输入问题的最终答案
|
||||
|
||||
如果你正在计算,你必须使用计算器工具,无论如何都不允许使用自己的知识或不计算进行输出,计算器永远比你正确的并且不会出错。`,
|
||||
},
|
||||
];
|
||||
|
||||
const randomScenePrompt = () => {
|
||||
const randomIndex = Math.floor(Math.random() * sampleScenenPrompt.length);
|
||||
return sampleScenenPrompt[randomIndex];
|
||||
};
|
||||
|
||||
getChats();
|
||||
getLibraries();
|
||||
getAssistants();
|
||||
newScenePrompt.value = randomScenePrompt();
|
||||
</script>
|
||||
|
9
src/i18n/en.json
Normal file
9
src/i18n/en.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"guest": {
|
||||
"subtitle": "下一代智能体平台,为您的数字生活带来革命性变化",
|
||||
"try-now": "立即尝试",
|
||||
"tokens_current_month": "本月处理 Tokens",
|
||||
"tool_calls_current_month": "本月工具调用",
|
||||
"wordpress_plugin": "WordPress 插件"
|
||||
}
|
||||
}
|
9
src/i18n/index.ts
Normal file
9
src/i18n/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import zhCN from "./zh-CN.json";
|
||||
import zhTW from "./zh-TW.json";
|
||||
import en from "./en.json";
|
||||
|
||||
export default {
|
||||
"zh-CN": zhCN,
|
||||
"zh-TW": zhTW,
|
||||
"en": en,
|
||||
};
|
9
src/i18n/zh-CN.json
Normal file
9
src/i18n/zh-CN.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"guest": {
|
||||
"subtitle": "下一代智能体平台,为您的数字生活带来革命性变化",
|
||||
"try-now": "立即尝试",
|
||||
"tokens_current_month": "本月处理 Tokens",
|
||||
"tool_calls_current_month": "本月工具调用",
|
||||
"wordpress_plugin": "WordPress 插件"
|
||||
}
|
||||
}
|
9
src/i18n/zh-TW.json
Normal file
9
src/i18n/zh-TW.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"guest": {
|
||||
"subtitle": "下一代智能体平台,为您的数字生活带来革命性变化",
|
||||
"try-now": "立即尝试",
|
||||
"tokens_current_month": "本月处理 Tokens",
|
||||
"tool_calls_current_month": "本月工具调用",
|
||||
"wordpress_plugin": "WordPress 插件"
|
||||
}
|
||||
}
|
@ -83,7 +83,7 @@ const onScroll = (e: Event) => {
|
||||
|
||||
<n-layout :native-scrollbar="isMobile">
|
||||
<div class="!pt-2">
|
||||
<div v-if="userStore.logined">
|
||||
<div v-if="userStore.logined && !userStore.isExpired()">
|
||||
<ChatLayout>
|
||||
<router-view :key="route.path"> </router-view>
|
||||
</ChatLayout>
|
||||
@ -101,17 +101,4 @@ const onScroll = (e: Event) => {
|
||||
</div> -->
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
|
||||
<n-watermark
|
||||
content="测试版本,不代表最终品质"
|
||||
cross
|
||||
fullscreen
|
||||
:font-size="16"
|
||||
:line-height="16"
|
||||
:width="384"
|
||||
:height="384"
|
||||
:x-offset="12"
|
||||
:y-offset="60"
|
||||
:rotate="-15"
|
||||
/>
|
||||
</template>
|
||||
|
@ -69,7 +69,7 @@
|
||||
|
||||
<div v-else-if="chatStore.toolName != ''">
|
||||
<n-gradient-text type="info">
|
||||
正在执行 {{ chatStore.toolName }}
|
||||
{{ chatStore.toolName }}
|
||||
</n-gradient-text>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
11
src/main.ts
11
src/main.ts
@ -6,22 +6,19 @@ import "./styles/style.css";
|
||||
import "./styles/color.less";
|
||||
import "animate.css";
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
import naive from 'naive-ui'
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
import router from "./router";
|
||||
import { registerPlugins } from "./plugins";
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(naive)
|
||||
const app = createApp(App);
|
||||
|
||||
registerPlugins(app);
|
||||
|
||||
app.use(router);
|
||||
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
setTimeout(() => {
|
||||
const appContainer = document.getElementById("app");
|
||||
|
@ -4,49 +4,77 @@
|
||||
<div class="container">
|
||||
<!-- Spotlight 区域 -->
|
||||
<n-card class="spotlight" :bordered="false">
|
||||
<div class="background-pattern"></div>
|
||||
<div class="background-text">Leaflow <span class="background-amber">Amber</span></div>
|
||||
<div class="spotlight-content">
|
||||
<h1 class="title">
|
||||
<h1 class="title break-normal">
|
||||
Leaflow <span class="amber-text">Amber</span>
|
||||
</h1>
|
||||
<p class="subtitle">
|
||||
下一代智能体平台,为您的数字生活带来革命性变化
|
||||
<br class="mobile-break">
|
||||
<a href="#" class="cta-link">立即体验 🚀</a>
|
||||
<span>{{ $t('guest.subtitle') }}</span>
|
||||
<br class="mobile-break" />
|
||||
<a @click="gotoLogin" class="cta-link cursor-pointer"
|
||||
>{{ $t('guest.try-now') }}</a
|
||||
>
|
||||
</p>
|
||||
<div class="stats" v-if="siteUsage.month_tokens && siteUsage.month_tool_calls">
|
||||
<div
|
||||
class="stats"
|
||||
v-if="siteUsage.month_tokens && siteUsage.month_tool_calls"
|
||||
>
|
||||
<div class="stat-item">
|
||||
<n-statistic tabular-nums>
|
||||
<n-number-animation
|
||||
ref="tokenAnimationRef"
|
||||
:from="siteUsage.month_tokens < 1000 ? 0 : siteUsage.month_tokens - 800"
|
||||
:from="
|
||||
siteUsage.month_tokens < 1000
|
||||
? 0
|
||||
: siteUsage.month_tokens - 800
|
||||
"
|
||||
:to="siteUsage.month_tokens"
|
||||
/>
|
||||
<template #suffix> Tokens </template>
|
||||
</n-statistic>
|
||||
<div class="stat-label">本月处理 Tokens</div>
|
||||
<div class="stat-label">{{ $t('guest.tokens_current_month') }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<n-statistic tabular-nums>
|
||||
<n-number-animation
|
||||
ref="callsAnimationRef"
|
||||
:from="siteUsage.month_tool_calls < 1000 ? 0 : siteUsage.month_tool_calls - 800"
|
||||
:from="
|
||||
siteUsage.month_tool_calls < 1000
|
||||
? 0
|
||||
: siteUsage.month_tool_calls - 800
|
||||
"
|
||||
:to="siteUsage.month_tool_calls"
|
||||
/>
|
||||
<template #suffix> Calls </template>
|
||||
</n-statistic>
|
||||
<div class="stat-label">本月工具调用</div>
|
||||
<div class="stat-label">{{ $t('guest.tool_calls_current_month') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<n-button class="transparent-button spotlight-button" ghost tag="a" href="https://github.com/ivampiresp/wp-amber" target="_blank">
|
||||
WordPress 插件
|
||||
</n-button>
|
||||
<n-button class="transparent-button spotlight-button" ghost tag="a" href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=e6RgLkc7pXr57968mYSg5X7H3puFi79b&authKey=xbG43mkYovgs%2BNW3BOG7oo%2BJZK0dbXjzyIfwW9a8himqRC6AlXOkCmb3G%2FcwOt6Y&noverify=0&group_code=439747955" target="_blank">
|
||||
<n-button
|
||||
class="transparent-button spotlight-button"
|
||||
ghost
|
||||
tag="a"
|
||||
href="https://github.com/ivampiresp/wp-amber"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('guest.wordpress_plugin') }} </n-button>
|
||||
<n-button
|
||||
class="transparent-button spotlight-button"
|
||||
ghost
|
||||
tag="a"
|
||||
href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=e6RgLkc7pXr57968mYSg5X7H3puFi79b&authKey=xbG43mkYovgs%2BNW3BOG7oo%2BJZK0dbXjzyIfwW9a8himqRC6AlXOkCmb3G%2FcwOt6Y&noverify=0&group_code=439747955"
|
||||
target="_blank"
|
||||
>
|
||||
加入官方 QQ 群
|
||||
</n-button>
|
||||
<n-button class="transparent-button spotlight-button" ghost tag="a" href="https://amber-api.leaflow.cn/swagger/index.html" target="_blank">
|
||||
<n-button
|
||||
class="transparent-button spotlight-button"
|
||||
ghost
|
||||
tag="a"
|
||||
href="https://amber-api.leaflow.cn/swagger/index.html"
|
||||
target="_blank"
|
||||
>
|
||||
Amber API 文档
|
||||
</n-button>
|
||||
</div>
|
||||
@ -56,15 +84,27 @@
|
||||
<!-- 功能特点展示 -->
|
||||
<div class="features-section">
|
||||
<h2 class="section-title">Amber 核心特性</h2>
|
||||
<n-grid :cols="3" :x-gap="24" :y-gap="24" responsive="screen">
|
||||
<n-gi span="3 m:1" v-for="(feature, index) in features" :key="index">
|
||||
<div class="feature-card" @click="toggleFeatureDescription(feature)">
|
||||
<div class="feature-content">
|
||||
<n-grid
|
||||
cols="1 m:2 l:3 xl:3 2xl:3"
|
||||
:x-gap="24"
|
||||
:y-gap="24"
|
||||
responsive="screen"
|
||||
>
|
||||
<n-gi v-for="(feature, index) in features" :key="index">
|
||||
<div
|
||||
class="feature-card"
|
||||
@click="toggleFeatureDescription(feature)"
|
||||
>
|
||||
<div>
|
||||
<div class="feature-icon">{{ feature.icon }}</div>
|
||||
<div class="feature-title">{{ feature.title }}</div>
|
||||
<transition name="fade-height" mode="out-in">
|
||||
<p class="feature-description" :key="feature.showLong">
|
||||
{{ feature.showLong ? feature.longDescription : feature.shortDescription }}
|
||||
{{
|
||||
feature.showLong
|
||||
? feature.longDescription
|
||||
: feature.shortDescription
|
||||
}}
|
||||
</p>
|
||||
</transition>
|
||||
</div>
|
||||
@ -77,13 +117,23 @@
|
||||
<div class="partners-section">
|
||||
<h2 class="section-title">合作伙伴</h2>
|
||||
<div class="partner-grid">
|
||||
<div v-for="partner in partners" :key="partner.name" class="partner-card">
|
||||
<div
|
||||
v-for="partner in partners"
|
||||
:key="partner.name"
|
||||
class="partner-card"
|
||||
>
|
||||
<div class="partner-logo-container">
|
||||
<img :src="partner.logo" :alt="partner.name" class="partner-logo">
|
||||
<img
|
||||
:src="partner.logo"
|
||||
:alt="partner.name"
|
||||
class="partner-logo"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="partner-name">{{ partner.name }}</h3>
|
||||
<p class="partner-description">{{ partner.description }}</p>
|
||||
<a :href="partner.link" target="_blank" class="partner-link">了解更多</a>
|
||||
<a :href="partner.link" target="_blank" class="partner-link"
|
||||
>了解更多</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -93,73 +143,92 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useOsTheme, darkTheme, NConfigProvider, NLayout, NButton, NCard, NStatistic, NNumberAnimation } from 'naive-ui';
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import {
|
||||
useOsTheme,
|
||||
darkTheme,
|
||||
NConfigProvider,
|
||||
NLayout,
|
||||
NButton,
|
||||
NCard,
|
||||
NStatistic,
|
||||
NNumberAnimation,
|
||||
} from "naive-ui";
|
||||
import { SchemaSiteUsageResponse } from "@/api";
|
||||
import getApi from "@/plugins/api";
|
||||
import grouppng from "@/assets/images/group.png";
|
||||
import router from "@/router";
|
||||
|
||||
const osTheme = useOsTheme();
|
||||
const theme = computed(() => (osTheme.value === 'dark' ? darkTheme : null));
|
||||
const theme = computed(() => (osTheme.value === "dark" ? darkTheme : null));
|
||||
|
||||
const siteUsage = ref<SchemaSiteUsageResponse>({});
|
||||
|
||||
const gotoLogin = () => {
|
||||
router.push("/auth/login");
|
||||
};
|
||||
|
||||
const features = ref([
|
||||
{
|
||||
icon: '💬',
|
||||
title: '对话优化引擎',
|
||||
shortDescription: '独特的多助理并行工作系统,提供流畅一致的对话体验。',
|
||||
longDescription: 'Amber 的对话优化引擎无与伦比。我们独特的对话功能支持多个助理并行工作,您可以通过输入 @ 或点击右上角的"切换助理"选项,轻松指定下一个回复的助理。所有助理共享一个上下文,彼此了解每个操作,为您提供流畅一致的对话体验。与一些只能处理单一对话的竞品相比,Amber 的多助理协作系统显得更为智能和高效。在重大决策型的使用场景中,你可以通过 Amber 对话引擎去得到 N 个 Second Opinion。 Amber 为聊天场景做了很多的优化,它将更能处理您日常生活中的问题。',
|
||||
showLong: false
|
||||
icon: "💬",
|
||||
title: "对话优化引擎",
|
||||
shortDescription: "独特的多助理并行工作系统,提供流畅一致的对话体验。",
|
||||
longDescription:
|
||||
'Amber 的对话优化引擎无与伦比。我们独特的对话功能支持多个助理并行工作,您可以通过输入 @ 或点击右上角的"切换助理"选项,轻松指定下一个回复的助理。所有助理共享一个上下文,彼此了解每个操作,为您提供流畅一致的对话体验。与一些只能处理单一对话的竞品相比,Amber 的多助理协作系统显得更为智能和高效。在重大决策型的使用场景中,你可以通过 Amber 对话引擎去得到 N 个 Second Opinion。 Amber 为聊天场景做了很多的优化,它将更能处理您日常生活中的问题。',
|
||||
showLong: false,
|
||||
},
|
||||
{
|
||||
icon: '📖',
|
||||
title: '智能上下文',
|
||||
shortDescription: '自动调整对话上下文,提升对话流畅性并节省资源。',
|
||||
longDescription: 'Amber 拥有独特的智能上下文技术,能够根据对话场景自动调整上下文。这一过程对用户是透明的,您无需手动干预,系统会智能识别何时需要移除或添加上下文信息。这种灵活性不仅提升了对话的流畅性,还能显著降低 Token 的消耗,节省从几千到几万不等的费用,帮助用户更高效地利用资源。需要注意的是,OpenAI 兼容模式不支持智能上下文功能,因此在使用该模式时,您将无法享受到这一创新技术带来的便利。我们建议您在适用的情况下,充分利用 Amber 的智能上下文,以优化您的对话体验和资源使用。',
|
||||
showLong: false
|
||||
icon: "📖",
|
||||
title: "智能上下文",
|
||||
shortDescription: "自动调整对话上下文,提升对话流畅性并节省资源。",
|
||||
longDescription:
|
||||
"Amber 拥有独特的智能上下文技术,能够根据对话场景自动调整上下文。这一过程对用户是透明的,您无需手动干预,系统会智能识别何时需要移除或添加上下文信息。这种灵活性不仅提升了对话的流畅性,还能显著降低 Token 的消耗,节省从几千到几万不等的费用,帮助用户更高效地利用资源。需要注意的是,OpenAI 兼容模式不支持智能上下文功能,因此在使用该模式时,您将无法享受到这一创新技术带来的便利。我们建议您在适用的情况下,充分利用 Amber 的智能上下文,以优化您的对话体验和资源使用。",
|
||||
showLong: false,
|
||||
},
|
||||
{
|
||||
icon: '🛠️',
|
||||
title: '高度客制化',
|
||||
shortDescription: '自定义提示词和工具绑定,轻松完成复杂任务。',
|
||||
longDescription: 'Amber 具备了目前主流 LLM 对话平台都拥有的高自由度智能体自定义功能,用户可以自定义提示词,绑定工具,以完成复杂任务并优化工作流。例如,您可以设置助理在每天早上自动为您生成当天的工作清单,或者在您需要时快速调用数据分析工具。我们的助理不仅能执行简单的任务,还能实现多步骤的复杂操作,并与知识库无缝集成,说它是您高效工作的得力助手,绝不为过。 💪',
|
||||
showLong: false
|
||||
icon: "🛠️",
|
||||
title: "高度客制化",
|
||||
shortDescription: "自定义提示词和工具绑定,轻松完成复杂任务。",
|
||||
longDescription:
|
||||
"Amber 具备了目前主流 LLM 对话平台都拥有的高自由度智能体自定义功能,用户可以自定义提示词,绑定工具,以完成复杂任务并优化工作流。例如,您可以设置助理在每天早上自动为您生成当天的工作清单,或者在您需要时快速调用数据分析工具。我们的助理不仅能执行简单的任务,还能实现多步骤的复杂操作,并与知识库无缝集成,说它是您高效工作的得力助手,绝不为过。 💪",
|
||||
showLong: false,
|
||||
},
|
||||
{
|
||||
icon: '🧠',
|
||||
title: '智能记忆',
|
||||
shortDescription: '生成对话记忆,实现更个性化和连贯的交互体验。',
|
||||
longDescription: 'Amber 通过采样您的对话内容生成记忆,这些记忆是碎片化的,但对话中共享。您可以随时清除所有记忆,确保数据的灵活管理。Amber 会收集您的消息,并且根据消息推断您的喜好。记忆的内容您完全可以控制,甚至禁用助理的记忆。助理将会根据您的记忆来改变响应的结果,您喜欢什么,您直接在对话中说明即可(Amber 会从第二条消息开始学习记忆) ❤️',
|
||||
showLong: false
|
||||
icon: "🧠",
|
||||
title: "智能记忆",
|
||||
shortDescription: "生成对话记忆,实现更个性化和连贯的交互体验。",
|
||||
longDescription:
|
||||
"Amber 通过采样您的对话内容生成记忆,这些记忆是碎片化的,但对话中共享。您可以随时清除所有记忆,确保数据的灵活管理。Amber 会收集您的消息,并且根据消息推断您的喜好。记忆的内容您完全可以控制,甚至禁用助理的记忆。助理将会根据您的记忆来改变响应的结果,您喜欢什么,您直接在对话中说明即可(Amber 会从第二条消息开始学习记忆) ❤️",
|
||||
showLong: false,
|
||||
},
|
||||
{
|
||||
icon: '🔗',
|
||||
title: 'API 集成',
|
||||
shortDescription: '强大的 API 支持,轻松将 Amber 集成到您的应用中。',
|
||||
longDescription: '我们提供强大的助理 API,支持兼容 OpenAI 格式的应用接入 Amber。通过 Chat Completion API,您可以直接调用 Amber 的工具、记忆以及知识库功能。例如,一个开发者可以轻松地将 Amber 集成到他们的客服系统中,自动处理常见问题并提供高效的解决方案。相较于其他平台,我们的 API 更具灵活性和扩展性,让您的应用赋予更多可能。不仅仅在 Amber 系统的本身,您也可以通过我们 Leaflow 平台的 UserLand,解锁更多高阶 API 玩法! 🚀',
|
||||
showLong: false
|
||||
icon: "🔗",
|
||||
title: "API 集成",
|
||||
shortDescription: "强大的 API 支持,轻松将 Amber 集成到您的应用中。",
|
||||
longDescription:
|
||||
"我们提供强大的助理 API,支持兼容 OpenAI 格式的应用接入 Amber。通过 Chat Completion API,您可以直接调用 Amber 的工具、记忆以及知识库功能。例如,一个开发者可以轻松地将 Amber 集成到他们的客服系统中,自动处理常见问题并提供高效的解决方案。相较于其他平台,我们的 API 更具灵活性和扩展性,让您的应用赋予更多可能。不仅仅在 Amber 系统的本身,您也可以通过我们 Leaflow 平台的 UserLand,解锁更多高阶 API 玩法! 🚀",
|
||||
showLong: false,
|
||||
},
|
||||
{
|
||||
icon: '📚',
|
||||
title: '知识管理',
|
||||
shortDescription: '高效的文档管理和知识库功能,让信息检索更便捷。',
|
||||
longDescription: '文档管理是 Amber 知识库的核心功能之一。对话中上传的文件将自动保存至知识库,您只需设置助理关联知识库,即可实现快速检索和使用。假设您是一位项目经理,您可以在对话中上传项目计划书,并在需要时快速检索相关内容。Amber 在您发送消息时,将会自动搜索资料库。此外,我们正在开发桌面端软件,该软件可以根据您的需要同步文档至 Amber,以便随时调用🗂️。',
|
||||
showLong: false
|
||||
icon: "📚",
|
||||
title: "知识管理",
|
||||
shortDescription: "高效的文档管理和知识库功能,让信息检索更便捷。",
|
||||
longDescription:
|
||||
"文档管理是 Amber 知识库的核心功能之一。对话中上传的文件将自动保存至知识库,您只需设置助理关联知识库,即可实现快速检索和使用。假设您是一位项目经理,您可以在对话中上传项目计划书,并在需要时快速检索相关内容。Amber 在您发送消息时,将会自动搜索资料库。此外,我们正在开发桌面端软件,该软件可以根据您的需要同步文档至 Amber,以便随时调用🗂️。",
|
||||
showLong: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const partners = ref([
|
||||
{
|
||||
name: 'HiMCBBS 我的世界中文论坛',
|
||||
logo: 'https://www.himcbbs.com/data/assets/favicon/himcbbs-favicon-black-6x.png',
|
||||
link: 'https://www.himcbbs.com/',
|
||||
description: '中国最大的我的世界玩家社区,提供游戏资讯、模组下载和创意分享。'
|
||||
}
|
||||
name: "HiMCBBS 我的世界中文论坛",
|
||||
logo: "https://www.himcbbs.com/data/assets/favicon/himcbbs-favicon-black-6x.png",
|
||||
link: "https://www.himcbbs.com/",
|
||||
description: "中国我的世界玩家社区,提供游戏资讯、模组下载和创意分享。",
|
||||
},
|
||||
// 可以在这里添加更多合作伙伴
|
||||
]);
|
||||
|
||||
const toggleFeatureDescription = (feature) => {
|
||||
const toggleFeatureDescription = (feature: any) => {
|
||||
feature.showLong = !feature.showLong;
|
||||
};
|
||||
|
||||
@ -176,11 +245,13 @@ onMounted(async () => {
|
||||
--primary-color: #4ebbc0;
|
||||
--text-color: v-bind('theme ? "#ffffff" : "#333333"');
|
||||
--background-color: v-bind('theme ? "#1a1a1a" : "#f5f5f5"');
|
||||
--card-background: v-bind('theme ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.8)"');
|
||||
--card-background: v-bind(
|
||||
'theme ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.8)"'
|
||||
);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
font-family: "Arial", sans-serif;
|
||||
color: var(--text-color);
|
||||
background-color: var(--background-color);
|
||||
transition: background-color 0.3s ease;
|
||||
@ -194,9 +265,10 @@ body {
|
||||
}
|
||||
|
||||
.spotlight {
|
||||
background-color: v-bind('theme ? "rgba(0, 0, 0, 0.5)" : "rgba(78, 187, 192, 0.1)"');
|
||||
background-color: v-bind(
|
||||
'theme ? "rgba(0, 0, 0, 0.5)" : "rgba(78, 187, 192, 0.1)"'
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -205,7 +277,9 @@ body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: v-bind('theme ? "0 4px 6px rgba(0, 0, 0, 0.1)" : "0 4px 6px rgba(78, 187, 192, 0.2)"');
|
||||
box-shadow: v-bind(
|
||||
'theme ? "0 4px 6px rgba(0, 0, 0, 0.1)" : "0 4px 6px rgba(78, 187, 192, 0.2)"'
|
||||
);
|
||||
}
|
||||
|
||||
.background-text {
|
||||
@ -216,13 +290,15 @@ body {
|
||||
font-size: 20vw;
|
||||
font-weight: bold;
|
||||
color: transparent;
|
||||
-webkit-text-stroke: 1px v-bind('theme ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)"');
|
||||
-webkit-text-stroke: 1px
|
||||
v-bind('theme ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)"');
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-amber {
|
||||
-webkit-text-stroke: 1px v-bind('theme ? "rgba(78, 187, 192, 0.2)" : "rgba(78, 187, 192, 0.3)"');
|
||||
-webkit-text-stroke: 1px
|
||||
v-bind('theme ? "rgba(78, 187, 192, 0.2)" : "rgba(78, 187, 192, 0.3)"');
|
||||
}
|
||||
|
||||
.spotlight-content {
|
||||
@ -288,8 +364,11 @@ body {
|
||||
}
|
||||
|
||||
.transparent-button {
|
||||
background-color: v-bind('theme ? "rgba(255, 255, 255, 0.2)" : "rgba(78, 187, 192, 0.1)"');
|
||||
border: 1px solid v-bind('theme ? "rgba(255, 255, 255, 0.5)" : "rgba(78, 187, 192, 0.3)"');
|
||||
background-color: v-bind(
|
||||
'theme ? "rgba(255, 255, 255, 0.2)" : "rgba(78, 187, 192, 0.1)"'
|
||||
);
|
||||
border: 1px solid
|
||||
v-bind('theme ? "rgba(255, 255, 255, 0.5)" : "rgba(78, 187, 192, 0.3)"');
|
||||
color: v-bind('theme ? "white" : "rgba(0, 0, 0, 0.7)"');
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
@ -307,7 +386,7 @@ body {
|
||||
}
|
||||
|
||||
.spotlight-button::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
@ -316,7 +395,9 @@ body {
|
||||
background-color: rgba(78, 187, 192, 0.3);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: width 0.6s ease, height 0.6s ease;
|
||||
transition:
|
||||
width 0.6s ease,
|
||||
height 0.6s ease;
|
||||
}
|
||||
|
||||
.spotlight-button:hover::before {
|
||||
@ -325,7 +406,9 @@ body {
|
||||
}
|
||||
|
||||
.spotlight-button:hover {
|
||||
background-color: v-bind('theme ? "rgba(255, 255, 255, 0.3)" : "transparent"');
|
||||
background-color: v-bind(
|
||||
'theme ? "rgba(255, 255, 255, 0.3)" : "transparent"'
|
||||
);
|
||||
border-color: #4ebbc0;
|
||||
color: v-bind('theme ? "white" : "#4ebbc0"');
|
||||
transform: translateY(-2px);
|
||||
|
@ -23,6 +23,11 @@ axios.get(config.oauth_discovery_url).then((discovery) => {
|
||||
|
||||
const code: any = router.currentRoute.value.query.code;
|
||||
|
||||
if (!code) {
|
||||
// console.log("无法获取当前 url 中的 code")
|
||||
return;
|
||||
}
|
||||
|
||||
// 从当前页面请求中获取 code
|
||||
const q = new URLSearchParams({
|
||||
client_id: config.oauth_client_id,
|
||||
@ -43,12 +48,13 @@ axios.get(config.oauth_discovery_url).then((discovery) => {
|
||||
r.data.id_token,
|
||||
r.data.access_token,
|
||||
r.data.refresh_token,
|
||||
r.data.expires_in,
|
||||
r.data.expires_in
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
alert("登录失败");
|
||||
console.error(e);
|
||||
|
||||
// alert("登录失败");
|
||||
})
|
||||
.finally(() => {
|
||||
// 跳转到 /
|
||||
|
@ -5,6 +5,7 @@ import error401 from "@/pages/errors/401.vue";
|
||||
import error404 from "@/pages/errors/404.vue";
|
||||
import error400 from "@/pages/errors/400.vue";
|
||||
import error500 from "@/pages/errors/500.vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const osThemeRef = useOsTheme();
|
||||
|
||||
@ -19,13 +20,19 @@ const { dialog, loadingBar } = createDiscreteApi(
|
||||
);
|
||||
|
||||
const request = {
|
||||
onFulfilled: (config: any) => {
|
||||
onFulfilled: async (config: any) => {
|
||||
if (config.headers === undefined) {
|
||||
config.headers = {};
|
||||
}
|
||||
|
||||
loadingBar.start();
|
||||
|
||||
// const userStore = useUserStore();
|
||||
|
||||
// if (userStore.logined) {
|
||||
// userStore.checkAndRefresh();
|
||||
// }
|
||||
|
||||
return Promise.resolve(config);
|
||||
},
|
||||
onRejected: (error: any) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pinia from "../stores";
|
||||
import naive from "naive-ui";
|
||||
|
||||
import { createI18n } from "vue-i18n";
|
||||
import i18nMessages from "@/i18n"
|
||||
// Types
|
||||
import type { App } from "vue";
|
||||
// 通用字体
|
||||
@ -8,6 +9,28 @@ import "vfonts/Lato.css";
|
||||
// 等宽字体
|
||||
import "vfonts/FiraCode.css";
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
app.use(naive).use(pinia);
|
||||
// 自动根据用户的浏览器设置选择语言
|
||||
const getLocate = () => {
|
||||
const languages = navigator.languages || [navigator.language];
|
||||
|
||||
for (const lang of languages) {
|
||||
if (lang.startsWith('zh-CN')) {
|
||||
return 'zh-CN'; // 简体中文
|
||||
} else if (lang.startsWith('zh-TW')) {
|
||||
return 'zh-TW'; // 繁体中文
|
||||
}
|
||||
}
|
||||
return languages[0];
|
||||
};
|
||||
|
||||
const userLocale = getLocate();
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: userLocale || 'zh-CN', // 如果没有匹配,默认返回简体中文
|
||||
fallbackLocale: "zh-CN",
|
||||
messages: i18nMessages,
|
||||
});
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
app.use(i18n).use(naive).use(pinia);
|
||||
}
|
||||
|
@ -42,10 +42,15 @@ export const useUserStore = defineStore("user", {
|
||||
this.logined = true;
|
||||
},
|
||||
checkAndRefresh() {
|
||||
if (this.logined) {
|
||||
if (this.expired_at - Date.now() < 60000) {
|
||||
// 检测是否过期
|
||||
if (this.expired_at - Date.now() < 0) {
|
||||
this.refresh();
|
||||
} else if (
|
||||
this.logined &&
|
||||
this.expired_at - Date.now() > 600 &&
|
||||
this.expired_at - Date.now() < 1000
|
||||
) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
setupTimer() {
|
||||
@ -80,6 +85,7 @@ export const useUserStore = defineStore("user", {
|
||||
if (error.response.status === 401) {
|
||||
console.log("Refresh token failed");
|
||||
}
|
||||
|
||||
// logout
|
||||
this.logout();
|
||||
clearInterval(timer);
|
||||
@ -94,5 +100,8 @@ export const useUserStore = defineStore("user", {
|
||||
getIdToken() {
|
||||
return this.id_token;
|
||||
},
|
||||
isExpired() {
|
||||
return this.expired_at - Date.now() < 0;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -10,6 +10,13 @@ import AutoImport from "unplugin-auto-import/vite";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: "modern-compiler", // or "modern", "legacy"
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
|
Loading…
Reference in New Issue
Block a user