fix lint type error

This commit is contained in:
duanfuxiang 2025-04-24 20:41:32 +08:00
parent 0178a9b024
commit a722e2ca40
9 changed files with 166 additions and 138 deletions

View File

@ -1,4 +1,9 @@
releases:
- version: "0.1.6"
features:
- "update model select in chat view, add collected models "
- "fix vault obsidian sync error"
- "update apply view, you can edit content in apply view"
- version: "0.1.5"
features:
- "hotfix command view style"

View File

@ -1,7 +1,7 @@
{
"id": "infio-copilot",
"name": "Infio Copilot",
"version": "0.1.5",
"version": "0.1.6",
"minAppVersion": "0.15.0",
"description": "A Cursor-inspired AI assistant for notes that offers smart autocomplete and interactive chat with your selected notes",
"author": "Felix.D",

View File

@ -17,8 +17,8 @@ export interface QuickCommand {
name: string
content: TemplateContent
contentText: string
createdAt: Date | undefined
updatedAt: Date | undefined
createdAt: number
updatedAt: number
}
const CommandsView = (

View File

@ -212,7 +212,6 @@ export function ModelSelect() {
return results
}, [searchableItems, searchTerm, fuse])
// 添加或删除收藏
const toggleCollected = (id: string, e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@ -224,12 +223,12 @@ export function ModelSelect() {
let newCollectedModels = settings.collectedChatModels || [];
if (isCurrentlyCollected) {
// 移除收藏
// remove
newCollectedModels = newCollectedModels.filter(
item => !(item.provider === modelProvider && item.modelId === id)
);
} else {
// 添加收藏
// add
newCollectedModels = [...newCollectedModels, { provider: modelProvider, modelId: id }];
}
@ -253,7 +252,7 @@ export function ModelSelect() {
<DropdownMenu.Portal>
<DropdownMenu.Content className="infio-popover infio-llm-setting-combobox-dropdown">
{/* 收藏的模型区域 - 所有providers的收藏模型 */}
{/* collected models */}
{settings.collectedChatModels?.length > 0 && (
<div className="infio-model-section">
<div className="infio-model-section-title">
@ -292,8 +291,7 @@ export function ModelSelect() {
<Star size={16} className="infio-star-active" onClick={(e) => {
e.stopPropagation();
e.preventDefault();
// 从收藏中删除
// delete
const newCollectedModels = settings.collectedChatModels.filter(
item => !(item.provider === collectedModel.provider && item.modelId === collectedModel.modelId)
);

View File

@ -1,115 +1,117 @@
import { App } from 'obsidian'
import { v4 as uuidv4 } from 'uuid'
import { ChatConversationMeta } from '../../../types/chat'
import { AbstractJsonRepository } from '../base'
import { CHAT_DIR, ROOT_DIR } from '../constants'
import { EmptyChatTitleException } from '../exception'
import {
CHAT_SCHEMA_VERSION,
ChatConversation,
ChatConversationMetadata,
CHAT_SCHEMA_VERSION,
ChatConversation
} from './types'
export class ChatManager extends AbstractJsonRepository<
ChatConversation,
ChatConversationMetadata
ChatConversation,
ChatConversationMeta
> {
constructor(app: App) {
super(app, `${ROOT_DIR}/${CHAT_DIR}`)
}
constructor(app: App) {
super(app, `${ROOT_DIR}/${CHAT_DIR}`)
}
protected generateFileName(chat: ChatConversation): string {
// Format: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const encodedTitle = encodeURIComponent(chat.title)
return `v${chat.schemaVersion}_${encodedTitle}_${chat.updatedAt}_${chat.id}.json`
}
protected generateFileName(chat: ChatConversation): string {
// Format: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const encodedTitle = encodeURIComponent(chat.title)
return `v${chat.schemaVersion}_${encodedTitle}_${chat.updatedAt}_${chat.id}.json`
}
protected parseFileName(fileName: string): ChatConversationMetadata | null {
// Parse: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const regex = new RegExp(
`^v${CHAT_SCHEMA_VERSION}_(.+)_(\\d+)_([0-9a-f-]+)\\.json$`,
)
const match = fileName.match(regex)
if (!match) return null
protected parseFileName(fileName: string): ChatConversationMeta | null {
// Parse: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const regex = new RegExp(
`^v${CHAT_SCHEMA_VERSION}_(.+)_(\\d+)_([0-9a-f-]+)\\.json$`,
)
const match = fileName.match(regex)
if (!match) return null
const title = decodeURIComponent(match[1])
const updatedAt = parseInt(match[2], 10)
const id = match[3]
const title = decodeURIComponent(match[1])
const updatedAt = parseInt(match[2], 10)
const id = match[3]
return {
id,
schemaVersion: CHAT_SCHEMA_VERSION,
title,
updatedAt,
}
}
return {
id,
schemaVersion: CHAT_SCHEMA_VERSION,
title,
updatedAt,
createdAt: 0,
}
}
public async createChat(
initialData: Partial<ChatConversation>,
): Promise<ChatConversation> {
if (initialData.title && initialData.title.length === 0) {
throw new EmptyChatTitleException()
}
public async createChat(
initialData: Partial<ChatConversation>,
): Promise<ChatConversation> {
if (initialData.title && initialData.title.length === 0) {
throw new EmptyChatTitleException()
}
const now = Date.now()
const newChat: ChatConversation = {
id: uuidv4(),
title: 'New chat',
messages: [],
createdAt: now,
updatedAt: now,
schemaVersion: CHAT_SCHEMA_VERSION,
...initialData,
}
const now = Date.now()
const newChat: ChatConversation = {
id: uuidv4(),
title: 'New chat',
messages: [],
createdAt: now,
updatedAt: now,
schemaVersion: CHAT_SCHEMA_VERSION,
...initialData,
}
await this.create(newChat)
return newChat
}
await this.create(newChat)
return newChat
}
public async findById(id: string): Promise<ChatConversation | null> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
public async findById(id: string): Promise<ChatConversation | null> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
if (!targetMetadata) return null
if (!targetMetadata) return null
return this.read(targetMetadata.fileName)
}
return this.read(targetMetadata.fileName)
}
public async updateChat(
id: string,
updates: Partial<
Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
>,
): Promise<ChatConversation | null> {
const chat = await this.findById(id)
if (!chat) return null
public async updateChat(
id: string,
updates: Partial<
Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
>,
): Promise<ChatConversation | null> {
const chat = await this.findById(id)
if (!chat) return null
if (updates.title !== undefined && updates.title.length === 0) {
throw new EmptyChatTitleException()
}
if (updates.title !== undefined && updates.title.length === 0) {
throw new EmptyChatTitleException()
}
const updatedChat: ChatConversation = {
...chat,
...updates,
updatedAt: Date.now(),
}
const updatedChat: ChatConversation = {
...chat,
...updates,
updatedAt: Date.now(),
}
await this.update(chat, updatedChat)
return updatedChat
}
await this.update(chat, updatedChat)
return updatedChat
}
public async deleteChat(id: string): Promise<boolean> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
if (!targetMetadata) return false
public async deleteChat(id: string): Promise<boolean> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
if (!targetMetadata) return false
await this.delete(targetMetadata.fileName)
return true
}
await this.delete(targetMetadata.fileName)
return true
}
public async listChats(): Promise<ChatConversationMetadata[]> {
const metadata = await this.listMetadata()
return metadata.sort((a, b) => b.updatedAt - a.updatedAt)
}
public async listChats(): Promise<ChatConversationMeta[]> {
const metadata = await this.listMetadata()
return metadata.sort((a, b) => b.updatedAt - a.updatedAt)
}
}

View File

@ -5,35 +5,34 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { editorStateToPlainText } from '../components/chat-view/chat-input/utils/editor-state-to-plain-text'
import { useApp } from '../contexts/AppContext'
import { ChatManager } from '../database/json/chat/ChatManager'
import { ChatConversationMetadata } from '../database/json/chat/types'
import { deserializeChatMessage, serializeChatMessage } from '../database/json/utils'
import { ChatMessage } from '../types/chat'
import { ChatConversationMeta, ChatMessage, ChatUserMessage } from '../types/chat'
type UseChatHistory = {
createOrUpdateConversation: (
id: string,
messages: ChatMessage[],
) => Promise<void>
deleteConversation: (id: string) => Promise<void>
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
updateConversationTitle: (id: string, title: string) => Promise<void>
chatList: ChatConversationMetadata[]
createOrUpdateConversation: (
id: string,
messages: ChatMessage[],
) => Promise<void>
deleteConversation: (id: string) => Promise<void>
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
updateConversationTitle: (id: string, title: string) => Promise<void>
chatList: ChatConversationMeta[]
}
export function useChatHistory(): UseChatHistory {
const app = useApp()
const chatManager = useMemo(() => new ChatManager(app), [app])
const app = useApp()
const chatManager = useMemo(() => new ChatManager(app), [app])
const [chatList, setChatList] = useState<ChatConversationMetadata[]>([])
const [chatList, setChatList] = useState<ChatConversationMeta[]>([])
const fetchChatList = useCallback(async () => {
const fetchChatList = useCallback(async () => {
const conversations = await chatManager.listChats()
setChatList(conversations)
}, [chatManager])
setChatList(conversations)
}, [chatManager])
useEffect(() => {
void fetchChatList()
}, [fetchChatList])
useEffect(() => {
void fetchChatList()
}, [fetchChatList])
const createOrUpdateConversation = useMemo(
() =>
@ -50,7 +49,7 @@ export function useChatHistory(): UseChatHistory {
messages: serializedMessages,
})
} else {
const firstUserMessage = messages.find((v) => v.role === 'user')
const firstUserMessage = messages.find((v) => v.role === 'user') as ChatUserMessage
await chatManager.createChat({
id,
@ -74,13 +73,13 @@ export function useChatHistory(): UseChatHistory {
[chatManager, fetchChatList],
)
const deleteConversation = useCallback(
async (id: string): Promise<void> => {
await chatManager.deleteChat(id)
await fetchChatList()
},
[chatManager, fetchChatList],
)
const deleteConversation = useCallback(
async (id: string): Promise<void> => {
await chatManager.deleteChat(id)
await fetchChatList()
},
[chatManager, fetchChatList],
)
const getChatMessagesById = useCallback(
async (id: string): Promise<ChatMessage[] | null> => {
@ -112,11 +111,11 @@ export function useChatHistory(): UseChatHistory {
[chatManager, fetchChatList],
)
return {
createOrUpdateConversation,
deleteConversation,
getChatMessagesById,
updateConversationTitle,
chatList,
}
return {
createOrUpdateConversation,
deleteConversation,
getChatMessagesById,
updateConversationTitle,
chatList,
}
}

View File

@ -16,7 +16,6 @@ import { findFilesMatchingPatterns } from '../utils/glob-utils';
import AdvancedSettings from './components/AdvancedSettings';
import BasicAutoCompleteSettings from './components/BasicAutoCompleteSettings';
import DangerZoneSettings from './components/DangerZoneSettings';
import ModelParametersSettings from './components/ModelParametersSettings';
import CustomProviderSettings from './components/ModelProviderSettings';
import PostprocessingSettings from './components/PostprocessingSettings';
import PreprocessingSettings from './components/PreprocessingSettings';

View File

@ -18,6 +18,7 @@ describe('parseSmartCopilotSettings', () => {
groqApiKey: '',
deepseekApiKey: '',
chatModelId: '',
collectedChatModels: [],
chatModelProvider: 'OpenRouter',
applyModelId: '',
applyModelProvider: 'OpenRouter',
@ -198,6 +199,7 @@ describe('settings migration', () => {
geminiApiKey: '',
groqApiKey: '',
deepseekApiKey: '',
collectedChatModels: [],
chatModelId: '',
chatModelProvider: 'OpenRouter',
applyModelId: '',

View File

@ -222,10 +222,29 @@ async function fetchOpenRouterModels(): Promise<Record<string, ModelInfo>> {
// Gemini
// https://ai.google.dev/gemini-api/docs/models/gemini
export type GeminiModelId = keyof typeof geminiModels
export const geminiDefaultModelId: GeminiModelId = "gemini-2.0-flash-001"
export const geminiDefaultModelId: GeminiModelId = "gemini-2.5-flash-preview-04-17"
export const geminiModels = {
"gemini-2.5-flash-preview-04-17:thinking": {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.15,
outputPrice: 3.5,
thinking: true,
// maxThinkingTokens: 24_576,
},
"gemini-2.5-flash-preview-04-17": {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.15,
outputPrice: 0.6,
thinking: false,
},
"gemini-2.5-pro-exp-03-25": {
maxTokens: 65_536,
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
@ -236,17 +255,21 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 2.5,
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
outputPrice: 15,
cacheReadsPrice: 0.625,
cacheWritesPrice: 4.5,
},
"gemini-2.0-flash-001": {
maxTokens: 8192,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
supportsPromptCache: true,
inputPrice: 0.1,
outputPrice: 0.4,
cacheReadsPrice: 0.025,
cacheWritesPrice: 1.0,
},
"gemini-2.0-flash-lite-preview-02-05": {
maxTokens: 8192,
@ -1404,7 +1427,7 @@ export const grokModels = {
inputPrice: 0,
outputPrice: 0,
},
"grok-2-vision": {
"grok-2-latest": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: true,
@ -1412,7 +1435,7 @@ export const grokModels = {
inputPrice: 0,
outputPrice: 0,
},
"grok-2-image": {
"grok-2": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: true,