- Implemented `report.nim` to create structured reports on metadata modifications. - Added functionality to merge reports and convert them to formatted strings. docs: Create prompt documentation for Markdown parser project - Added `prompt.md` detailing the requirements and functionalities for the Markdown parser. - Included specifications, usage examples, and testing guidelines. docs: Generate code review report for Markdown parser - Created `rapport_revue_code.md` outlining security vulnerabilities, code quality issues, and suggested improvements. - Provided a detailed analysis of the codebase with actionable recommendations. test: Add test data for Markdown parser - Included various Markdown files and a JPG image in `test_data` to simulate different scenarios. - Ensured that the parser can handle both valid and invalid metadata. chore: Add version management file - Created `version.nim` for automatic versioning of the Markdown parser. - Established constants for major, minor, patch, and build versions.
693 lines
24 KiB
Nim
693 lines
24 KiB
Nim
# metadata.nim
|
|
# Module responsable de l'extraction et de la manipulation des métadonnées YAML
|
|
|
|
import std/[strutils, strformat, options, times, os, httpclient, json, sequtils, tables, uri]
|
|
import config
|
|
|
|
# Fonctions utilitaires
|
|
proc isEmptyOrWhitespace*(s: string): bool =
|
|
## Vérifie si une chaîne est vide ou contient uniquement des espaces blancs
|
|
if s.len == 0:
|
|
return true
|
|
|
|
for c in s:
|
|
if not c.isSpaceAscii():
|
|
return false
|
|
|
|
return true
|
|
|
|
proc parseYamlValue(line: string): string =
|
|
## Extrait la valeur d'une ligne YAML au format "clé: valeur"
|
|
let parts = line.split(':', 1)
|
|
if parts.len > 1:
|
|
return parts[1].strip()
|
|
return ""
|
|
|
|
proc isValidTag*(tag: string): bool =
|
|
## Vérifie si un tag est valide (contient au moins un caractère alphanumérique)
|
|
## Retourne false pour les tags qui ne contiennent que des caractères spéciaux comme "--"
|
|
if tag.len == 0:
|
|
return false
|
|
|
|
# Vérifier si le tag contient au moins une lettre ou un chiffre
|
|
for c in tag:
|
|
if c.isAlphaNumeric():
|
|
return true
|
|
|
|
return false # Tag ne contient que des caractères spéciaux
|
|
|
|
proc cleanTagText*(tag: string): string =
|
|
## Nettoie un tag en remplaçant les espaces par des underscores et en éliminant les caractères indésirables
|
|
var resultString = tag.strip()
|
|
# Supprimer les guillemets s'ils encadrent le tag
|
|
if resultString.len >= 2 and resultString[0] == '"' and resultString[^1] == '"':
|
|
resultString = resultString[1..^2]
|
|
|
|
# Supprimer les apostrophes s'ils encadrent le tag
|
|
if resultString.len >= 2 and resultString[0] == '\'' and resultString[^1] == '\'':
|
|
resultString = resultString[1..^2]
|
|
|
|
# Convertir en minuscules
|
|
resultString = resultString.toLowerAscii()
|
|
|
|
# Remplacer les espaces par des underscores
|
|
resultString = resultString.replace(" ", "_")
|
|
|
|
# Remplacer certains caractères spéciaux par des underscores
|
|
for c in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=', '{', '}', '[', ']', '|', '\\', ':', ';', '"', '\'', '<', '>', ',', '.', '?', '/']:
|
|
resultString = resultString.replace($c, "_")
|
|
|
|
# Limiter les underscores consécutifs à un seul
|
|
while "__" in resultString:
|
|
resultString = resultString.replace("__", "_")
|
|
|
|
# Supprimer les underscores au début et à la fin
|
|
resultString = resultString.strip(chars={'_'})
|
|
|
|
return resultString
|
|
|
|
proc parseYamlSeq(lines: seq[string], startIndex: int): tuple[tags: seq[string], endIndex: int] =
|
|
## Parse une séquence YAML commençant par des tirets
|
|
var
|
|
tags: seq[string] = @[]
|
|
i = startIndex
|
|
|
|
while i < lines.len:
|
|
let line = lines[i].strip()
|
|
if line.startsWith("-"):
|
|
let tagText = line[1..^1].strip()
|
|
let cleanedTag = cleanTagText(tagText)
|
|
if isValidTag(cleanedTag): # Ne garder que les tags valides
|
|
tags.add(cleanedTag)
|
|
i += 1
|
|
else:
|
|
break
|
|
|
|
return (tags: tags, endIndex: i)
|
|
|
|
type
|
|
AIAnalysisResult* = object
|
|
title*: string
|
|
description*: string
|
|
tags*: seq[string]
|
|
|
|
Metadata* = object
|
|
title*: string
|
|
description*: string
|
|
tags*: seq[string]
|
|
creationDate*: string
|
|
creationTime*: string
|
|
modificationDate*: string
|
|
modificationTime*: string
|
|
auteur*: string
|
|
url*: string
|
|
lang*: string
|
|
category*: string
|
|
isNew*: bool # Indique si les métadonnées ont été générées ou étaient déjà présentes
|
|
|
|
proc newMetadata*(): Metadata =
|
|
## Crée un nouvel objet Metadata vide
|
|
result = Metadata(
|
|
title: "",
|
|
description: "",
|
|
tags: @[],
|
|
creationDate: "",
|
|
creationTime: "",
|
|
modificationDate: "",
|
|
modificationTime: "",
|
|
auteur: "Non spécifié",
|
|
url: "",
|
|
lang: "fr", # Langue par défaut
|
|
category: "",
|
|
isNew: true
|
|
)
|
|
|
|
proc formatCurrentDateTime*(): tuple[date: string, time: string] =
|
|
## Retourne la date et l'heure actuelles formatées selon le format requis
|
|
let now = now()
|
|
return (
|
|
date: now.format("yyyy-MM-dd"),
|
|
time: now.format("HH:mm:ss")
|
|
)
|
|
|
|
proc formatFileTime*(time: Time): tuple[date: string, time: string] =
|
|
## Formate un objet Time en tuple (date, heure) selon le format requis
|
|
return (
|
|
date: time.format("yyyy-MM-dd"),
|
|
time: time.format("HH:mm:ss")
|
|
)
|
|
|
|
proc getFileTimeInfo*(filePath: string): tuple[creation: tuple[date: string, time: string],
|
|
modification: tuple[date: string, time: string]] =
|
|
## Obtient les informations de date/heure de création et modification du fichier
|
|
try:
|
|
let
|
|
fileInfo = getFileInfo(filePath)
|
|
creationTime = fileInfo.creationTime
|
|
modificationTime = fileInfo.lastWriteTime
|
|
|
|
return (
|
|
creation: formatFileTime(creationTime),
|
|
modification: formatFileTime(modificationTime)
|
|
)
|
|
except:
|
|
let current = formatCurrentDateTime()
|
|
return (
|
|
creation: current,
|
|
modification: current
|
|
)
|
|
|
|
proc extractTitle*(content: string, filePath: string = ""): string =
|
|
## Tente d'extraire un titre à partir du contenu Markdown
|
|
## Regarde la première ligne commençant par # ou utilise le nom du fichier
|
|
|
|
let lines = content.splitLines()
|
|
for line in lines:
|
|
let trimmed = line.strip()
|
|
if trimmed.startsWith("# "):
|
|
return trimmed[2..^1]
|
|
|
|
# Si pas de titre trouvé, utiliser le nom du fichier sans extension
|
|
if filePath != "":
|
|
let fileName = extractFilename(filePath)
|
|
if fileName.contains('.'):
|
|
return fileName.split('.')[0]
|
|
else:
|
|
return fileName
|
|
|
|
return "Document Markdown"
|
|
|
|
proc extractYamlValue(yaml: string, key: string): string =
|
|
## Extrait la valeur d'une clé spécifique d'un bloc YAML
|
|
let lines = yaml.splitLines()
|
|
|
|
for line in lines:
|
|
let trimmedLine = line.strip()
|
|
# Cherche une ligne qui commence par la clé suivie de deux points
|
|
if trimmedLine.startsWith(key & ":"):
|
|
# Extrait la partie après les deux points
|
|
let value = trimmedLine.substr(key.len + 1).strip()
|
|
return value
|
|
|
|
return ""
|
|
|
|
proc extractYamlList(yaml: string, key: string): seq[string] =
|
|
## Extrait une liste de valeurs pour une clé spécifique d'un bloc YAML
|
|
var resultList: seq[string] = @[]
|
|
let lines = yaml.splitLines()
|
|
var i = 0
|
|
|
|
# Chercher la ligne avec la clé
|
|
while i < lines.len:
|
|
if lines[i].strip().startsWith(key & ":"):
|
|
# Si la clé est suivie directement de valeurs sur la même ligne
|
|
let restOfLine = lines[i].substr(key.len + 1).strip()
|
|
if restOfLine.len > 0 and not restOfLine.startsWith("-"):
|
|
# Format compact: key: [val1, val2, val3]
|
|
if restOfLine.startsWith("[") and restOfLine.endsWith("]"):
|
|
let items = restOfLine[1..^2].split(",")
|
|
for item in items:
|
|
let cleaned = item.strip()
|
|
if cleaned.len > 0:
|
|
resultList.add(cleaned)
|
|
break
|
|
|
|
# Sinon, chercher les éléments de liste sur les lignes suivantes
|
|
i += 1
|
|
while i < lines.len:
|
|
let line = lines[i].strip()
|
|
if line.startsWith("-"):
|
|
let value = line[1..^1].strip()
|
|
if value.len > 0:
|
|
resultList.add(value)
|
|
else:
|
|
if not line.isEmptyOrWhitespace():
|
|
break
|
|
i += 1
|
|
break
|
|
i += 1
|
|
|
|
return resultList
|
|
|
|
proc extractYamlBlock*(content: string): Option[string] =
|
|
## Extrait un bloc YAML d'une chaîne de caractères, délimité par des marqueurs "---"
|
|
let lines = content.splitLines()
|
|
|
|
# Vérifier si le contenu commence par un délimiteur YAML
|
|
if lines.len < 3 or lines[0] != "---":
|
|
return none(string) # Pas de section YAML
|
|
|
|
var endYamlIndex = -1
|
|
|
|
# Chercher la fin de la section YAML
|
|
for j in 1..<lines.len:
|
|
if lines[j] == "---":
|
|
endYamlIndex = j
|
|
break
|
|
|
|
if endYamlIndex == -1:
|
|
return none(string) # Section YAML mal formatée
|
|
|
|
# Extraire le bloc YAML sans les délimiteurs
|
|
let yamlBlock = lines[1..<endYamlIndex].join("\n")
|
|
return some(yamlBlock)
|
|
|
|
proc extractMetadataFromYaml*(content: string): Option[Metadata] =
|
|
## Tente d'extraire les métadonnées YAML du début d'un fichier Markdown
|
|
|
|
let lines = content.splitLines()
|
|
if lines.len < 3 or lines[0] != "---":
|
|
return none(Metadata) # Pas de section YAML
|
|
|
|
var
|
|
endYamlIndex = -1
|
|
metadata = newMetadata()
|
|
i = 1
|
|
|
|
# Chercher la fin de la section YAML
|
|
for j in 1..<lines.len:
|
|
if lines[j] == "---":
|
|
endYamlIndex = j
|
|
break
|
|
|
|
if endYamlIndex == -1:
|
|
return none(Metadata) # Section YAML mal formatée
|
|
|
|
metadata.isNew = false
|
|
|
|
# Analyser chaque ligne YAML
|
|
i = 1
|
|
while i < endYamlIndex:
|
|
let line = lines[i].strip()
|
|
|
|
if line.startsWith("Titre:"):
|
|
metadata.title = parseYamlValue(line)
|
|
elif line.startsWith("Description:"):
|
|
metadata.description = parseYamlValue(line)
|
|
elif line.startsWith("tags:"):
|
|
i += 1
|
|
let res = parseYamlSeq(lines, i)
|
|
metadata.tags = res.tags
|
|
i = res.endIndex
|
|
continue # On a déjà incrémenté i
|
|
elif line.startsWith("Date de création:"):
|
|
metadata.creationDate = parseYamlValue(line)
|
|
elif line.startsWith("Heure de création:"):
|
|
metadata.creationTime = parseYamlValue(line)
|
|
elif line.startsWith("Date de modification:"):
|
|
metadata.modificationDate = parseYamlValue(line)
|
|
elif line.startsWith("Heure de modification:"):
|
|
metadata.modificationTime = parseYamlValue(line)
|
|
elif line.startsWith("Author:"):
|
|
metadata.auteur = parseYamlValue(line)
|
|
elif line.startsWith("URL:"):
|
|
metadata.url = parseYamlValue(line)
|
|
elif line.startsWith("Lang:"):
|
|
metadata.lang = parseYamlValue(line)
|
|
elif line.startsWith("Catégorie:"):
|
|
metadata.category = parseYamlValue(line)
|
|
|
|
i += 1
|
|
|
|
return some(metadata)
|
|
|
|
proc analyzeWithAI*(content: string, configPath = "config.json"): AIAnalysisResult =
|
|
## Analyse le contenu Markdown avec l'IA et retourne les métadonnées générées
|
|
var
|
|
appConfig: AppConfig
|
|
activeModel: LLMModelConfig
|
|
|
|
try:
|
|
appConfig = loadConfig(configPath)
|
|
activeModel = appConfig.getActiveModel()
|
|
|
|
# Afficher les informations de debug si le mode debug est activé
|
|
if appConfig.debugModeActive:
|
|
echo fmt"[DEBUG] API URL: {appConfig.apiUrl}"
|
|
echo fmt"[DEBUG] Modèle actif: {activeModel.name}"
|
|
echo fmt"[DEBUG] activeModelName dans config: {appConfig.activeModelName}"
|
|
except Exception as e:
|
|
echo fmt"Erreur lors du chargement de la configuration: {e.msg}"
|
|
return AIAnalysisResult(title: "", description: "", tags: @[])
|
|
|
|
let client = newHttpClient()
|
|
client.headers = newHttpHeaders({
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json"
|
|
})
|
|
|
|
try:
|
|
# Construction du corps de la requête
|
|
var requestBody = %* {
|
|
"model": activeModel.name,
|
|
"messages": [
|
|
{
|
|
"role": activeModel.systemRole,
|
|
"content": activeModel.systemContent
|
|
},
|
|
{
|
|
"role": activeModel.userRole,
|
|
"content": formatUserContent(activeModel, content)
|
|
}
|
|
],
|
|
"max_tokens": 2048,
|
|
"temperature": 0.7,
|
|
"frequency_penalty": 0.0,
|
|
"presence_penalty": 0.0,
|
|
"stream": false,
|
|
"stop": []
|
|
}
|
|
|
|
# Afficher la requête complète en mode débogage
|
|
if appConfig.debugModeActive:
|
|
echo fmt"[DEBUG] Requête: {$requestBody}"
|
|
echo fmt"[DEBUG] Envoi de la requête à: {appConfig.apiUrl}"
|
|
|
|
let
|
|
response = client.post(appConfig.apiUrl, body = $requestBody)
|
|
responseJson = parseJson(response.body)
|
|
|
|
if appConfig.debugModeActive:
|
|
echo fmt"[DEBUG] Code de réponse: {response.code}"
|
|
|
|
if response.code == Http200:
|
|
if appConfig.debugModeActive:
|
|
echo fmt"[DEBUG] Réponse reçue avec succès"
|
|
var content = responseJson["choices"][0]["message"]["content"].getStr()
|
|
|
|
# Nettoyage explicite des caractères d'échappement \n dans la réponse complète
|
|
content = content.replace("\\n", "\n")
|
|
|
|
var
|
|
title = ""
|
|
description = ""
|
|
tags: seq[string] = @[]
|
|
|
|
# Extraction du bloc YAML
|
|
let yamlMatch = content.extractYamlBlock()
|
|
if yamlMatch.isSome:
|
|
let yaml = yamlMatch.get()
|
|
|
|
if yaml.len > 0:
|
|
# Extraction des valeurs
|
|
title = extractYamlValue(yaml, "title")
|
|
description = extractYamlValue(yaml, "description")
|
|
tags = extractYamlList(yaml, "tags")
|
|
|
|
if appConfig.debugModeActive:
|
|
echo fmt"[DEBUG] Titre extrait: {title}"
|
|
echo fmt"[DEBUG] Description extraite: {description}"
|
|
echo fmt"[DEBUG] Tags extraits: {tags.len} tags"
|
|
|
|
return AIAnalysisResult(title: title, description: description, tags: tags)
|
|
else:
|
|
echo fmt"Erreur lors de l'appel à l'API: {response.code} - {response.body}"
|
|
return AIAnalysisResult(title: "", description: "", tags: @[])
|
|
except Exception as e:
|
|
echo fmt"Exception lors de l'appel à l'API: {e.msg}"
|
|
return AIAnalysisResult(title: "", description: "", tags: @[])
|
|
finally:
|
|
client.close()
|
|
|
|
proc extractCategoryFromPath*(filePath: string): string =
|
|
## Extrait une catégorie basée sur le chemin du fichier
|
|
## Format: Parent/Enfant/SousEnfant
|
|
if filePath.len == 0:
|
|
return "Non classé"
|
|
|
|
# Normaliser le chemin en utilisant / plutôt que \
|
|
var normalizedPath = filePath.replace('\\', '/')
|
|
|
|
# Obtenir le chemin relatif en supprimant les chemins absolus
|
|
let
|
|
driveLetterPos = normalizedPath.find(":/")
|
|
pathStart = if driveLetterPos != -1: driveLetterPos + 2 else: 0
|
|
relativePath = normalizedPath[pathStart..^1]
|
|
|
|
# Diviser le chemin en parties
|
|
var parts = relativePath.split('/')
|
|
|
|
# Retirer le nom du fichier (dernier élément)
|
|
if parts.len > 0 and parts[^1].contains('.'):
|
|
discard parts.pop()
|
|
|
|
# Filtrer les parties vides et les noms de répertoires spéciaux
|
|
parts = parts.filterIt(it.len > 0 and it != "." and it != "..")
|
|
|
|
# Si après filtrage le chemin est vide, renvoyer catégorie par défaut
|
|
if parts.len == 0:
|
|
return "Non classé"
|
|
|
|
# Rejoindre les parties avec un /
|
|
result = parts.join("/")
|
|
|
|
proc updateMetadata*(metadata: var Metadata, content: string, filePath: string, useAI: bool = true, forceUpdateCategory: bool = true): bool =
|
|
## Complète les métadonnées manquantes
|
|
## Retourne vrai si des modifications ont été apportées
|
|
##
|
|
## Paramètres:
|
|
## - metadata: Les métadonnées à mettre à jour
|
|
## - content: Le contenu du fichier Markdown
|
|
## - filePath: Chemin complet vers le fichier
|
|
## - useAI: Utiliser l'IA pour compléter les métadonnées manquantes
|
|
## - forceUpdateCategory: Si vrai, remplace toujours la catégorie par celle extraite du chemin
|
|
var modified = false
|
|
|
|
# Utiliser l'IA pour compléter les métadonnées manquantes
|
|
if useAI:
|
|
if metadata.title == "" or metadata.description == "" or metadata.tags.len == 0:
|
|
# Analyser le contenu avec l'IA
|
|
let aiResult = analyzeWithAI(content)
|
|
|
|
# Compléter le titre s'il est manquant
|
|
if metadata.title == "" and aiResult.title != "":
|
|
metadata.title = aiResult.title
|
|
modified = true
|
|
|
|
# Compléter la description si elle est manquante
|
|
if metadata.description == "" and aiResult.description != "":
|
|
metadata.description = aiResult.description
|
|
modified = true
|
|
|
|
# Compléter les tags s'ils sont manquants
|
|
if metadata.tags.len == 0 and aiResult.tags.len > 0:
|
|
# Filtrer pour ne garder que les tags valides et nettoyer les espaces
|
|
var cleanedTags: seq[string] = @[]
|
|
for tag in aiResult.tags:
|
|
let cleanedTag = cleanTagText(tag)
|
|
if isValidTag(cleanedTag):
|
|
cleanedTags.add(cleanedTag)
|
|
|
|
metadata.tags = cleanedTags
|
|
if metadata.tags.len > 0:
|
|
modified = true
|
|
|
|
# Méthodes traditionnelles si l'IA n'est pas utilisée ou n'a pas fourni de données
|
|
if metadata.title == "":
|
|
metadata.title = extractTitle(content, filePath)
|
|
modified = true
|
|
|
|
# Obtenir la date de création réelle du fichier
|
|
let
|
|
fileExists = fileExists(filePath)
|
|
currentDateTime = formatCurrentDateTime() # Date/heure actuelle
|
|
|
|
# Utiliser la date de création réelle du fichier si elle existe
|
|
if metadata.creationDate == "" or metadata.creationTime == "":
|
|
if fileExists:
|
|
try:
|
|
let creationTime = getCreationTime(filePath)
|
|
let formattedCreationDate = format(creationTime, "yyyy-MM-dd")
|
|
let formattedCreationTime = format(creationTime, "HH:mm:ss")
|
|
|
|
if metadata.creationDate == "":
|
|
metadata.creationDate = formattedCreationDate
|
|
modified = true
|
|
|
|
if metadata.creationTime == "":
|
|
metadata.creationTime = formattedCreationTime
|
|
modified = true
|
|
except:
|
|
# En cas d'erreur, utiliser la date/heure actuelle comme fallback
|
|
if metadata.creationDate == "":
|
|
metadata.creationDate = currentDateTime.date
|
|
modified = true
|
|
|
|
if metadata.creationTime == "":
|
|
metadata.creationTime = currentDateTime.time
|
|
modified = true
|
|
else:
|
|
# Pour les nouveaux fichiers, utiliser la date/heure actuelle
|
|
if metadata.creationDate == "":
|
|
metadata.creationDate = currentDateTime.date
|
|
modified = true
|
|
|
|
if metadata.creationTime == "":
|
|
metadata.creationTime = currentDateTime.time
|
|
modified = true
|
|
|
|
if metadata.auteur == "":
|
|
metadata.auteur = "Non spécifié"
|
|
modified = true
|
|
|
|
if metadata.lang == "":
|
|
metadata.lang = "fr"
|
|
modified = true
|
|
|
|
if forceUpdateCategory or metadata.category == "" or metadata.category == "Non classé":
|
|
# Utiliser la structure des répertoires comme catégorie
|
|
let pathCategory = extractCategoryFromPath(filePath)
|
|
echo fmt">>> extractCategoryFromPath pour {filePath} a retourné: {pathCategory}"
|
|
# Toujours mettre à jour la catégorie si forceUpdateCategory est true
|
|
echo fmt">>> Mise à jour de la catégorie: {metadata.category} -> {pathCategory}"
|
|
metadata.category = pathCategory
|
|
modified = true
|
|
|
|
# Nettoyer les tags existants pour éliminer les tags non valides
|
|
if metadata.tags.len > 0:
|
|
let originalTags = metadata.tags # Garder une copie des tags originaux
|
|
var cleanedTags: seq[string] = @[]
|
|
|
|
for tag in metadata.tags:
|
|
let cleanedTag = cleanTagText(tag)
|
|
if isValidTag(cleanedTag):
|
|
cleanedTags.add(cleanedTag)
|
|
|
|
# Vérifier si les tags ont été modifiés (nombre différent ou contenu différent)
|
|
if originalTags.len != cleanedTags.len or originalTags != cleanedTags:
|
|
modified = true
|
|
|
|
metadata.tags = cleanedTags
|
|
|
|
# Si des modifications ont été apportées, mettre à jour la date et l'heure de modification
|
|
# pour refléter le moment actuel du traitement
|
|
if modified:
|
|
metadata.modificationDate = currentDateTime.date
|
|
metadata.modificationTime = currentDateTime.time
|
|
else:
|
|
# Si aucune modification n'a été apportée, vérifier si la date/heure de modification est définie
|
|
# Si non, utiliser la date de dernière modification du fichier ou la date actuelle
|
|
if metadata.modificationDate == "" or metadata.modificationTime == "":
|
|
if fileExists:
|
|
try:
|
|
let modificationTime = getLastModificationTime(filePath)
|
|
let formattedModDate = format(modificationTime, "yyyy-MM-dd")
|
|
let formattedModTime = format(modificationTime, "HH:mm:ss")
|
|
|
|
if metadata.modificationDate == "":
|
|
metadata.modificationDate = formattedModDate
|
|
modified = true # Marquer comme modifié même si on utilise juste la date du fichier
|
|
|
|
if metadata.modificationTime == "":
|
|
metadata.modificationTime = formattedModTime
|
|
modified = true # Marquer comme modifié même si on utilise juste l'heure du fichier
|
|
except:
|
|
# En cas d'erreur, utiliser la date/heure actuelle
|
|
if metadata.modificationDate == "":
|
|
metadata.modificationDate = currentDateTime.date
|
|
modified = true
|
|
|
|
if metadata.modificationTime == "":
|
|
metadata.modificationTime = currentDateTime.time
|
|
modified = true
|
|
else:
|
|
# Pour les nouveaux fichiers, utiliser la date/heure actuelle
|
|
if metadata.modificationDate == "":
|
|
metadata.modificationDate = currentDateTime.date
|
|
modified = true
|
|
|
|
if metadata.modificationTime == "":
|
|
metadata.modificationTime = currentDateTime.time
|
|
modified = true
|
|
|
|
return modified
|
|
|
|
proc metadataToYaml*(metadata: Metadata): string =
|
|
## Convertit les métadonnées en YAML formaté
|
|
|
|
# Créer les lignes YAML individuellement sans l'opérateur &=
|
|
var lines: seq[string] = @["---"]
|
|
|
|
lines.add(fmt"Titre: {metadata.title}")
|
|
lines.add(fmt"Description: {metadata.description}")
|
|
|
|
lines.add("tags:")
|
|
for tag in metadata.tags:
|
|
lines.add(fmt" - {tag}")
|
|
|
|
lines.add(fmt"Date de création: {metadata.creationDate}")
|
|
lines.add(fmt"Heure de création: {metadata.creationTime}")
|
|
lines.add(fmt"Date de modification: {metadata.modificationDate}")
|
|
lines.add(fmt"Heure de modification: {metadata.modificationTime}")
|
|
lines.add(fmt"Auteur: {metadata.auteur}")
|
|
lines.add(fmt"URL: {metadata.url}")
|
|
lines.add(fmt"Lang: {metadata.lang}")
|
|
lines.add(fmt"Catégorie: {metadata.category}")
|
|
|
|
lines.add("---")
|
|
|
|
# Joindre toutes les lignes avec des retours à la ligne
|
|
return lines.join("\n")
|
|
|
|
proc getContentWithoutYaml*(content: string): string =
|
|
## Retourne le contenu sans la section YAML initiale, s'il y en a une
|
|
let lines = content.splitLines()
|
|
|
|
if lines.len >= 2 and lines[0] == "---":
|
|
# Chercher la ligne de fin de la section YAML
|
|
for i in 1..<lines.len:
|
|
if lines[i] == "---":
|
|
# Retourner le contenu après la section YAML
|
|
return lines[i+1..^1].join("\n")
|
|
|
|
# Pas de section YAML, retourner le contenu tel quel
|
|
return content
|
|
|
|
proc addMetadataToContent*(metadata: Metadata, content: string): string =
|
|
## Ajoute les métadonnées formatées en YAML au début du contenu
|
|
## Supprime d'abord toute section YAML existante
|
|
let cleanContent = getContentWithoutYaml(content)
|
|
|
|
return metadataToYaml(metadata) & "\n" & cleanContent
|
|
|
|
proc isModelAvailable*(config: AppConfig): bool =
|
|
## Vérifie si le modèle actif est disponible sur le serveur LM Studio
|
|
var client = newHttpClient()
|
|
defer: client.close()
|
|
client.headers = newHttpHeaders({
|
|
"Accept": "application/json"
|
|
})
|
|
|
|
try:
|
|
if config.debugModeActive:
|
|
echo fmt"[DEBUG] Vérification de la disponibilité des modèles sur: {config.apiMonitorUrl}"
|
|
|
|
let
|
|
response = client.get(config.apiMonitorUrl)
|
|
responseJson = parseJson(response.body)
|
|
|
|
if response.code == Http200:
|
|
# Vérifier si le modèle actif figure dans la liste des modèles disponibles
|
|
var activeModel = config.activeModelName
|
|
|
|
# Vérifier si la réponse contient une clé "data" avec un tableau de modèles
|
|
if responseJson.hasKey("data") and responseJson["data"].kind == JArray:
|
|
let modelsArray = responseJson["data"]
|
|
for modelObj in modelsArray:
|
|
if modelObj.hasKey("id") and modelObj["id"].getStr() == activeModel:
|
|
echo fmt"Modèle '{activeModel}' disponible sur le serveur"
|
|
return true
|
|
|
|
# Si nous arrivons ici, le modèle n'a pas été trouvé
|
|
echo fmt"Modèle '{activeModel}' non disponible sur le serveur."
|
|
return false
|
|
else:
|
|
echo "Format de réponse inattendu de l'API."
|
|
return false
|
|
else:
|
|
echo fmt"Erreur lors de la connexion à l'API: {response.code}"
|
|
return false
|
|
except Exception as e:
|
|
echo fmt"Exception lors de la vérification du modèle: {e.msg}"
|
|
return false
|