浏览代码

Merge branch 'development' of github.com:visuddhinanda/mint into development

visuddhinanda 9 月之前
父节点
当前提交
955138c98d
共有 43 个文件被更改,包括 1569 次插入66 次删除
  1. 2 1
      ai-translate/docker/Dockerfile
  2. 1 1
      ai-translate/docker/build.sh
  3. 1 1
      ai-translate/docker/run.sh
  4. 1 1
      api-v12/docker/build.sh
  5. 1 1
      api-v12/docker/run.sh
  6. 1 0
      api-v8/app/Console/Commands/CreateMyHanCrop.php
  7. 37 10
      api-v8/app/Console/Commands/TestMdRender.php
  8. 1 10
      api-v8/app/Http/Api/TemplateRender.php
  9. 1 1
      api-v8/app/Http/Controllers/ProjectTreeController.php
  10. 28 0
      api-v8/app/Providers/TemplateServiceProvider.php
  11. 13 0
      api-v8/app/Services/Template/ContentNode.php
  12. 15 0
      api-v8/app/Services/Template/Contracts/ParserInterface.php
  13. 25 0
      api-v8/app/Services/Template/MarkdownNode.php
  14. 51 0
      api-v8/app/Services/Template/ParameterResolver.php
  15. 21 0
      api-v8/app/Services/Template/ParsedDocument.php
  16. 141 0
      api-v8/app/Services/Template/Renderers/HtmlRenderer.php
  17. 36 0
      api-v8/app/Services/Template/Renderers/JsonRenderer.php
  18. 70 0
      api-v8/app/Services/Template/Renderers/MarkdownRenderer.php
  19. 32 0
      api-v8/app/Services/Template/Renderers/RendererFactory.php
  20. 150 0
      api-v8/app/Services/Template/Renderers/TextRenderer.php
  21. 34 0
      api-v8/app/Services/Template/TemplateNode.php
  22. 159 0
      api-v8/app/Services/Template/TemplateParser.php
  23. 83 0
      api-v8/app/Services/Template/TemplateRegistry.php
  24. 209 0
      api-v8/app/Services/Template/TemplateService.php
  25. 114 0
      api-v8/app/Services/Template/TemplateTokenizer.php
  26. 25 0
      api-v8/app/Services/Template/TextNode.php
  27. 1 0
      api-v8/app/Services/TemplateRender.php
  28. 115 0
      api-v8/app/Services/Templates/NoteTemplate.php
  29. 8 8
      api-v8/app/Services/Templates/TermTemplate.php
  30. 49 0
      api-v8/config/template.php
  31. 1 1
      api-v8/storage/resources
  32. 4 2
      open-ai-server/.gitignore
  33. 5 7
      open-ai-server/README.md
  34. 1 0
      open-ai-server/config.orig.json
  35. 2 0
      open-ai-server/docker/.gitignore
  36. 33 0
      open-ai-server/docker/Dockerfile
  37. 21 0
      open-ai-server/docker/build.sh
  38. 8 0
      open-ai-server/docker/run.sh
  39. 11 6
      open-ai-server/package.json
  40. 22 0
      open-ai-server/src/index.js
  41. 14 0
      open-ai-server/src/logger.js
  42. 10 16
      open-ai-server/src/server.js
  43. 12 0
      open-ai-server/webpack.config.js

+ 2 - 1
ai-translate/docker/Dockerfile

@@ -30,7 +30,8 @@ RUN echo 'source $HOME/python3/bin/activate' >> $HOME/.bashrc
 
 # https://pip.pypa.io/en/stable/installation/#get-pip-py
 ADD --chown=deploy https://bootstrap.pypa.io/get-pip.py /opt/
-RUN bash -c "source $HOME/python3/bin/activate && python3 /opt/get-pip.py"
+RUN bash -c ". $HOME/python3/bin/activate && python3 /opt/get-pip.py"
+RUN bash -c ". $HOME/python3/bin/activate && pip install pika requests redis[hiredis] openai"
 
 # COPY --chown=deploy README.md pyproject.toml /opt/ai-translate/
 # COPY --chown=deploy ai_translate /opt/ai-translate/ai_translate

+ 1 - 1
ai-translate/docker/build.sh

@@ -8,7 +8,7 @@ if [ "$#" -ne 1 ]; then
 fi
 
 export VERSION=$(date "+%4Y%m%d%H%M%S")
-export CODE="mint-python$1"
+export CODE="mint-python-$1"
 export TAR="$CODE-$VERSION-$(uname -m)"
 
 podman pull ubuntu:latest

+ 1 - 1
ai-translate/docker/run.sh

@@ -5,4 +5,4 @@ if [ "$#" -ne 1 ]; then
     exit 1
 fi
 
-podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-python$1"
+podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-python-$1"

+ 1 - 1
api-v12/docker/build.sh

@@ -8,7 +8,7 @@ if [ "$#" -ne 1 ]; then
 fi
 
 export VERSION=$(date "+%4Y%m%d%H%M%S")
-export CODE="mint-php$1"
+export CODE="mint-php-$1"
 export TAR="$CODE-$VERSION-$(uname -m)"
 
 podman pull ubuntu:latest

+ 1 - 1
api-v12/docker/run.sh

@@ -5,4 +5,4 @@ if [ "$#" -ne 1 ]; then
     exit 1
 fi
 
-podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-php$1"
+podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-php-$1"

+ 1 - 0
api-v8/app/Console/Commands/CreateMyHanCrop.php

@@ -105,6 +105,7 @@ class CreateMyHanCrop extends Command
             Storage::disk('local')->makeDirectory($dir);
             Storage::disk('local')->makeDirectory($dir . '/img');
             Storage::disk('local')->put($dir . "/index.html", $content);
+            Storage::disk('local')->put($dir . "/img/{$page}", $page);
             $this->info("page={$page} word=" . count($words));
         } else {
             $this->error('page' . $page . 'no words');

+ 37 - 10
api-v8/app/Console/Commands/TestMdRender.php

@@ -41,10 +41,37 @@ class TestMdRender extends Command
      */
     public function handle()
     {
-        if(\App\Tools\Tools::isStop()){
+        if (\App\Tools\Tools::isStop()) {
             return 0;
         }
-        Log::info('md render start item='.$this->argument('item'));
+
+        $content = <<<md
+        # 测试
+        ## 测试
+
+        {{note|text=这是一个普通信息提示|type=info}}
+
+        下面是一个警告信息:
+        {{warning|message=请注意这个重要提示|title=重要}}
+
+        你也可以使用位置参数:
+        {{note|
+        成功完成操作|
+        success}}
+
+        支持嵌套模板:
+        {{info|
+        content=外层信息 {{note|内嵌提示|warning}} 继续外层|
+        title=嵌套示例}}
+
+        **粗体文本** 和 *斜体文本* 也被支持。
+        md;
+        $parser = new \App\Services\Template\TemplateService(false);
+        $render = $parser->parseAndRender($content, 'json');
+        var_dump($render);
+        return 0;
+
+        Log::info('md render start item=' . $this->argument('item'));
         $data = array();
         $data['bold'] = <<<md
         **三十位** 经在[中间]六处为**[licchavi]**,在极果为**慧解脱**
@@ -147,24 +174,24 @@ class TestMdRender extends Command
         Markdown::driver($this->option('driver'));
 
         $format = $this->option('format');
-        if(empty($format)){
-            $formats = ['react','unity','text','tex','html','simple'];
-        }else{
+        if (empty($format)) {
+            $formats = ['react', 'unity', 'text', 'tex', 'html', 'simple'];
+        } else {
             $formats = [$format];
         }
         foreach ($formats as $format) {
             $this->info("format:{$format}");
             foreach ($data as $key => $value) {
                 $_item = $this->argument('item');
-                if(!empty($_item) && $key !==$_item){
+                if (!empty($_item) && $key !== $_item) {
                     continue;
                 }
                 $mdRender = new MdRender([
-                    'format'=>$format,
-                    'footnote'=>true,
-                    'paragraph'=>true,
+                    'format' => $format,
+                    'footnote' => true,
+                    'paragraph' => true,
                 ]);
-                $output = $mdRender->convert($value,['00ae2c48-c204-4082-ae79-79ba2740d506']);
+                $output = $mdRender->convert($value, ['00ae2c48-c204-4082-ae79-79ba2740d506']);
                 echo $output;
             }
         }

+ 1 - 10
api-v8/app/Http/Api/TemplateRender.php

@@ -553,17 +553,8 @@ class TemplateRender
                     'tpl' => 'nissaya',
                 ];
                 break;
-            case 'text':
-                $output = $pali . '၊' . $meaning;
-                break;
-            case 'tex':
-                $output = $pali . '၊' . $meaning;
-                break;
-            case 'simple':
-                $output = $pali . '၊' . $meaning;
-                break;
             case 'prompt':
-                $output = Tools::MyToRm($pali) . ':' . $meaning;
+                $output = Tools::MyToRm($pali) . ':' . end($props["meaning"]);
                 break;
             default:
                 $output = $pali . '၊' . $meaning;

+ 1 - 1
api-v8/app/Http/Controllers/ProjectTreeController.php

@@ -61,7 +61,7 @@ class ProjectTreeController extends Controller
         }
         foreach ($newData as $key => $value) {
             if ($value['parent_id']) {
-                $parent = array_find($newData, function ($element) use ($value) {
+                $parent = \array_find($newData, function ($element) use ($value) {
                     return $element['old_id'] == $value['parent_id'];
                 });
                 if ($parent) {

+ 28 - 0
api-v8/app/Providers/TemplateServiceProvider.php

@@ -0,0 +1,28 @@
+<?php
+
+// ================== 服务提供者 ==================
+
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use App\Services\Template\TemplateService;
+
+class TemplateServiceProvider extends ServiceProvider
+{
+    public function register(): void
+    {
+        $this->app->singleton(TemplateService::class, function ($app) {
+            return new TemplateService(
+                config('template.cache_enabled', true),
+                config('template.cache_ttl', 3600)
+            );
+        });
+    }
+
+    public function boot(): void
+    {
+        $this->publishes([
+            __DIR__ . '/../config/template.php' => config_path('template.php'),
+        ], 'template-config');
+    }
+}

+ 13 - 0
api-v8/app/Services/Template/ContentNode.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Services\Template;
+
+
+abstract class ContentNode
+{
+    public string $type;
+    public string $content;
+    public array $position = [];
+
+    abstract public function toArray(): array;
+}

+ 15 - 0
api-v8/app/Services/Template/Contracts/ParserInterface.php

@@ -0,0 +1,15 @@
+<?php
+
+// ================== 契约接口 ==================
+
+namespace App\Services\Template\Contracts;
+
+interface ParserInterface
+{
+    public function parse(string $content): \App\Services\Template\ParsedDocument;
+}
+
+interface RendererInterface
+{
+    public function render(\App\Services\Template\ParsedDocument $document): string;
+}

+ 25 - 0
api-v8/app/Services/Template/MarkdownNode.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Services\Template;
+
+
+class MarkdownNode extends ContentNode
+{
+    public string $content;
+
+    public function __construct(string $content, array $position = [])
+    {
+        $this->type = 'markdown';
+        $this->content = $content;
+        $this->position = $position;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'type' => $this->type,
+            'content' => $this->content,
+            'position' => $this->position
+        ];
+    }
+}

+ 51 - 0
api-v8/app/Services/Template/ParameterResolver.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Services\Template;
+
+
+// ================== 参数解析器 ==================
+
+class ParameterResolver
+{
+    private TemplateRegistry $registry;
+
+    public function __construct(TemplateRegistry $registry)
+    {
+        $this->registry = $registry;
+    }
+
+    public function resolveParameters(string $templateName, array $rawParams): array
+    {
+        $template = $this->registry->getTemplate($templateName);
+        if (!$template) {
+            return $rawParams;
+        }
+
+        $resolved = [];
+        $paramMapping = $template['paramMapping'] ?? [];
+        $defaultValues = $template['defaultValues'] ?? [];
+
+        // 处理位置参数
+        $positionIndex = 0;
+        foreach ($rawParams as $key => $value) {
+            if (is_numeric($key)) {
+                // 位置参数
+                $paramName = $paramMapping[$positionIndex] ?? $positionIndex;
+                $resolved[$paramName] = $value;
+                $positionIndex++;
+            } else {
+                // 命名参数
+                $resolved[$key] = $value;
+            }
+        }
+
+        // 应用默认值
+        foreach ($defaultValues as $key => $value) {
+            if (!isset($resolved[$key])) {
+                $resolved[$key] = $value;
+            }
+        }
+
+        return $resolved;
+    }
+}

+ 21 - 0
api-v8/app/Services/Template/ParsedDocument.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Services\Template;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\Contracts\ParserInterface;
+
+// ================== 数据结构 ==================
+
+class ParsedDocument
+{
+    public string $type = 'document';
+    public array $content = [];
+    public array $meta = [];
+
+    public function __construct(array $content = [], array $meta = [])
+    {
+        $this->content = $content;
+        $this->meta = $meta;
+    }
+}

+ 141 - 0
api-v8/app/Services/Template/Renderers/HtmlRenderer.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace App\Services\Template\Renderers;
+
+use Illuminate\Support\Str;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\ParsedDocument;
+use App\Services\Template\ContentNode;
+use App\Services\Template\TextNode;
+use App\Services\Template\MarkdownNode;
+use App\Services\Template\TemplateNode;
+
+
+
+// ================== HTML 渲染器 ==================
+
+class HtmlRenderer implements RendererInterface
+{
+    private array $templateMappings = [];
+
+    public function __construct()
+    {
+        $this->initializeTemplateMappings();
+    }
+
+    private function initializeTemplateMappings(): void
+    {
+        $this->templateMappings = [
+            'note' => function ($params) {
+                $type = $params['type'] ?? 'info';
+                $text = $params['text'] ?? '';
+                $title = $params['title'] ?? '';
+
+                $typeClass = match ($type) {
+                    'warning' => 'alert-warning',
+                    'error' => 'alert-danger',
+                    'success' => 'alert-success',
+                    default => 'alert-info'
+                };
+
+                $titleHtml = $title ? "<h6 class='alert-heading'>$title</h6>" : '';
+                return "<div class='alert $typeClass' role='alert'>$titleHtml$text</div>";
+            },
+
+            'info' => function ($params) {
+                $content = $params['content'] ?? '';
+                $title = $params['title'] ?? '';
+
+                $titleHtml = $title ? "<h6 class='alert-heading'>$title</h6>" : '';
+                return "<div class='alert alert-info' role='alert'>$titleHtml$content</div>";
+            },
+
+            'warning' => function ($params) {
+                $message = $params['message'] ?? '';
+                $title = $params['title'] ?? '';
+
+                $titleHtml = $title ? "<h6 class='alert-heading'>$title</h6>" : '';
+                return "<div class='alert alert-warning' role='alert'>$titleHtml$message</div>";
+            }
+        ];
+    }
+
+    public function render(ParsedDocument $document): string
+    {
+        $html = '';
+
+        foreach ($document->content as $node) {
+            $html .= $this->renderNode($node);
+        }
+
+        return Str::markdown($html);
+    }
+
+    private function renderNode(ContentNode $node): string
+    {
+        switch ($node->type) {
+            case 'text':
+                return $this->renderText($node);
+            case 'markdown':
+                return $this->renderMarkdown($node);
+            case 'template':
+                return $this->renderTemplate($node);
+            default:
+                return '';
+        }
+    }
+
+    public function renderText(TextNode $text): string
+    {
+        return htmlspecialchars($text->content, ENT_QUOTES, 'UTF-8');
+    }
+
+    private function renderMarkdown(MarkdownNode $markdown): string
+    {
+        return Str::markdown($markdown->content);
+        // 简单的 Markdown 渲染,实际项目中可以使用 CommonMark 等库
+        $html = $markdown->content;
+
+        // 处理粗体
+        $html = preg_replace('/\*\*(.*?)\*\*/', '<strong>$1</strong>', $html);
+        $html = preg_replace('/\*(.*?)\*/', '<em>$1</em>', $html);
+
+        // 处理链接
+        $html = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $html);
+
+        return $html;
+    }
+
+    public function renderTemplate(TemplateNode $template): string
+    {
+        if (!isset($this->templateMappings[$template->name])) {
+            // 未知模板,返回原始内容
+            return htmlspecialchars($template->raw, ENT_QUOTES, 'UTF-8');
+        }
+
+        $renderer = $this->templateMappings[$template->name];
+
+        // 处理参数中的嵌套内容
+        $processedParams = [];
+        foreach ($template->parameters as $key => $value) {
+            if (is_array($value)) {
+                // 嵌套内容,递归渲染
+                $nestedHtml = '';
+                foreach ($value as $childNode) {
+                    $nestedHtml .= $this->renderNode($childNode);
+                }
+                $processedParams[$key] = $nestedHtml;
+            } else {
+                $processedParams[$key] = $value;
+            }
+        }
+
+        return $renderer($processedParams);
+    }
+
+    public function registerTemplate(string $name, callable $renderer): void
+    {
+        $this->templateMappings[$name] = $renderer;
+    }
+}

+ 36 - 0
api-v8/app/Services/Template/Renderers/JsonRenderer.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Services\Template\Renderers;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\ParsedDocument;
+use App\Services\Template\ContentNode;
+use App\Services\Template\TextNode;
+use App\Services\Template\MarkdownNode;
+use App\Services\Template\TemplateNode;
+
+// ================== JSON 渲染器 ==================
+
+class JsonRenderer implements RendererInterface
+{
+    public function render(ParsedDocument $document): string
+    {
+        $data = [
+            'type' => $document->type,
+            'content' => array_map(fn($node) => $node->toArray(), $document->content),
+            'meta' => $document->meta
+        ];
+
+        return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+    }
+
+    public function renderTemplate(TemplateNode $template): string
+    {
+        return json_encode($template->toArray(), JSON_UNESCAPED_UNICODE);
+    }
+
+    public function renderText(TextNode $text): string
+    {
+        return json_encode($text->toArray(), JSON_UNESCAPED_UNICODE);
+    }
+}

+ 70 - 0
api-v8/app/Services/Template/Renderers/MarkdownRenderer.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Services\Template\Renderers;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\ParsedDocument;
+use App\Services\Template\ContentNode;
+use App\Services\Template\TextNode;
+use App\Services\Template\MarkdownNode;
+use App\Services\Template\TemplateNode;
+
+
+
+
+// ================== Markdown 渲染器 ==================
+
+class MarkdownRenderer implements RendererInterface
+{
+    public function render(ParsedDocument $document): string
+    {
+        $markdown = '';
+
+        foreach ($document->content as $node) {
+            $markdown .= $this->renderNode($node);
+        }
+
+        return $markdown;
+    }
+
+    private function renderNode(ContentNode $node): string
+    {
+        switch ($node->type) {
+            case 'text':
+                return $this->renderText($node);
+            case 'markdown':
+                return $node->content;
+            case 'template':
+                return $this->renderTemplate($node);
+            default:
+                return '';
+        }
+    }
+
+    public function renderText(TextNode $text): string
+    {
+        return $text->content;
+    }
+
+    public function renderTemplate(TemplateNode $template): string
+    {
+        // 将模板转换回 Markdown 格式
+        $params = [];
+
+        foreach ($template->parameters as $key => $value) {
+            if (is_array($value)) {
+                // 嵌套内容,递归渲染
+                $nestedMarkdown = '';
+                foreach ($value as $childNode) {
+                    $nestedMarkdown .= $this->renderNode($childNode);
+                }
+                $params[] = "$key=$nestedMarkdown";
+            } else {
+                $params[] = is_numeric($key) ? $value : "$key=$value";
+            }
+        }
+
+        $paramString = implode('|', $params);
+        return "{{" . $template->name . ($paramString ? "|$paramString" : "") . "}}";
+    }
+}

+ 32 - 0
api-v8/app/Services/Template/Renderers/RendererFactory.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Services\Template\Renderers;
+
+use App\Services\Template\Contracts\RendererInterface;
+
+// ================== 渲染器工厂 ==================
+
+class RendererFactory
+{
+    private static array $renderers = [];
+
+    public static function create(string $format): RendererInterface
+    {
+        if (!isset(self::$renderers[$format])) {
+            self::$renderers[$format] = match ($format) {
+                'json' => new JsonRenderer(),
+                'html' => new HtmlRenderer(),
+                'markdown' => new MarkdownRenderer(),
+                'text' => new TextRenderer(),
+                default => throw new \InvalidArgumentException("Unsupported format: $format")
+            };
+        }
+
+        return self::$renderers[$format];
+    }
+
+    public static function getSupportedFormats(): array
+    {
+        return ['json', 'html', 'markdown', 'text'];
+    }
+}

+ 150 - 0
api-v8/app/Services/Template/Renderers/TextRenderer.php

@@ -0,0 +1,150 @@
+<?php
+
+namespace App\Services\Template\Renderers;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\ParsedDocument;
+use App\Services\Template\ContentNode;
+use App\Services\Template\TextNode;
+use App\Services\Template\MarkdownNode;
+use App\Services\Template\TemplateNode;
+
+
+
+// ================== 纯文本渲染器 ==================
+
+class TextRenderer implements RendererInterface
+{
+    private array $templateTexts = [];
+
+    public function __construct()
+    {
+        $this->initializeTemplateTexts();
+    }
+
+    private function initializeTemplateTexts(): void
+    {
+        $this->templateTexts = [
+            'note' => function ($params) {
+                $type = $params['type'] ?? 'info';
+                $text = $params['text'] ?? '';
+                $title = $params['title'] ?? '';
+
+                $prefix = match ($type) {
+                    'warning' => '[警告]',
+                    'error' => '[错误]',
+                    'success' => '[成功]',
+                    default => '[信息]'
+                };
+
+                return $prefix . ($title ? " $title: " : ' ') . $text;
+            },
+
+            'info' => function ($params) {
+                $content = $params['content'] ?? '';
+                $title = $params['title'] ?? '';
+
+                return '[信息]' . ($title ? " $title: " : ' ') . $content;
+            },
+
+            'warning' => function ($params) {
+                $message = $params['message'] ?? '';
+                $title = $params['title'] ?? '';
+
+                return '[警告]' . ($title ? " $title: " : ' ') . $message;
+            }
+        ];
+    }
+
+    public function render(ParsedDocument $document): string
+    {
+        $text = '';
+
+        foreach ($document->content as $node) {
+            $text .= $this->renderNode($node);
+        }
+
+        return $text;
+    }
+
+    private function renderNode(ContentNode $node): string
+    {
+        switch ($node->type) {
+            case 'text':
+                return $this->renderText($node);
+            case 'markdown':
+                return $this->renderMarkdownAsText($node);
+            case 'template':
+                return $this->renderTemplate($node);
+            default:
+                return '';
+        }
+    }
+
+    public function renderText(TextNode $text): string
+    {
+        return $text->content;
+    }
+
+    private function renderMarkdownAsText(MarkdownNode $markdown): string
+    {
+        // 移除 Markdown 标记,返回纯文本
+        $text = $markdown->content;
+
+        // 移除粗体和斜体标记
+        $text = preg_replace('/\*\*(.*?)\*\*/', '$1', $text);
+        $text = preg_replace('/\*(.*?)\*/', '$1', $text);
+
+        // 移除链接标记,保留链接文本
+        $text = preg_replace('/\[([^\]]+)\]\([^)]+\)/', '$1', $text);
+
+        return $text;
+    }
+
+    public function renderTemplate(TemplateNode $template): string
+    {
+        if (!isset($this->templateTexts[$template->name])) {
+            // 未知模板,返回简化的文本表示
+            $text = $template->name;
+            if (!empty($template->parameters)) {
+                $paramTexts = [];
+                foreach ($template->parameters as $key => $value) {
+                    if (is_array($value)) {
+                        $nestedText = '';
+                        foreach ($value as $childNode) {
+                            $nestedText .= $this->renderNode($childNode);
+                        }
+                        $paramTexts[] = is_numeric($key) ? $nestedText : "$key: $nestedText";
+                    } else {
+                        $paramTexts[] = is_numeric($key) ? $value : "$key: $value";
+                    }
+                }
+                $text .= '(' . implode(', ', $paramTexts) . ')';
+            }
+            return $text;
+        }
+
+        $renderer = $this->templateTexts[$template->name];
+
+        // 处理参数中的嵌套内容
+        $processedParams = [];
+        foreach ($template->parameters as $key => $value) {
+            if (is_array($value)) {
+                $nestedText = '';
+                foreach ($value as $childNode) {
+                    $nestedText .= $this->renderNode($childNode);
+                }
+                $processedParams[$key] = $nestedText;
+            } else {
+                $processedParams[$key] = $value;
+            }
+        }
+
+        return $renderer($processedParams);
+    }
+
+    public function registerTemplate(string $name, callable $renderer): void
+    {
+        $this->templateTexts[$name] = $renderer;
+    }
+}

+ 34 - 0
api-v8/app/Services/Template/TemplateNode.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Services\Template;
+
+
+class TemplateNode extends ContentNode
+{
+    public string $name;
+    public array $parameters = [];
+    public array $children = [];
+    public string $raw = '';
+
+    public function __construct(string $name, array $parameters = [], array $children = [], string $raw = '', array $position = [])
+    {
+        $this->type = 'template';
+        $this->name = $name;
+        $this->parameters = $parameters;
+        $this->children = $children;
+        $this->raw = $raw;
+        $this->position = $position;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'type' => $this->type,
+            'name' => $this->name,
+            'parameters' => $this->parameters,
+            'children' => array_map(fn($child) => $child->toArray(), $this->children),
+            'raw' => $this->raw,
+            'position' => $this->position
+        ];
+    }
+}

+ 159 - 0
api-v8/app/Services/Template/TemplateParser.php

@@ -0,0 +1,159 @@
+<?php
+
+// ================== 主解析器 ==================
+
+namespace App\Services\Template;
+
+use App\Services\Template\Contracts\RendererInterface;
+use App\Services\Template\Contracts\ParserInterface;
+
+class TemplateParser implements ParserInterface
+{
+    private TemplateRegistry $registry;
+    private ParameterResolver $parameterResolver;
+
+    public function __construct()
+    {
+        $this->registry = new TemplateRegistry();
+        $this->parameterResolver = new ParameterResolver($this->registry);
+    }
+
+    public function parse(string $content): ParsedDocument
+    {
+        $tokenizer = new TemplateTokenizer($content);
+        $tokens = $tokenizer->tokenize();
+
+        $nodes = [];
+        $templatesUsed = [];
+
+        foreach ($tokens as $token) {
+            if ($token['type'] === 'text') {
+                $nodes[] = new TextNode($token['content'], $token['position']);
+            } elseif ($token['type'] === 'template') {
+                $templateNode = $this->parseTemplateContent($token);
+                if ($templateNode) {
+                    $nodes[] = $templateNode;
+                    $templatesUsed[] = $templateNode->name;
+                } else {
+                    // 解析失败,当作文本处理
+                    $nodes[] = new TextNode($token['raw'], $token['position']);
+                }
+            }
+        }
+
+        return new ParsedDocument($nodes, [
+            'templates_used' => array_unique($templatesUsed),
+            'total_templates' => count($templatesUsed)
+        ]);
+    }
+
+    private function parseTemplateContent(array $token): ?TemplateNode
+    {
+        $content = $token['content'];
+        $parts = $this->splitTemplateParts($content);
+
+        if (empty($parts)) {
+            return null;
+        }
+
+        $templateName = array_shift($parts);
+        $rawParams = $this->parseParameters($parts);
+
+        // 递归解析参数中的嵌套模板
+        $processedParams = [];
+        foreach ($rawParams as $key => $value) {
+            if (strpos($value, '{{') !== false) {
+                // 参数值包含模板,递归解析
+                $subDocument = $this->parse($value);
+                $processedParams[$key] = $subDocument->content;
+            } else {
+                $processedParams[$key] = $value;
+            }
+        }
+
+        $resolvedParams = $this->parameterResolver->resolveParameters($templateName, $processedParams);
+
+        return new TemplateNode(
+            $templateName,
+            $resolvedParams,
+            [],
+            $token['raw'],
+            $token['position']
+        );
+    }
+
+    private function splitTemplateParts(string $content): array
+    {
+        $parts = [];
+        $current = '';
+        $braceLevel = 0;
+        $inQuotes = false;
+        $quoteChar = '';
+
+        for ($i = 0; $i < strlen($content); $i++) {
+            $char = $content[$i];
+            $nextChar = $i + 1 < strlen($content) ? $content[$i + 1] : '';
+
+            if (!$inQuotes && ($char === '"' || $char === "'")) {
+                $inQuotes = true;
+                $quoteChar = $char;
+                $current .= $char;
+            } elseif ($inQuotes && $char === $quoteChar) {
+                $inQuotes = false;
+                $quoteChar = '';
+                $current .= $char;
+            } elseif (!$inQuotes && $char === '{' && $nextChar === '{') {
+                $braceLevel++;
+                $current .= '{{';
+                $i++; // 跳过下一个字符
+            } elseif (!$inQuotes && $char === '}' && $nextChar === '}') {
+                $braceLevel--;
+                $current .= '}}';
+                $i++; // 跳过下一个字符
+            } elseif (!$inQuotes && $char === '|' && $braceLevel === 0) {
+                $parts[] = trim($current);
+                $current = '';
+            } else {
+                $current .= $char;
+            }
+        }
+
+        if ($current !== '') {
+            $parts[] = trim($current);
+        }
+
+        return $parts;
+    }
+
+    private function parseParameters(array $parts): array
+    {
+        $params = [];
+        $positionalIndex = 0;
+
+        foreach ($parts as $part) {
+            if (strpos($part, '=') !== false && !$this->isInNestedTemplate($part)) {
+                // 命名参数
+                [$key, $value] = explode('=', $part, 2);
+                $params[trim($key)] = trim($value);
+            } else {
+                // 位置参数
+                $params[$positionalIndex] = trim($part);
+                $positionalIndex++;
+            }
+        }
+
+        return $params;
+    }
+
+    private function isInNestedTemplate(string $content): bool
+    {
+        $openBraces = substr_count($content, '{{');
+        $closeBraces = substr_count($content, '}}');
+        return $openBraces > 0 && $openBraces >= $closeBraces;
+    }
+
+    public function getRegistry(): TemplateRegistry
+    {
+        return $this->registry;
+    }
+}

+ 83 - 0
api-v8/app/Services/Template/TemplateRegistry.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Services\Template;
+
+
+// ================== 模板注册表 ==================
+
+class TemplateRegistry
+{
+    private array $templates = [];
+
+    public function __construct()
+    {
+        $this->loadDefaultTemplates();
+    }
+
+    private function loadDefaultTemplates(): void
+    {
+        $this->templates = [
+            'note' => [
+                'defaultParams' => ['text', 'type'],
+                'paramMapping' => [
+                    '0' => 'text',
+                    '1' => 'type'
+                ],
+                'defaultValues' => [
+                    'type' => 'info'
+                ],
+                'validation' => [
+                    'required' => ['text'],
+                    'optional' => ['type', 'title']
+                ]
+            ],
+            'info' => [
+                'defaultParams' => ['content'],
+                'paramMapping' => [
+                    '0' => 'content'
+                ],
+                'defaultValues' => [],
+                'validation' => [
+                    'required' => ['content'],
+                    'optional' => ['title']
+                ]
+            ],
+            'warning' => [
+                'defaultParams' => ['message'],
+                'paramMapping' => [
+                    '0' => 'message'
+                ],
+                'defaultValues' => [],
+                'validation' => [
+                    'required' => ['message'],
+                    'optional' => ['title']
+                ]
+            ]
+        ];
+    }
+
+    public function registerTemplate(string $name, array $config): void
+    {
+        $this->templates[$name] = $config;
+    }
+
+    public function getTemplate(string $name): ?array
+    {
+        return $this->templates[$name] ?? null;
+    }
+
+    public function hasTemplate(string $name): bool
+    {
+        return isset($this->templates[$name]);
+    }
+
+    public function getParamMapping(string $templateName): array
+    {
+        return $this->templates[$templateName]['paramMapping'] ?? [];
+    }
+
+    public function getDefaultValues(string $templateName): array
+    {
+        return $this->templates[$templateName]['defaultValues'] ?? [];
+    }
+}

+ 209 - 0
api-v8/app/Services/Template/TemplateService.php

@@ -0,0 +1,209 @@
+<?php
+
+namespace App\Services\Template;
+
+use App\Services\Template\TemplateParser;
+use App\Services\Template\Renderers\RendererFactory;
+use App\Services\Template\TemplateRegistry;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+
+class TemplateService
+{
+    private TemplateParser $parser;
+    private bool $cacheEnabled;
+    private int $cacheTtl;
+
+    public function __construct(bool $cacheEnabled = true, int $cacheTtl = 3600)
+    {
+        $this->parser = new TemplateParser();
+        $this->cacheEnabled = $cacheEnabled;
+        $this->cacheTtl = $cacheTtl;
+    }
+
+    /**
+     * 解析并渲染内容
+     */
+    public function parseAndRender(string $content, string $format = 'json'): array
+    {
+        try {
+            // 生成缓存键
+            $cacheKey = $this->generateCacheKey($content, $format);
+
+            if ($this->cacheEnabled && Cache::has($cacheKey)) {
+                return Cache::get($cacheKey);
+            }
+
+            // 解析内容
+            $document = $this->parser->parse($content);
+
+            // 渲染内容
+            $renderer = RendererFactory::create($format);
+            $renderedContent = $renderer->render($document);
+
+            $result = [
+                'data' => $format === 'json' ? json_decode($renderedContent, true) : $renderedContent,
+                'meta' => $document->meta
+            ];
+
+            // 缓存结果
+            if ($this->cacheEnabled) {
+                Cache::put($cacheKey, $result, $this->cacheTtl);
+            }
+
+            return $result;
+        } catch (\Exception $e) {
+            Log::error('Template parsing failed', [
+                'content' => substr($content, 0, 200),
+                'format' => $format,
+                'error' => $e->getMessage()
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 仅解析,不渲染
+     */
+    public function parse(string $content): ParsedDocument
+    {
+        return $this->parser->parse($content);
+    }
+
+    /**
+     * 仅渲染已解析的文档
+     */
+    public function render(ParsedDocument $document, string $format): string
+    {
+        $renderer = RendererFactory::create($format);
+        return $renderer->render($document);
+    }
+
+    /**
+     * 注册新模板
+     */
+    public function registerTemplate(string $name, array $config): void
+    {
+        $registry = $this->parser->getRegistry();
+        $registry->registerTemplate($name, $config);
+
+        // 清除相关缓存
+        if ($this->cacheEnabled) {
+            $this->clearTemplateCache($name);
+        }
+    }
+
+    /**
+     * 获取可用模板列表
+     */
+    public function getAvailableTemplates(): array
+    {
+        $registry = $this->parser->getRegistry();
+        $templates = [];
+
+        // 这里需要添加一个方法来获取所有模板
+        // 为了示例,我们返回一些基本信息
+        $basicTemplates = ['note', 'info', 'warning'];
+
+        foreach ($basicTemplates as $templateName) {
+            $template = $registry->getTemplate($templateName);
+            if ($template) {
+                $templates[$templateName] = [
+                    'name' => $templateName,
+                    'config' => $template,
+                    'example' => $this->generateTemplateExample($templateName, $template)
+                ];
+            }
+        }
+
+        return $templates;
+    }
+
+    /**
+     * 生成模板使用示例
+     */
+    private function generateTemplateExample(string $name, array $config): string
+    {
+        $params = [];
+        $defaultParams = $config['defaultParams'] ?? [];
+
+        foreach ($defaultParams as $index => $paramName) {
+            $params[] = $paramName . '=示例值' . ($index + 1);
+        }
+
+        return '{{' . $name . '|' . implode('|', $params) . '}}';
+    }
+
+    /**
+     * 生成缓存键
+     */
+    private function generateCacheKey(string $content, string $format): string
+    {
+        return 'template_' . $format . '_' . md5($content);
+    }
+
+    /**
+     * 清除模板相关缓存
+     */
+    private function clearTemplateCache(string $templateName): void
+    {
+        // 这里可以实现更精确的缓存清除逻辑
+        Cache::flush(); // 简单起见,清除所有缓存
+    }
+
+    /**
+     * 批量处理内容
+     */
+    public function batchProcess(array $contents, string $format = 'json'): array
+    {
+        $results = [];
+
+        foreach ($contents as $index => $content) {
+            try {
+                $results[$index] = $this->parseAndRender($content, $format);
+            } catch (\Exception $e) {
+                $results[$index] = [
+                    'success' => false,
+                    'error' => $e->getMessage()
+                ];
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * 验证模板语法
+     */
+    public function validateSyntax(string $content): array
+    {
+        $errors = [];
+
+        try {
+            $document = $this->parser->parse($content);
+
+            // 检查是否有未知模板
+            foreach ($document->content as $node) {
+                if ($node instanceof TemplateNode) {
+                    $registry = $this->parser->getRegistry();
+                    if (!$registry->hasTemplate($node->name)) {
+                        $errors[] = [
+                            'type' => 'unknown_template',
+                            'template' => $node->name,
+                            'position' => $node->position,
+                            'message' => "Unknown template: {$node->name}"
+                        ];
+                    }
+                }
+            }
+        } catch (\Exception $e) {
+            $errors[] = [
+                'type' => 'parse_error',
+                'message' => $e->getMessage()
+            ];
+        }
+
+        return $errors;
+    }
+}

+ 114 - 0
api-v8/app/Services/Template/TemplateTokenizer.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Services\Template;
+
+
+// ================== 词法分析器 ==================
+
+class TemplateTokenizer
+{
+    private string $content;
+    private int $position = 0;
+    private int $length;
+
+    public function __construct(string $content)
+    {
+        $this->content = $content;
+        $this->length = strlen($content);
+    }
+
+    public function tokenize(): array
+    {
+        $tokens = [];
+        $this->position = 0;
+
+        while ($this->position < $this->length) {
+            $token = $this->nextToken();
+            if ($token) {
+                $tokens[] = $token;
+            }
+        }
+
+        return $tokens;
+    }
+
+    private function nextToken(): ?array
+    {
+        // 寻找模板开始标记
+        $templateStart = strpos($this->content, '{{', $this->position);
+
+        if ($templateStart === false) {
+            // 没有更多模板,返回剩余文本
+            if ($this->position < $this->length) {
+                $text = substr($this->content, $this->position);
+                $this->position = $this->length;
+                return [
+                    'type' => 'text',
+                    'content' => $text,
+                    'position' => ['start' => $this->position - strlen($text), 'end' => $this->position]
+                ];
+            }
+            return null;
+        }
+
+        // 如果模板前有文本
+        if ($templateStart > $this->position) {
+            $text = substr($this->content, $this->position, $templateStart - $this->position);
+            $this->position = $templateStart;
+            return [
+                'type' => 'text',
+                'content' => $text,
+                'position' => ['start' => $this->position - strlen($text), 'end' => $this->position]
+            ];
+        }
+
+        // 解析模板
+        return $this->parseTemplate();
+    }
+
+    private function parseTemplate(): ?array
+    {
+        $start = $this->position;
+        $this->position += 2; // 跳过 '{{'
+
+        $braceCount = 1;
+        $templateContent = '';
+
+        while ($this->position < $this->length && $braceCount > 0) {
+            $char = $this->content[$this->position];
+            $nextChar = $this->position + 1 < $this->length ? $this->content[$this->position + 1] : '';
+
+            if ($char === '{' && $nextChar === '{') {
+                $braceCount++;
+                $templateContent .= '{{';
+                $this->position += 2;
+            } elseif ($char === '}' && $nextChar === '}') {
+                $braceCount--;
+                if ($braceCount > 0) {
+                    $templateContent .= '}}';
+                }
+                $this->position += 2;
+            } else {
+                $templateContent .= $char;
+                $this->position++;
+            }
+        }
+
+        if ($braceCount > 0) {
+            // 未闭合的模板,当作普通文本处理
+            $this->position = $start + 2;
+            return [
+                'type' => 'text',
+                'content' => '{{',
+                'position' => ['start' => $start, 'end' => $start + 2]
+            ];
+        }
+
+        return [
+            'type' => 'template',
+            'content' => trim($templateContent),
+            'raw' => '{{' . $templateContent . '}}',
+            'position' => ['start' => $start, 'end' => $this->position]
+        ];
+    }
+}

+ 25 - 0
api-v8/app/Services/Template/TextNode.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Services\Template;
+
+
+class TextNode extends ContentNode
+{
+    public string $content;
+
+    public function __construct(string $content, array $position = [])
+    {
+        $this->type = 'text';
+        $this->content = $content;
+        $this->position = $position;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'type' => $this->type,
+            'content' => $this->content,
+            'position' => $this->position
+        ];
+    }
+}

+ 1 - 0
api-v8/app/Services/TemplateRender.php

@@ -54,6 +54,7 @@ class TemplateRender
             'cf' => \App\Services\Templates\ConfidenceTemplate::class,
             'nissaya' => \App\Services\Templates\NissayaTemplate::class,
             'term' => \App\Services\Templates\TermTemplate::class,
+            'note' => \App\Services\Templates\NoteTemplate::class,
         ];
 
         if (!isset($templateMap[$this->templateName])) {

+ 115 - 0
api-v8/app/Services/Templates/NoteTemplate.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace App\Services\Templates;
+
+use App\Http\Api\MdRender;
+
+class NoteTemplate extends AbstractTemplate
+{
+    public function __construct() {}
+
+    public function render(): array
+    {
+        $note = $this->getParam("text", 1);
+        $trigger = $this->getParam("trigger", 2);
+        $props = ["note" => $note];
+        $innerString = "";
+        if (!empty($trigger)) {
+            $props["trigger"] = $trigger;
+            $innerString = $props["trigger"];
+        }
+        if ($this->options['format'] === 'unity') {
+            $props["note"] = MdRender::render(
+                $props["note"],
+                $this->options['channel_id'],
+                null,
+                'read',
+                'translation',
+                'markdown',
+                'unity'
+            );
+        }
+        $output = $note;
+        switch ($this->options['format']) {
+            case 'react':
+                $output = [
+                    'props' => base64_encode(\json_encode($props)),
+                    'html' => $innerString,
+                    'tag' => 'span',
+                    'tpl' => 'note',
+                ];
+                break;
+            case 'unity':
+                $output = [
+                    'props' => base64_encode(\json_encode($props)),
+                    'tpl' => 'note',
+                ];
+                break;
+            case 'html':
+                if (isset($GLOBALS['note_sn'])) {
+                    $GLOBALS['note_sn']++;
+                } else {
+                    $GLOBALS['note_sn'] = 1;
+                    $GLOBALS['note'] = array();
+                }
+                $GLOBALS['note'][] = [
+                    'sn' => $GLOBALS['note_sn'],
+                    'trigger' => $trigger,
+                    'content' => MdRender::render(
+                        $props["note"],
+                        $this->options['channel_id'],
+                        null,
+                        'read',
+                        'translation',
+                        'markdown',
+                        'html'
+                    ),
+                ];
+
+                $link = "<a href='#footnote-" . $GLOBALS['note_sn'] . "' name='note-" . $GLOBALS['note_sn'] . "'>";
+                if (empty($trigger)) {
+                    $output =  $link . "<sup>[" . $GLOBALS['note_sn'] . "]</sup></a>";
+                } else {
+                    $output = $link . $trigger . "</a>";
+                }
+                break;
+            case 'text':
+                $output = $trigger;
+                break;
+            case 'tex':
+                $output = $trigger;
+                break;
+            case 'simple':
+                $output = '';
+                break;
+            case 'markdown':
+                if (isset($GLOBALS['note_sn'])) {
+                    $GLOBALS['note_sn']++;
+                } else {
+                    $GLOBALS['note_sn'] = 1;
+                    $GLOBALS['note'] = array();
+                }
+                $content = MdRender::render(
+                    $props["note"],
+                    $this->options['channel_id'],
+                    null,
+                    'read',
+                    'translation',
+                    'markdown',
+                    'markdown'
+                );
+                $output = '[^' . $GLOBALS['note_sn'] . ']';
+                $GLOBALS['note'][] = [
+                    'sn' => $GLOBALS['note_sn'],
+                    'trigger' => $trigger,
+                    'content' => $content,
+                ];
+                //$output = '<footnote id="'.$GLOBALS['note_sn'].'">'.$content.'</footnote>';
+                break;
+            default:
+                $output = '';
+                break;
+        }
+        return $output;
+    }
+}

+ 8 - 8
api-v8/app/Services/Templates/TermTemplate.php

@@ -132,14 +132,14 @@ class TermTemplate extends AbstractTemplate
         if ($channel && !empty($channel)) {
             $channelId = $channel;
         } else {
-            if (count($this->channel_id) > 0) {
-                $channelId = $this->channel_id[0];
+            if (count($this->options['channel_id']) > 0) {
+                $channelId = $this->options['channel_id'][0];
             } else {
                 $channelId = null;
             }
         }
 
-        if (count($this->channelInfo) === 0) {
+        if (count($this->options['channelInfo']) === 0) {
             if (!empty($channel)) {
                 $channelInfo = Channel::where('uid', $channel)->first();
                 if (!$channelInfo) {
@@ -155,7 +155,7 @@ class TermTemplate extends AbstractTemplate
                 return $output;
             }
         } else {
-            $channelInfo = $this->channelInfo[0];
+            $channelInfo = $this->options['channelInfo'][0];
         }
 
         if (Str::isUuid($channelId)) {
@@ -165,8 +165,8 @@ class TermTemplate extends AbstractTemplate
             } else {
                 $langFamily = 'zh';
             }
-            $this->info("term:{$word} 先查属于这个channel 的", 'term');
-            $this->info('channel id' . $channelId, 'term');
+            Log::info("term:{$word} 先查属于这个channel 的", 'term');
+            Log::info('channel id' . $channelId, 'term');
             $table = DhammaTerm::where("word", $word)
                 ->where('channal', $channelId);
             if ($tag && !empty($tag)) {
@@ -179,7 +179,7 @@ class TermTemplate extends AbstractTemplate
             $tplParam = false;
             $lang = '';
             $langFamily = '';
-            $studioId = $this->studioId;
+            $studioId = $this->options['studioId'];
         }
 
         if (!$tplParam) {
@@ -252,7 +252,7 @@ class TermTemplate extends AbstractTemplate
             $output["channel"] = $tplParam->channal;
             if (!empty($tplParam->note)) {
                 $mdRender = new MdRender(['format' => $this->options['format']]);
-                $output['note'] = $mdRender->convert($tplParam->note, $this->channel_id);
+                $output['note'] = $mdRender->convert($tplParam->note, $this->options['channel_id']);
             }
             if (isset($isCommunity)) {
                 $output["isCommunity"] = true;

+ 49 - 0
api-v8/config/template.php

@@ -0,0 +1,49 @@
+<?php
+
+return [
+    /*
+    |--------------------------------------------------------------------------
+    | Template Cache Settings
+    |--------------------------------------------------------------------------
+    */
+    'cache_enabled' => env('TEMPLATE_CACHE_ENABLED', true),
+    'cache_ttl' => env('TEMPLATE_CACHE_TTL', 3600),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Templates
+    |--------------------------------------------------------------------------
+    */
+    'default_templates' => [
+        'note' => [
+            'defaultParams' => ['text', 'type'],
+            'paramMapping' => [
+                '0' => 'text',
+                '1' => 'type'
+            ],
+            'defaultValues' => [
+                'type' => 'info'
+            ],
+            'validation' => [
+                'required' => ['text'],
+                'optional' => ['type', 'title']
+            ]
+        ],
+        // 更多模板定义...
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Renderer Settings
+    |--------------------------------------------------------------------------
+    */
+    'supported_formats' => ['json', 'html', 'markdown', 'text'],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Parser Settings
+    |--------------------------------------------------------------------------
+    */
+    'max_nesting_level' => 10,
+    'max_template_size' => 10000, // characters
+];

+ 1 - 1
api-v8/storage/resources

@@ -1 +1 @@
-Subproject commit 8d97f4676c32d4b76f94a8180816eaa7f3319df0
+Subproject commit 5f87f0be2352900d3614dc9d9f765141bb5140ce

+ 4 - 2
open-ai-server/.gitignore

@@ -1,2 +1,4 @@
-/node_modules
-package-lock.json
+/node_modules/
+/dist/
+/package-lock.json
+/config.json

+ 5 - 7
open-ai-server/README.md

@@ -12,13 +12,11 @@ npm install
 
 1. **启动服务器**:
 
-```bash
-# 开发模式(自动重启)
-npm run dev
+`bash
+npm run build
+node dist/main-XXX.js config.json
 
-# 生产模式
-npm start
-```
+````
 
 ## 主要功能特点
 
@@ -48,7 +46,7 @@ npm start
     "stream": true // 可选,启用流式响应
   }
 }
-```
+````
 
 **非流式响应**:返回完整的 JSON 结果
 **流式响应**:返回 Server-Sent Events 格式的实时数据流

+ 1 - 0
open-ai-server/config.orig.json

@@ -0,0 +1 @@
+{ "port": 4000, "debug": true }

+ 2 - 0
open-ai-server/docker/.gitignore

@@ -0,0 +1,2 @@
+*.tar
+*.md5

+ 33 - 0
open-ai-server/docker/Dockerfile

@@ -0,0 +1,33 @@
+FROM ubuntu:latest
+LABEL maintainer="Kassapa"
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# https://nodejs.org/en/about/previous-releases
+ARG NODEJS_VERSION="jod"
+
+RUN apt update
+RUN apt -y upgrade
+RUN apt -y install lsb-release curl wget git vim locales locales-all tzdata build-essential
+RUN apt -y autoremove
+RUN apt -y clean
+
+RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
+RUN locale-gen
+RUN update-locale LANG=en_US.UTF-8
+RUN update-alternatives --set editor /usr/bin/vim.basic
+
+# https://github.com/nvm-sh/nvm
+ENV NVM_VERSION "v0.40.3"
+RUN git clone -b ${NVM_VERSION} https://github.com/nvm-sh/nvm.git $HOME/.nvm
+RUN echo 'export NVM_DIR="$HOME/.nvm"' >> $HOME/.bashrc
+RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $HOME/.bashrc
+RUN echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> $HOME/.bashrc
+RUN bash -c ". $HOME/.nvm/nvm.sh && nvm install --lts=${NODEJS_VERSION}"
+
+RUN echo "$(date -u +%4Y%m%d%H%M%S)" | tee /VERSION
+
+VOLUME /srv
+WORKDIR /srv
+
+CMD ["/bin/bash", "-l"]

+ 21 - 0
open-ai-server/docker/build.sh

@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+if [ "$#" -ne 1 ]; then
+    echo "USAGE: $0 NODEJS_VERSION"
+    exit 1
+fi
+
+export VERSION=$(date "+%4Y%m%d%H%M%S")
+export CODE="mint-nodejs-$1"
+export TAR="$CODE-$VERSION-$(uname -m)"
+
+podman pull ubuntu:latest
+podman build --build-arg NODEJS_VERSION=$1 -t $CODE .
+podman save --format=oci-archive -o $TAR.tar $CODE
+md5sum $TAR.tar >$TAR.md5
+
+echo "done($TAR.tar)."
+
+exit 0

+ 8 - 0
open-ai-server/docker/run.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ "$#" -ne 1 ]; then
+    echo "USAGE: $0 NODEJS_VERSION"
+    exit 1
+fi
+
+podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-nodejs-$1"

+ 11 - 6
open-ai-server/package.json

@@ -1,11 +1,13 @@
 {
   "name": "openai-proxy-api",
-  "version": "1.0.0",
+  "version": "2025.7.4",
   "description": "RESTful API proxy for OpenAI with streaming support",
-  "main": "server.js",
+  "main": "index.js",
+  "private": true,
   "scripts": {
-    "start": "node server.js",
-    "dev": "nodemon server.js",
+    "start": "node main.js",
+    "dev": "nodemon main.js",
+    "build": "NODE_ENV=production webpack --config webpack.config.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "keywords": [
@@ -19,12 +21,15 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
+    "cors": "^2.8.5",
     "express": "^4.18.2",
     "openai": "^4.20.1",
-    "cors": "^2.8.5"
+    "pino": "^9.7.0",
+    "pino-pretty": "^13.0.0"
   },
   "devDependencies": {
-    "nodemon": "^3.0.1"
+    "webpack": "^5.99.9",
+    "webpack-cli": "^6.0.1"
   },
   "engines": {
     "node": ">=16.0.0"

+ 22 - 0
open-ai-server/src/index.js

@@ -0,0 +1,22 @@
+import progress from "node:process";
+import fs from "node:fs";
+
+import logger from "./logger";
+import server from "./server";
+
+if (progress.argv.length !== 3) {
+  logger.error("USAGE: node main-XXX.js config.json");
+  process.exit(1);
+}
+
+const config_file = progress.argv[2];
+const config = JSON.parse(fs.readFileSync(config_file, "utf8"));
+logger.info(`load config from ${config_file}`);
+logger.debug("run on debug mode");
+
+const port = config["port"];
+server.listen(port, () => {
+  logger.info("Server is running on port %d", port);
+  logger.info("Health check: http://0.0.0.0:%d/health", port);
+  logger.info("API endpoint: http://0.0.0.0:%d/api/openai", port);
+});

+ 14 - 0
open-ai-server/src/logger.js

@@ -0,0 +1,14 @@
+import pino from "pino";
+import pretty from "pino-pretty";
+
+const stream = pretty({
+  colorize: true,
+});
+const logger = pino(
+  {
+    level: "debug",
+  },
+  stream
+);
+
+export default logger;

+ 10 - 16
open-ai-server/server.js → open-ai-server/src/server.js

@@ -1,9 +1,10 @@
-const express = require("express");
-const OpenAI = require("openai");
-const cors = require("cors");
+import express from "express";
+import OpenAI from "openai";
+import cors from "cors";
+
+import logger from "./logger";
 
 const app = express();
-const PORT = process.env.PORT || 3000;
 
 // 中间件
 app.use(cors());
@@ -13,7 +14,7 @@ app.use(express.json());
 app.post("/api/openai", async (req, res) => {
   try {
     const { open_ai_url, api_key, payload } = req.body;
-    console.debug("request", open_ai_url);
+    logger.debug("request %s", open_ai_url);
     // 验证必需的参数
     if (!open_ai_url || !api_key || !payload) {
       return res.status(400).json({
@@ -42,7 +43,7 @@ app.post("/api/openai", async (req, res) => {
           ...payload,
           stream: true,
         });
-        console.info("waiting response");
+        logger.info("waiting response");
         for await (const chunk of stream) {
           const data = JSON.stringify(chunk);
           res.write(`data: ${data}\n\n`);
@@ -51,7 +52,7 @@ app.post("/api/openai", async (req, res) => {
         res.write("data: [DONE]\n\n");
         res.end();
       } catch (streamError) {
-        console.error("Streaming error:", streamError);
+        logger.error("Streaming error: %s", streamError);
         res.write(
           `data: ${JSON.stringify({ error: streamError.message })}\n\n`
         );
@@ -64,7 +65,7 @@ app.post("/api/openai", async (req, res) => {
       res.json(completion);
     }
   } catch (error) {
-    console.error("API Error:", error);
+    logger.error("API Error: %s", error);
 
     // 处理不同类型的错误
     if (error.status) {
@@ -86,11 +87,4 @@ app.get("/health", (req, res) => {
   res.json({ status: "OK", timestamp: new Date().toISOString() });
 });
 
-// 启动服务器
-app.listen(PORT, () => {
-  console.log(`Server is running on port ${PORT}`);
-  console.log(`Health check: http://localhost:${PORT}/health`);
-  console.log(`API endpoint: http://localhost:${PORT}/api/openai`);
-});
-
-module.exports = app;
+export default app;

+ 12 - 0
open-ai-server/webpack.config.js

@@ -0,0 +1,12 @@
+const path = require("path");
+
+module.exports = {
+  entry: "./src/index.js",
+  output: {
+    path: path.resolve(__dirname, "dist"),
+    filename: "[name].[contenthash].js",
+    clean: true,
+  },
+  optimization: { runtimeChunk: "single" },
+  target: "node",
+};