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: 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" - version: "0.1.5"
features: features:
- "hotfix command view style" - "hotfix command view style"

View File

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

View File

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

View File

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

View File

@ -1,115 +1,117 @@
import { App } from 'obsidian' import { App } from 'obsidian'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { ChatConversationMeta } from '../../../types/chat'
import { AbstractJsonRepository } from '../base' import { AbstractJsonRepository } from '../base'
import { CHAT_DIR, ROOT_DIR } from '../constants' import { CHAT_DIR, ROOT_DIR } from '../constants'
import { EmptyChatTitleException } from '../exception' import { EmptyChatTitleException } from '../exception'
import { import {
CHAT_SCHEMA_VERSION, CHAT_SCHEMA_VERSION,
ChatConversation, ChatConversation
ChatConversationMetadata,
} from './types' } from './types'
export class ChatManager extends AbstractJsonRepository< export class ChatManager extends AbstractJsonRepository<
ChatConversation, ChatConversation,
ChatConversationMetadata ChatConversationMeta
> { > {
constructor(app: App) { constructor(app: App) {
super(app, `${ROOT_DIR}/${CHAT_DIR}`) super(app, `${ROOT_DIR}/${CHAT_DIR}`)
} }
protected generateFileName(chat: ChatConversation): string { protected generateFileName(chat: ChatConversation): string {
// Format: v{schemaVersion}_{title}_{updatedAt}_{id}.json // Format: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const encodedTitle = encodeURIComponent(chat.title) const encodedTitle = encodeURIComponent(chat.title)
return `v${chat.schemaVersion}_${encodedTitle}_${chat.updatedAt}_${chat.id}.json` return `v${chat.schemaVersion}_${encodedTitle}_${chat.updatedAt}_${chat.id}.json`
} }
protected parseFileName(fileName: string): ChatConversationMetadata | null { protected parseFileName(fileName: string): ChatConversationMeta | null {
// Parse: v{schemaVersion}_{title}_{updatedAt}_{id}.json // Parse: v{schemaVersion}_{title}_{updatedAt}_{id}.json
const regex = new RegExp( const regex = new RegExp(
`^v${CHAT_SCHEMA_VERSION}_(.+)_(\\d+)_([0-9a-f-]+)\\.json$`, `^v${CHAT_SCHEMA_VERSION}_(.+)_(\\d+)_([0-9a-f-]+)\\.json$`,
) )
const match = fileName.match(regex) const match = fileName.match(regex)
if (!match) return null if (!match) return null
const title = decodeURIComponent(match[1]) const title = decodeURIComponent(match[1])
const updatedAt = parseInt(match[2], 10) const updatedAt = parseInt(match[2], 10)
const id = match[3] const id = match[3]
return { return {
id, id,
schemaVersion: CHAT_SCHEMA_VERSION, schemaVersion: CHAT_SCHEMA_VERSION,
title, title,
updatedAt, updatedAt,
} createdAt: 0,
} }
}
public async createChat( public async createChat(
initialData: Partial<ChatConversation>, initialData: Partial<ChatConversation>,
): Promise<ChatConversation> { ): Promise<ChatConversation> {
if (initialData.title && initialData.title.length === 0) { if (initialData.title && initialData.title.length === 0) {
throw new EmptyChatTitleException() throw new EmptyChatTitleException()
} }
const now = Date.now() const now = Date.now()
const newChat: ChatConversation = { const newChat: ChatConversation = {
id: uuidv4(), id: uuidv4(),
title: 'New chat', title: 'New chat',
messages: [], messages: [],
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
schemaVersion: CHAT_SCHEMA_VERSION, schemaVersion: CHAT_SCHEMA_VERSION,
...initialData, ...initialData,
} }
await this.create(newChat) await this.create(newChat)
return newChat return newChat
} }
public async findById(id: string): Promise<ChatConversation | null> { public async findById(id: string): Promise<ChatConversation | null> {
const allMetadata = await this.listMetadata() const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id) 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( public async updateChat(
id: string, id: string,
updates: Partial< updates: Partial<
Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'> Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
>, >,
): Promise<ChatConversation | null> { ): Promise<ChatConversation | null> {
const chat = await this.findById(id) const chat = await this.findById(id)
if (!chat) return null if (!chat) return null
if (updates.title !== undefined && updates.title.length === 0) { if (updates.title !== undefined && updates.title.length === 0) {
throw new EmptyChatTitleException() throw new EmptyChatTitleException()
} }
const updatedChat: ChatConversation = { const updatedChat: ChatConversation = {
...chat, ...chat,
...updates, ...updates,
updatedAt: Date.now(), updatedAt: Date.now(),
} }
await this.update(chat, updatedChat) await this.update(chat, updatedChat)
return updatedChat return updatedChat
} }
public async deleteChat(id: string): Promise<boolean> { public async deleteChat(id: string): Promise<boolean> {
const allMetadata = await this.listMetadata() const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id) const targetMetadata = allMetadata.find((meta) => meta.id === id)
if (!targetMetadata) return false if (!targetMetadata) return false
await this.delete(targetMetadata.fileName) await this.delete(targetMetadata.fileName)
return true return true
} }
public async listChats(): Promise<ChatConversationMetadata[]> { public async listChats(): Promise<ChatConversationMeta[]> {
const metadata = await this.listMetadata() const metadata = await this.listMetadata()
return metadata.sort((a, b) => b.updatedAt - a.updatedAt) 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 { editorStateToPlainText } from '../components/chat-view/chat-input/utils/editor-state-to-plain-text'
import { useApp } from '../contexts/AppContext' import { useApp } from '../contexts/AppContext'
import { ChatManager } from '../database/json/chat/ChatManager' import { ChatManager } from '../database/json/chat/ChatManager'
import { ChatConversationMetadata } from '../database/json/chat/types'
import { deserializeChatMessage, serializeChatMessage } from '../database/json/utils' import { deserializeChatMessage, serializeChatMessage } from '../database/json/utils'
import { ChatMessage } from '../types/chat' import { ChatConversationMeta, ChatMessage, ChatUserMessage } from '../types/chat'
type UseChatHistory = { type UseChatHistory = {
createOrUpdateConversation: ( createOrUpdateConversation: (
id: string, id: string,
messages: ChatMessage[], messages: ChatMessage[],
) => Promise<void> ) => Promise<void>
deleteConversation: (id: string) => Promise<void> deleteConversation: (id: string) => Promise<void>
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null> getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
updateConversationTitle: (id: string, title: string) => Promise<void> updateConversationTitle: (id: string, title: string) => Promise<void>
chatList: ChatConversationMetadata[] chatList: ChatConversationMeta[]
} }
export function useChatHistory(): UseChatHistory { export function useChatHistory(): UseChatHistory {
const app = useApp() const app = useApp()
const chatManager = useMemo(() => new ChatManager(app), [app]) 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() const conversations = await chatManager.listChats()
setChatList(conversations) setChatList(conversations)
}, [chatManager]) }, [chatManager])
useEffect(() => { useEffect(() => {
void fetchChatList() void fetchChatList()
}, [fetchChatList]) }, [fetchChatList])
const createOrUpdateConversation = useMemo( const createOrUpdateConversation = useMemo(
() => () =>
@ -50,7 +49,7 @@ export function useChatHistory(): UseChatHistory {
messages: serializedMessages, messages: serializedMessages,
}) })
} else { } else {
const firstUserMessage = messages.find((v) => v.role === 'user') const firstUserMessage = messages.find((v) => v.role === 'user') as ChatUserMessage
await chatManager.createChat({ await chatManager.createChat({
id, id,
@ -74,13 +73,13 @@ export function useChatHistory(): UseChatHistory {
[chatManager, fetchChatList], [chatManager, fetchChatList],
) )
const deleteConversation = useCallback( const deleteConversation = useCallback(
async (id: string): Promise<void> => { async (id: string): Promise<void> => {
await chatManager.deleteChat(id) await chatManager.deleteChat(id)
await fetchChatList() await fetchChatList()
}, },
[chatManager, fetchChatList], [chatManager, fetchChatList],
) )
const getChatMessagesById = useCallback( const getChatMessagesById = useCallback(
async (id: string): Promise<ChatMessage[] | null> => { async (id: string): Promise<ChatMessage[] | null> => {
@ -112,11 +111,11 @@ export function useChatHistory(): UseChatHistory {
[chatManager, fetchChatList], [chatManager, fetchChatList],
) )
return { return {
createOrUpdateConversation, createOrUpdateConversation,
deleteConversation, deleteConversation,
getChatMessagesById, getChatMessagesById,
updateConversationTitle, updateConversationTitle,
chatList, chatList,
} }
} }

View File

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

View File

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

View File

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