entari-plugin-hyw 2.2.5__py3-none-any.whl → 3.5.0rc6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. entari_plugin_hyw/__init__.py +371 -315
  2. entari_plugin_hyw/assets/card-dist/index.html +396 -0
  3. entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +1 -0
  4. entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
  5. entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
  6. entari_plugin_hyw/assets/card-dist/logos/gemini.svg +1 -0
  7. entari_plugin_hyw/assets/card-dist/logos/google.svg +1 -0
  8. entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
  9. entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
  10. entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +15 -0
  11. entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
  12. entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
  13. entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
  14. entari_plugin_hyw/assets/card-dist/logos/openai.svg +1 -0
  15. entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
  16. entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +24 -0
  17. entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
  18. entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
  19. entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
  20. entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
  21. entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
  22. entari_plugin_hyw/assets/icon/anthropic.svg +1 -0
  23. entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
  24. entari_plugin_hyw/assets/icon/deepseek.png +0 -0
  25. entari_plugin_hyw/assets/icon/gemini.svg +1 -0
  26. entari_plugin_hyw/assets/icon/google.svg +1 -0
  27. entari_plugin_hyw/assets/icon/grok.png +0 -0
  28. entari_plugin_hyw/assets/icon/huggingface.png +0 -0
  29. entari_plugin_hyw/assets/icon/microsoft.svg +15 -0
  30. entari_plugin_hyw/assets/icon/minimax.png +0 -0
  31. entari_plugin_hyw/assets/icon/mistral.png +0 -0
  32. entari_plugin_hyw/assets/icon/nvida.png +0 -0
  33. entari_plugin_hyw/assets/icon/openai.svg +1 -0
  34. entari_plugin_hyw/assets/icon/openrouter.png +0 -0
  35. entari_plugin_hyw/assets/icon/perplexity.svg +24 -0
  36. entari_plugin_hyw/assets/icon/qwen.png +0 -0
  37. entari_plugin_hyw/assets/icon/xai.png +0 -0
  38. entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
  39. entari_plugin_hyw/assets/icon/zai.png +0 -0
  40. entari_plugin_hyw/card-ui/.gitignore +24 -0
  41. entari_plugin_hyw/card-ui/README.md +5 -0
  42. entari_plugin_hyw/card-ui/index.html +16 -0
  43. entari_plugin_hyw/card-ui/package-lock.json +2342 -0
  44. entari_plugin_hyw/card-ui/package.json +31 -0
  45. entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
  46. entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
  47. entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
  48. entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
  49. entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
  50. entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
  51. entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
  52. entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
  53. entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
  54. entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
  55. entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
  56. entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
  57. entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
  58. entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
  59. entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
  60. entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
  61. entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
  62. entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
  63. entari_plugin_hyw/card-ui/public/vite.svg +1 -0
  64. entari_plugin_hyw/card-ui/src/App.vue +412 -0
  65. entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
  66. entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
  67. entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +386 -0
  68. entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
  69. entari_plugin_hyw/card-ui/src/components/StageCard.vue +237 -0
  70. entari_plugin_hyw/card-ui/src/main.ts +5 -0
  71. entari_plugin_hyw/card-ui/src/style.css +29 -0
  72. entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
  73. entari_plugin_hyw/card-ui/src/types.ts +52 -0
  74. entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
  75. entari_plugin_hyw/card-ui/tsconfig.json +7 -0
  76. entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
  77. entari_plugin_hyw/card-ui/vite.config.ts +16 -0
  78. entari_plugin_hyw/history.py +170 -0
  79. entari_plugin_hyw/image_cache.py +274 -0
  80. entari_plugin_hyw/misc.py +128 -0
  81. entari_plugin_hyw/pipeline.py +1338 -0
  82. entari_plugin_hyw/prompts.py +108 -0
  83. entari_plugin_hyw/render_vue.py +314 -0
  84. entari_plugin_hyw/search.py +696 -0
  85. entari_plugin_hyw-3.5.0rc6.dist-info/METADATA +116 -0
  86. entari_plugin_hyw-3.5.0rc6.dist-info/RECORD +88 -0
  87. entari_plugin_hyw/hyw_core.py +0 -555
  88. entari_plugin_hyw-2.2.5.dist-info/METADATA +0 -135
  89. entari_plugin_hyw-2.2.5.dist-info/RECORD +0 -6
  90. {entari_plugin_hyw-2.2.5.dist-info → entari_plugin_hyw-3.5.0rc6.dist-info}/WHEEL +0 -0
  91. {entari_plugin_hyw-2.2.5.dist-info → entari_plugin_hyw-3.5.0rc6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,29 @@
1
+ @import "tailwindcss";
2
+ @plugin "daisyui";
3
+ @plugin "@tailwindcss/typography";
4
+
5
+ /* Custom styles */
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
+ }
9
+
10
+ /* Premium reading color scheme - applies to all prose elements */
11
+ .prose {
12
+ color: var(--text-body, #3a3a3c) !important;
13
+ }
14
+
15
+ .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
16
+ color: var(--text-primary, #2c2c2e) !important;
17
+ }
18
+
19
+ .prose p, .prose li, .prose td, .prose th {
20
+ color: var(--text-body, #3a3a3c) !important;
21
+ }
22
+
23
+ .prose strong, .prose b {
24
+ color: var(--text-primary, #2c2c2e) !important;
25
+ }
26
+
27
+ .prose code {
28
+ color: var(--text-body, #3a3a3c) !important;
29
+ }
@@ -0,0 +1,103 @@
1
+
2
+ const stripPrefixBeforeH1 = (text) => {
3
+ const h1Match = text.match(/^#\s+/m)
4
+ if (h1Match && h1Match.index !== undefined) {
5
+ return text.substring(h1Match.index)
6
+ }
7
+ return text
8
+ }
9
+
10
+ const dedent = (text) => {
11
+ const lines = text.split('\n')
12
+ // Find minimum indentation of non-empty lines
13
+ let minIndent = Infinity
14
+ for (const line of lines) {
15
+ if (line.trim().length === 0) continue
16
+ const leadingSpace = line.match(/^\s*/)?.[0].length || 0
17
+ if (leadingSpace < minIndent) minIndent = leadingSpace
18
+ }
19
+
20
+ if (minIndent === Infinity || minIndent === 0) return text
21
+
22
+ return lines.map(line => {
23
+ if (line.trim().length === 0) return ''
24
+ return line.substring(minIndent)
25
+ }).join('\n')
26
+ }
27
+
28
+ const parse = (rawMd) => {
29
+ if (!rawMd) return []
30
+
31
+ const md = stripPrefixBeforeH1(rawMd)
32
+
33
+ let content = md.replace(/^#\s+.+$/m, '')
34
+ content = content.replace(/(?:^|\n)\s*(?:#{1,3}|\*\*)\s*(?:References|Citations|Sources)[\s\S]*$/i, '')
35
+ content = content.trim()
36
+
37
+ const sections = []
38
+
39
+ const combinedRegex = /(```[\s\S]*?```|((?:^|\n)\|[^\n]*\|(?:\n\|[^\n]*\|)*)|<summary>[\s\S]*?<\/summary>)/
40
+
41
+ let remaining = content
42
+
43
+ while (remaining) {
44
+ const match = remaining.match(combinedRegex)
45
+ if (!match) {
46
+ if (remaining.trim()) {
47
+ sections.push({ type: 'markdown', content: remaining.trim() })
48
+ }
49
+ break
50
+ }
51
+
52
+ const index = match.index
53
+ const matchedStr = match[0]
54
+ const preText = remaining.substring(0, index)
55
+
56
+ if (preText.trim()) {
57
+ sections.push({ type: 'markdown', content: preText.trim() })
58
+ }
59
+
60
+ const isCode = matchedStr.startsWith('```')
61
+ const isSummary = matchedStr.startsWith('<summary>')
62
+ const isTable = !isCode && !isSummary && matchedStr.trim().startsWith('|')
63
+
64
+ if (isCode || isTable || isSummary) {
65
+ let language = ''
66
+ let content = matchedStr.trim()
67
+
68
+ if (isCode) {
69
+ const match = matchedStr.match(/^```(\w+)/)
70
+ if (match && match[1]) language = match[1]
71
+ } else if (isSummary) {
72
+ content = content.replace(/^<summary>/, '').replace(/<\/summary>$/, '')
73
+ content = dedent(content)
74
+ }
75
+
76
+ sections.push({
77
+ type: 'card',
78
+ title: isCode ? 'Code' : (isSummary ? 'Summary' : 'Table'),
79
+ content: content,
80
+ contentType: isCode ? 'code' : (isSummary ? 'summary' : 'table'),
81
+ language: language
82
+ })
83
+ } else {
84
+ sections.push({ type: 'markdown', content: matchedStr })
85
+ }
86
+
87
+ remaining = remaining.substring(index + matchedStr.length)
88
+ }
89
+
90
+ return sections
91
+ }
92
+
93
+ const test1 = `
94
+ # Title
95
+
96
+ <summary>
97
+ Indented text.
98
+ It might become code block.
99
+ </summary>
100
+ `
101
+
102
+ console.log("\n--- Test 2 (After Fix) ---")
103
+ console.log(JSON.stringify(parse(test1), null, 2))
@@ -0,0 +1,52 @@
1
+ // Type definitions for render data
2
+ // Python only passes raw data, all processing happens in frontend
3
+
4
+ export interface Stage {
5
+ name: string
6
+ model: string
7
+ provider: string
8
+ icon_name?: string // Icon identifier (e.g., "google", "openai")
9
+ time: number // Time in seconds (raw number)
10
+ cost: number // Cost in dollars (raw number)
11
+ references?: Reference[]
12
+ image_references?: ImageReference[]
13
+ crawled_pages?: CrawledPage[]
14
+ }
15
+
16
+ export interface Reference {
17
+ title: string
18
+ url: string
19
+ }
20
+
21
+ export interface ImageReference {
22
+ title: string
23
+ url: string
24
+ thumbnail?: string
25
+ }
26
+
27
+ export interface CrawledPage {
28
+ title: string
29
+ url: string
30
+ }
31
+
32
+ export interface Stats {
33
+ total_time?: number
34
+ vision_duration?: number
35
+ }
36
+
37
+ export interface Flags {
38
+ has_vision: boolean
39
+ has_search: boolean
40
+ }
41
+
42
+ // Raw data from Python - minimal processing
43
+ export interface RenderData {
44
+ markdown: string // Raw markdown content
45
+ stages: Stage[]
46
+ references: Reference[] // All references for citation
47
+ page_references: Reference[]
48
+ image_references: ImageReference[]
49
+ stats: Stats
50
+ total_time: number
51
+ theme_color?: string // Configurable theme color (hex)
52
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "types": ["vite/client"],
6
+
7
+ /* Linting */
8
+ "strict": true,
9
+ "noUnusedLocals": true,
10
+ "noUnusedParameters": true,
11
+ "erasableSyntaxOnly": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "noUncheckedSideEffectImports": true
14
+ },
15
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
16
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import { viteSingleFile } from 'vite-plugin-singlefile'
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ vue(),
9
+ tailwindcss(),
10
+ viteSingleFile(),
11
+ ],
12
+ build: {
13
+ outDir: '../assets/card-dist',
14
+ emptyOutDir: true,
15
+ },
16
+ })
@@ -0,0 +1,170 @@
1
+ import random
2
+ import string
3
+ from typing import Dict, List, Any, Optional
4
+
5
+ class HistoryManager:
6
+ def __init__(self):
7
+ self._history: Dict[str, List[Dict[str, Any]]] = {}
8
+ self._metadata: Dict[str, Dict[str, Any]] = {}
9
+ self._mapping: Dict[str, str] = {}
10
+ self._context_latest: Dict[str, str] = {}
11
+
12
+ # New: Short code management
13
+ self._short_codes: Dict[str, str] = {} # code -> key
14
+ self._key_to_code: Dict[str, str] = {} # key -> code
15
+ self._context_history: Dict[str, List[str]] = {} # context_id -> list of keys
16
+
17
+ def is_bot_message(self, message_id: str) -> bool:
18
+ """Check if the message ID belongs to a bot message"""
19
+ return message_id in self._history
20
+
21
+ def generate_short_code(self) -> str:
22
+ """Generate a unique 4-digit hex code"""
23
+ while True:
24
+ code = ''.join(random.choices(string.hexdigits.lower(), k=4))
25
+ if code not in self._short_codes:
26
+ return code
27
+
28
+ def get_conversation_id(self, message_id: str) -> Optional[str]:
29
+ return self._mapping.get(message_id)
30
+
31
+ def get_key_by_code(self, code: str) -> Optional[str]:
32
+ return self._short_codes.get(code.lower())
33
+
34
+ def get_code_by_key(self, key: str) -> Optional[str]:
35
+ return self._key_to_code.get(key)
36
+
37
+ def get_history(self, key: str) -> List[Dict[str, Any]]:
38
+ return self._history.get(key, [])
39
+
40
+ def get_metadata(self, key: str) -> Dict[str, Any]:
41
+ return self._metadata.get(key, {})
42
+
43
+ def get_latest_from_context(self, context_id: str) -> Optional[str]:
44
+ return self._context_latest.get(context_id)
45
+
46
+ def list_by_context(self, context_id: str, limit: int = 10) -> List[str]:
47
+ """Return list of keys for a context, most recent first"""
48
+ keys = self._context_history.get(context_id, [])
49
+ return keys[-limit:][::-1]
50
+
51
+ def remember(self, message_id: Optional[str], history: List[Dict[str, Any]], related_ids: List[str], metadata: Optional[Dict[str, Any]] = None, context_id: Optional[str] = None, code: Optional[str] = None):
52
+ if not message_id:
53
+ return
54
+
55
+ key = message_id
56
+ self._history[key] = history
57
+ if metadata:
58
+ self._metadata[key] = metadata
59
+
60
+ self._mapping[key] = key
61
+ for rid in related_ids:
62
+ if rid:
63
+ self._mapping[rid] = key
64
+
65
+ # Generate or use provided short code
66
+ if key not in self._key_to_code:
67
+ if not code:
68
+ code = self.generate_short_code()
69
+ self._short_codes[code] = key
70
+ self._key_to_code[key] = code
71
+
72
+ if context_id:
73
+ self._context_latest[context_id] = key
74
+ if context_id not in self._context_history:
75
+ self._context_history[context_id] = []
76
+ self._context_history[context_id].append(key)
77
+
78
+ def save_to_disk(self, key: str, save_dir: str = "data/conversations"):
79
+ """Save conversation history to disk"""
80
+ import os
81
+ import time
82
+ import re
83
+
84
+ if key not in self._history:
85
+ return
86
+
87
+ try:
88
+ os.makedirs(save_dir, exist_ok=True)
89
+
90
+ # Extract user's first message (question) for filename
91
+ user_question = ""
92
+ for msg in self._history[key]:
93
+ if msg.get("role") == "user":
94
+ content = msg.get("content", "")
95
+ # Handle content that might be a list (multimodal)
96
+ if isinstance(content, list):
97
+ for item in content:
98
+ if isinstance(item, dict) and item.get("type") == "text":
99
+ user_question = item.get("text", "")
100
+ break
101
+ else:
102
+ user_question = str(content)
103
+ break
104
+
105
+ # Clean and truncate question for filename (10 chars)
106
+ question_part = re.sub(r'[\\/:*?"<>|\n\r\t]', '', user_question)[:10].strip()
107
+ if not question_part:
108
+ question_part = "conversation"
109
+
110
+ # Format: YYYYMMDD_HHMMSS_question.md
111
+ time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
112
+ filename = f"{save_dir}/{time_str}_{question_part}.md"
113
+
114
+ # Formatter
115
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
116
+ meta = self._metadata.get(key, {})
117
+ model_name = meta.get("model", "unknown")
118
+ code = self._key_to_code.get(key, "N/A")
119
+
120
+ md_content = f"# Conversation Log: {key}\n\n"
121
+ md_content += f"**Time**: {timestamp}\n"
122
+ md_content += f"**Code**: {code}\n"
123
+ md_content += f"**Model**: {model_name}\n"
124
+ md_content += f"**Metadata**: {meta}\n\n"
125
+
126
+ trace_md = meta.get("trace_markdown") if isinstance(meta, dict) else None
127
+ if trace_md:
128
+ md_content += "## Trace\n\n"
129
+ md_content += f"{trace_md}\n\n"
130
+
131
+ md_content += "## History\n\n"
132
+
133
+ for msg in self._history[key]:
134
+ role = msg.get("role", "unknown").upper()
135
+ content = msg.get("content", "")
136
+
137
+ md_content += f"### {role}\n\n"
138
+
139
+ tool_calls = msg.get("tool_calls")
140
+ if tool_calls:
141
+ import json
142
+ try:
143
+ tc_str = json.dumps(tool_calls, ensure_ascii=False, indent=2)
144
+ except:
145
+ tc_str = str(tool_calls)
146
+ md_content += f"**Tool Calls**:\n```json\n{tc_str}\n```\n\n"
147
+
148
+ # Special handling for tool outputs or complex content
149
+ if role == "TOOL":
150
+ # Try to pretty print if it's JSON
151
+ try:
152
+ import json
153
+ # Content might be a JSON string already
154
+ parsed_content = json.loads(content)
155
+ pretty_content = json.dumps(parsed_content, ensure_ascii=False, indent=2)
156
+ md_content += f"**Output**:\n```json\n{pretty_content}\n```\n\n"
157
+ except:
158
+ md_content += f"**Output**:\n```text\n{content}\n```\n\n"
159
+ else:
160
+ if content:
161
+ md_content += f"{content}\n\n"
162
+
163
+ md_content += "---\n\n"
164
+
165
+ with open(filename, "w", encoding="utf-8") as f:
166
+ f.write(md_content)
167
+
168
+ except Exception as e:
169
+ # We can't log easily here without importing logger, but it's fine
170
+ print(f"Failed to save conversation: {e}")