npcsh 1.1.5__py3-none-any.whl → 1.1.6__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.
- npcsh/_state.py +438 -319
- npcsh/npc_team/jinxs/code/sh.jinx +0 -1
- npcsh/npc_team/jinxs/code/sql.jinx +1 -3
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +33 -38
- npcsh/npc_team/jinxs/utils/ots.jinx +34 -65
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +33 -45
- npcsh/routes.py +32 -14
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npc-studio.jinx +33 -38
- npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sh.jinx +0 -1
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sql.jinx +1 -3
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/vixynt.jinx +33 -45
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/RECORD +65 -73
- npcsh/npc_team/jinxs/utils/search/brainblast.jinx +0 -51
- npcsh/npc_team/jinxs/utils/search/kg_search.jinx +0 -43
- npcsh/npc_team/jinxs/utils/search/memory_search.jinx +0 -36
- npcsh/npc_team/jinxs/utils/search/rag.jinx +0 -70
- npcsh/npc_team/jinxs/utils/search/search.jinx +0 -192
- npcsh-1.1.5.data/data/npcsh/npc_team/brainblast.jinx +0 -51
- npcsh-1.1.5.data/data/npcsh/npc_team/kg_search.jinx +0 -43
- npcsh-1.1.5.data/data/npcsh/npc_team/memory_search.jinx +0 -36
- npcsh-1.1.5.data/data/npcsh/npc_team/ots.jinx +0 -92
- npcsh-1.1.5.data/data/npcsh/npc_team/rag.jinx +0 -70
- npcsh-1.1.5.data/data/npcsh/npc_team/search.jinx +0 -192
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/breathe.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/flush.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plan.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/wander.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
jinx_name: sql_executor
|
|
2
2
|
description: Execute queries on the ~/npcsh_history.db to pull data. The database
|
|
3
3
|
contains only information about conversations and other user-provided data. It does
|
|
4
|
-
not store any information about individual files. Avoid using percent signs unless absolutely necessary.
|
|
4
|
+
not store any information about individual files. Avoid using percent signs unless absolutely necessary.
|
|
5
5
|
inputs:
|
|
6
6
|
- sql_query
|
|
7
|
-
- user_query
|
|
8
|
-
- interpret: true
|
|
9
7
|
steps:
|
|
10
8
|
- engine: python
|
|
11
9
|
code: |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
jinx_name: "npc-studio"
|
|
2
2
|
description: "Start npc studio"
|
|
3
3
|
inputs:
|
|
4
|
-
- user_command: ""
|
|
4
|
+
- user_command: ""
|
|
5
5
|
steps:
|
|
6
6
|
- name: "launch_npc_studio"
|
|
7
7
|
engine: "python"
|
|
@@ -13,9 +13,12 @@ steps:
|
|
|
13
13
|
import traceback
|
|
14
14
|
|
|
15
15
|
NPC_STUDIO_DIR = Path.home() / ".npcsh" / "npc-studio"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
|
|
17
|
+
user_command = context.get('user_command')
|
|
18
|
+
output_messages = context.get('messages', [])
|
|
19
|
+
output_result = ""
|
|
20
|
+
|
|
21
|
+
try:
|
|
19
22
|
if not NPC_STUDIO_DIR.exists():
|
|
20
23
|
os.makedirs(NPC_STUDIO_DIR.parent, exist_ok=True)
|
|
21
24
|
subprocess.check_call([
|
|
@@ -28,55 +31,47 @@ steps:
|
|
|
28
31
|
["git", "pull"],
|
|
29
32
|
cwd=NPC_STUDIO_DIR
|
|
30
33
|
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
subprocess.check_call(
|
|
36
|
+
["npm", "install"],
|
|
37
|
+
cwd=NPC_STUDIO_DIR
|
|
38
|
+
)
|
|
39
|
+
|
|
36
40
|
req_file = NPC_STUDIO_DIR / "requirements.txt"
|
|
37
41
|
if req_file.exists():
|
|
38
|
-
subprocess.check_call([
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
subprocess.check_call([
|
|
43
|
+
sys.executable,
|
|
44
|
+
"-m",
|
|
45
|
+
"pip",
|
|
46
|
+
"install",
|
|
47
|
+
"-r",
|
|
48
|
+
str(req_file)
|
|
49
|
+
])
|
|
50
|
+
|
|
48
51
|
backend = subprocess.Popen(
|
|
49
52
|
[sys.executable, "npc_studio_serve.py"],
|
|
50
|
-
cwd=NPC_STUDIO_DIR
|
|
51
|
-
shell = False
|
|
53
|
+
cwd=NPC_STUDIO_DIR
|
|
52
54
|
)
|
|
53
55
|
|
|
54
|
-
# npm run dev is typically for the frontend development server
|
|
55
56
|
dev_server = subprocess.Popen(
|
|
56
57
|
["npm", "run", "dev"],
|
|
57
|
-
cwd=NPC_STUDIO_DIR
|
|
58
|
-
shell=False
|
|
58
|
+
cwd=NPC_STUDIO_DIR
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
# npm start is typically for electron or other packaged frontend
|
|
62
61
|
frontend = subprocess.Popen(
|
|
63
62
|
["npm", "start"],
|
|
64
|
-
cwd=NPC_STUDIO_DIR
|
|
65
|
-
|
|
63
|
+
cwd=NPC_STUDIO_DIR
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
output_result = (
|
|
67
|
+
f"NPC Studio started!\n"
|
|
68
|
+
f"Backend PID={backend.pid}, "
|
|
69
|
+
f"Dev Server PID={dev_server.pid}, "
|
|
70
|
+
f"Frontend PID={frontend.pid}"
|
|
66
71
|
)
|
|
67
|
-
|
|
68
|
-
return backend, dev_server, frontend
|
|
69
|
-
|
|
70
|
-
user_command = context.get('user_command')
|
|
71
|
-
output_messages = context.get('messages', [])
|
|
72
|
-
output_result = ""
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
backend, electron, frontend = launch_npc_studio(user_command or None)
|
|
76
|
-
output_result = f"NPC Studio started!\nBackend PID={backend.pid}, Electron PID={electron.pid} Frontend PID={frontend.pid}"
|
|
77
72
|
except Exception as e:
|
|
78
73
|
traceback.print_exc()
|
|
79
74
|
output_result = f"Failed to start NPC Studio: {e}"
|
|
80
75
|
|
|
81
76
|
context['output'] = output_result
|
|
82
|
-
context['messages'] = output_messages
|
|
77
|
+
context['messages'] = output_messages
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
jinx_name: "ots"
|
|
2
|
-
description: "Take screenshot and analyze with vision model"
|
|
2
|
+
description: "Take screenshot and analyze with vision model. Usage: /ots <prompt>"
|
|
3
3
|
inputs:
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
- vmodel: ""
|
|
7
|
-
- vprovider: ""
|
|
8
|
-
- stream: False # Whether to stream the output from the LLM.
|
|
9
|
-
- api_url: "" # API URL for the LLM.
|
|
10
|
-
- api_key: "" # API key for the LLM.
|
|
4
|
+
- prompt
|
|
5
|
+
- image_paths_args: ""
|
|
6
|
+
- vmodel: ""
|
|
7
|
+
- vprovider: ""
|
|
11
8
|
steps:
|
|
12
9
|
- name: "analyze_screenshot_or_image"
|
|
13
10
|
engine: "python"
|
|
@@ -16,77 +13,49 @@ steps:
|
|
|
16
13
|
import traceback
|
|
17
14
|
from npcpy.llm_funcs import get_llm_response
|
|
18
15
|
from npcpy.data.image import capture_screenshot
|
|
19
|
-
# Assuming NPCSH_VISION_MODEL and NPCSH_VISION_PROVIDER are accessible through _state or defaults
|
|
20
|
-
# For simplicity in Jinx, we'll use fallbacks or assume context will provide
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
vision_model = context.get('vmodel')
|
|
25
|
-
vision_provider = context.get('vprovider')
|
|
26
|
-
stream_output = context.get('stream')
|
|
27
|
-
api_url = context.get('api_url')
|
|
28
|
-
api_key = context.get('api_key')
|
|
17
|
+
user_prompt = context.get('prompt') or ""
|
|
18
|
+
image_paths_args_str = context.get('image_paths_args') or ""
|
|
19
|
+
vision_model = context.get('vmodel') or ""
|
|
20
|
+
vision_provider = context.get('vprovider') or ""
|
|
21
|
+
stream_output = context.get('stream') or False
|
|
22
|
+
api_url = context.get('api_url') or ""
|
|
23
|
+
api_key = context.get('api_key') or ""
|
|
29
24
|
output_messages = context.get('messages', [])
|
|
30
25
|
current_npc = context.get('npc')
|
|
31
26
|
|
|
32
27
|
image_paths = []
|
|
33
|
-
if image_paths_args_str
|
|
28
|
+
if image_paths_args_str.strip():
|
|
34
29
|
for img_path_arg in image_paths_args_str.split(','):
|
|
35
30
|
full_path = os.path.abspath(os.path.expanduser(img_path_arg.strip()))
|
|
36
31
|
if os.path.exists(full_path):
|
|
37
32
|
image_paths.append(full_path)
|
|
38
|
-
else:
|
|
39
|
-
context['output'] = f"Error: Image file not found at {full_path}"
|
|
40
|
-
context['messages'] = output_messages
|
|
41
|
-
exit()
|
|
42
33
|
|
|
43
34
|
if not image_paths:
|
|
44
35
|
screenshot_info = capture_screenshot(full=False)
|
|
45
36
|
if screenshot_info and "file_path" in screenshot_info:
|
|
46
37
|
image_paths.append(screenshot_info["file_path"])
|
|
47
|
-
print(f"Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
48
|
-
else:
|
|
49
|
-
context['output'] = "Error: Failed to capture screenshot."
|
|
50
|
-
context['messages'] = output_messages
|
|
51
|
-
exit()
|
|
38
|
+
print(f"📸 Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
52
39
|
|
|
53
|
-
if not
|
|
54
|
-
|
|
55
|
-
context['messages'] = output_messages
|
|
56
|
-
exit()
|
|
57
|
-
|
|
58
|
-
if not user_prompt or not user_prompt.strip():
|
|
59
|
-
# In a non-interactive Jinx, a default prompt is better than waiting for input
|
|
60
|
-
user_prompt = "Describe the image(s)."
|
|
61
|
-
|
|
62
|
-
# Fallback for model/provider if not explicitly set in Jinx inputs
|
|
63
|
-
if not vision_model and current_npc and current_npc.model:
|
|
64
|
-
vision_model = current_npc.model
|
|
65
|
-
if not vision_provider and current_npc and current_npc.provider:
|
|
66
|
-
vision_provider = current_npc.provider
|
|
40
|
+
if not vision_model:
|
|
41
|
+
vision_model = getattr(current_npc, 'model', 'gpt-4o-mini')
|
|
67
42
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if not vision_provider: vision_provider = "gemini" # Example default
|
|
43
|
+
if not vision_provider:
|
|
44
|
+
vision_provider = getattr(current_npc, 'provider', 'openai')
|
|
71
45
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
except Exception as e:
|
|
90
|
-
traceback.print_exc()
|
|
91
|
-
context['output'] = f"Error during /ots command: {e}"
|
|
92
|
-
context['messages'] = output_messages
|
|
46
|
+
response_data = get_llm_response(
|
|
47
|
+
prompt=user_prompt,
|
|
48
|
+
model=vision_model,
|
|
49
|
+
provider=vision_provider,
|
|
50
|
+
messages=output_messages,
|
|
51
|
+
images=image_paths,
|
|
52
|
+
stream=stream_output,
|
|
53
|
+
npc=current_npc,
|
|
54
|
+
api_url=api_url or None,
|
|
55
|
+
api_key=api_key or None
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
context['output'] = response_data.get('response', 'No response received')
|
|
59
|
+
context['messages'] = response_data.get('messages', output_messages)
|
|
60
|
+
context['model'] = vision_model
|
|
61
|
+
context['provider'] = vision_provider
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
jinx_name: "search"
|
|
2
|
+
description: >
|
|
3
|
+
Executes a search across various sources.
|
|
4
|
+
Usage:
|
|
5
|
+
/search <query> (Default: Web Search)
|
|
6
|
+
/search --memory <query> (Search approved memories)
|
|
7
|
+
/search --kg <query> (Search the knowledge graph)
|
|
8
|
+
/search --rag [-f <paths>] <query> (Execute a RAG search)
|
|
9
|
+
/search --brainblast <query> (Advanced history search)
|
|
10
|
+
inputs:
|
|
11
|
+
- query: ""
|
|
12
|
+
- memory: false
|
|
13
|
+
- kg: false
|
|
14
|
+
- rag: false
|
|
15
|
+
- brainblast: false
|
|
16
|
+
- file_paths: ""
|
|
17
|
+
- history_db_path: "~/npcsh_history.db"
|
|
18
|
+
- vector_db_path: "~/npcsh_chroma.db"
|
|
19
|
+
- sprovider: ""
|
|
20
|
+
- emodel: ""
|
|
21
|
+
- eprovider: ""
|
|
22
|
+
steps:
|
|
23
|
+
- name: "execute_unified_search"
|
|
24
|
+
engine: "python"
|
|
25
|
+
code: |
|
|
26
|
+
import os
|
|
27
|
+
import traceback
|
|
28
|
+
|
|
29
|
+
# Access query from context
|
|
30
|
+
query = context.get('query')
|
|
31
|
+
if not query or not query.strip():
|
|
32
|
+
context['output'] = "Usage: /search [--memory|--kg|--rag|--brainblast] <query>"
|
|
33
|
+
else:
|
|
34
|
+
# state is available as a GLOBAL variable (from extra_globals)
|
|
35
|
+
# Access it directly, not from context
|
|
36
|
+
try:
|
|
37
|
+
current_state = state # This should work now
|
|
38
|
+
except NameError:
|
|
39
|
+
context['output'] = "Error: Shell state not available in jinx context"
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
current_npc = current_state.npc
|
|
43
|
+
current_team = current_state.team
|
|
44
|
+
|
|
45
|
+
npc_name = getattr(current_npc, 'name', '__none__') if current_npc else '__none__'
|
|
46
|
+
team_name = getattr(current_team, 'name', '__none__') if current_team else '__none__'
|
|
47
|
+
current_path = os.getcwd()
|
|
48
|
+
db_path = os.path.expanduser(context.get("history_db_path"))
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
cmd_history = CommandHistory(db_path)
|
|
52
|
+
|
|
53
|
+
if context.get('memory'):
|
|
54
|
+
memories = get_relevant_memories(
|
|
55
|
+
command_history=cmd_history,
|
|
56
|
+
npc_name=npc_name,
|
|
57
|
+
team_name=team_name,
|
|
58
|
+
path=current_path,
|
|
59
|
+
query=query,
|
|
60
|
+
max_memories=10,
|
|
61
|
+
state=current_state # Pass the state object
|
|
62
|
+
)
|
|
63
|
+
print(memories)
|
|
64
|
+
|
|
65
|
+
if not memories:
|
|
66
|
+
output = f"No memories found for query: '{query}'"
|
|
67
|
+
else:
|
|
68
|
+
output = f"Found {len(memories)} memories:\n\n" + "\n".join(
|
|
69
|
+
f"{i}. [{mem.get('timestamp', 'unknown')}] {mem.get('final_memory') or mem.get('initial_memory')}"
|
|
70
|
+
for i, mem in enumerate(memories, 1)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
elif context.get('kg'):
|
|
74
|
+
facts = search_kg_facts(
|
|
75
|
+
cmd_history,
|
|
76
|
+
npc_name,
|
|
77
|
+
team_name,
|
|
78
|
+
current_path,
|
|
79
|
+
query
|
|
80
|
+
)
|
|
81
|
+
print(facts)
|
|
82
|
+
|
|
83
|
+
if not facts:
|
|
84
|
+
output = f"No KG facts found for query: '{query}'"
|
|
85
|
+
else:
|
|
86
|
+
output = f"Found {len(facts)} KG facts:\n\n" + "\n".join(
|
|
87
|
+
f"{i}. {fact.get('statement')}" for i, fact in enumerate(facts, 1)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elif context.get('rag'):
|
|
91
|
+
file_paths_str = context.get('file_paths', '')
|
|
92
|
+
file_paths = [os.path.abspath(os.path.expanduser(p.strip())) for p in file_paths_str.split(',') if p.strip()]
|
|
93
|
+
emodel = context.get('emodel') or current_state.embedding_model
|
|
94
|
+
eprovider = context.get('eprovider') or current_state.embedding_provider
|
|
95
|
+
|
|
96
|
+
file_contents = []
|
|
97
|
+
for path in file_paths:
|
|
98
|
+
chunks = load_file_contents(path)
|
|
99
|
+
basename = os.path.basename(path)
|
|
100
|
+
file_contents.extend([f"{basename}: {chunk}" for chunk in chunks])
|
|
101
|
+
|
|
102
|
+
result = execute_rag_command(
|
|
103
|
+
command=query,
|
|
104
|
+
vector_db_path=os.path.expanduser(context.get('vector_db_path')),
|
|
105
|
+
embedding_model=emodel,
|
|
106
|
+
embedding_provider=eprovider,
|
|
107
|
+
file_contents=file_contents or None
|
|
108
|
+
)
|
|
109
|
+
print(result)
|
|
110
|
+
output = result.get('response', 'No response from RAG.')
|
|
111
|
+
|
|
112
|
+
elif context.get('brainblast'):
|
|
113
|
+
result = execute_brainblast_command(
|
|
114
|
+
command=query,
|
|
115
|
+
command_history=cmd_history,
|
|
116
|
+
**context
|
|
117
|
+
)
|
|
118
|
+
print(result)
|
|
119
|
+
output = result.get('output', 'Brainblast search executed.')
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
# Default to web search
|
|
123
|
+
provider = context.get('sprovider') or current_state.search_provider
|
|
124
|
+
results = search_web(query, provider=provider)
|
|
125
|
+
output = "\n".join([f"- {res}" for res in results]) if results else "No web results found."
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
output = f"An error occurred in the search jinx: {e}\n{traceback.format_exc()}"
|
|
129
|
+
|
|
130
|
+
context['output'] = output
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
jinx_name: "vixynt"
|
|
2
2
|
description: "Generates images from text descriptions or edits existing ones."
|
|
3
3
|
inputs:
|
|
4
|
-
- prompt
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
4
|
+
- prompt
|
|
5
|
+
- model: ""
|
|
6
|
+
- provider: ""
|
|
7
|
+
- output_name: ""
|
|
8
|
+
- attachments: ""
|
|
9
|
+
- n_images: 1
|
|
10
|
+
- height: 1024
|
|
11
|
+
- width: 1024
|
|
12
12
|
steps:
|
|
13
13
|
- name: "generate_or_edit_image"
|
|
14
14
|
engine: "python"
|
|
@@ -20,13 +20,13 @@ steps:
|
|
|
20
20
|
from PIL import Image
|
|
21
21
|
from npcpy.llm_funcs import gen_image
|
|
22
22
|
|
|
23
|
-
# Extract inputs from context
|
|
23
|
+
# Extract inputs from context
|
|
24
24
|
image_prompt = context.get('prompt', '').strip()
|
|
25
|
-
|
|
25
|
+
output_name = context.get('output_name')
|
|
26
26
|
attachments_str = context.get('attachments')
|
|
27
|
-
n_images = int(context.get('n_images', 1))
|
|
28
|
-
height = int(context.get('height', 1024))
|
|
29
|
-
width = int(context.get('width', 1024))
|
|
27
|
+
n_images = int(context.get('n_images', 1))
|
|
28
|
+
height = int(context.get('height', 1024))
|
|
29
|
+
width = int(context.get('width', 1024))
|
|
30
30
|
model = context.get('model')
|
|
31
31
|
provider = context.get('provider')
|
|
32
32
|
|
|
@@ -34,29 +34,28 @@ steps:
|
|
|
34
34
|
if attachments_str and attachments_str.strip():
|
|
35
35
|
input_images = [p.strip() for p in attachments_str.split(',')]
|
|
36
36
|
|
|
37
|
-
# Use NPC's model/provider as fallback
|
|
37
|
+
# Use NPC's model/provider as fallback
|
|
38
38
|
if not model and npc and npc.model:
|
|
39
39
|
model = npc.model
|
|
40
40
|
if not provider and npc and npc.provider:
|
|
41
41
|
provider = npc.provider
|
|
42
42
|
|
|
43
|
-
# Final fallbacks
|
|
43
|
+
# Final fallbacks
|
|
44
44
|
if not model:
|
|
45
45
|
model = "runwayml/stable-diffusion-v1-5"
|
|
46
46
|
if not provider:
|
|
47
47
|
provider = "diffusers"
|
|
48
|
-
|
|
49
48
|
|
|
50
49
|
output_messages = context.get('messages', [])
|
|
51
50
|
|
|
52
51
|
if not image_prompt:
|
|
53
|
-
context['output'] = "
|
|
52
|
+
context['output'] = "Error: No prompt provided for image generation."
|
|
54
53
|
context['messages'] = output_messages
|
|
55
|
-
exit()
|
|
54
|
+
exit()
|
|
56
55
|
|
|
57
56
|
try:
|
|
58
|
-
# Generate image(s)
|
|
59
|
-
|
|
57
|
+
# Generate image(s)
|
|
58
|
+
result = gen_image(
|
|
60
59
|
prompt=image_prompt,
|
|
61
60
|
model=model,
|
|
62
61
|
provider=provider,
|
|
@@ -67,20 +66,22 @@ steps:
|
|
|
67
66
|
input_images=input_images if input_images else None
|
|
68
67
|
)
|
|
69
68
|
|
|
69
|
+
# Ensure we have a list of images
|
|
70
|
+
if not isinstance(result, list):
|
|
71
|
+
images_list = [result] if result is not None else []
|
|
72
|
+
else:
|
|
73
|
+
images_list = result
|
|
74
|
+
|
|
70
75
|
saved_files = []
|
|
71
|
-
compressed_images = []
|
|
72
76
|
|
|
73
|
-
if not isinstance(images_list, list):
|
|
74
|
-
images_list = [images_list] if images_list is not None else []
|
|
75
|
-
|
|
76
77
|
for i, image in enumerate(images_list):
|
|
77
78
|
if image is None:
|
|
78
79
|
continue
|
|
79
80
|
|
|
80
81
|
# Determine output filename
|
|
81
|
-
if
|
|
82
|
-
base_name, ext = os.path.splitext(os.path.expanduser(
|
|
83
|
-
if not ext:
|
|
82
|
+
if output_name and output_name.strip():
|
|
83
|
+
base_name, ext = os.path.splitext(os.path.expanduser(output_name))
|
|
84
|
+
if not ext:
|
|
84
85
|
ext = ".png"
|
|
85
86
|
current_output_file = f"{base_name}_{i}{ext}" if len(images_list) > 1 else f"{base_name}{ext}"
|
|
86
87
|
else:
|
|
@@ -93,16 +94,6 @@ steps:
|
|
|
93
94
|
# Save image to file
|
|
94
95
|
image.save(current_output_file)
|
|
95
96
|
saved_files.append(current_output_file)
|
|
96
|
-
|
|
97
|
-
# Create compressed base64 image for HTML rendering
|
|
98
|
-
img_buffer = BytesIO()
|
|
99
|
-
img_copy = image.copy()
|
|
100
|
-
img_copy.thumbnail((800, 600), Image.Resampling.LANCZOS)
|
|
101
|
-
img_copy.save(img_buffer, format='PNG', optimize=True, quality=85)
|
|
102
|
-
img_buffer.seek(0)
|
|
103
|
-
|
|
104
|
-
img_base64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
|
105
|
-
compressed_images.append(f"data:image/png;base64,{img_base64}")
|
|
106
97
|
|
|
107
98
|
if saved_files:
|
|
108
99
|
if input_images:
|
|
@@ -110,20 +101,17 @@ steps:
|
|
|
110
101
|
else:
|
|
111
102
|
output = f"Image(s) generated and saved to: {', '.join(saved_files)}"
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
html_images += f'<img src="{img_b64}" style="max-width: 400px; margin: 10px;" /><br/>'
|
|
116
|
-
|
|
117
|
-
output += f"\n\nGenerated Images:\n{html_images}"
|
|
104
|
+
# DO NOT include base64 data - just reference the file paths
|
|
105
|
+
output += f"\n\nThe image files have been saved and are ready to view."
|
|
118
106
|
else:
|
|
119
|
-
output = "No images generated."
|
|
107
|
+
output = "No images were generated."
|
|
120
108
|
|
|
121
109
|
except Exception as e:
|
|
122
110
|
import traceback
|
|
123
111
|
traceback.print_exc()
|
|
124
112
|
output = f"Error {'editing' if input_images else 'generating'} image: {str(e)}"
|
|
125
113
|
|
|
126
|
-
context['output'] = output
|
|
127
|
-
context['messages'] = output_messages
|
|
114
|
+
context['output'] = output
|
|
115
|
+
context['messages'] = output_messages
|
|
128
116
|
context['model'] = model
|
|
129
117
|
context['provider'] = provider
|
npcsh/routes.py
CHANGED
|
@@ -2,9 +2,11 @@ from typing import Callable, Dict, Any, List, Optional
|
|
|
2
2
|
import functools
|
|
3
3
|
import os
|
|
4
4
|
import traceback
|
|
5
|
+
import sys
|
|
6
|
+
import inspect
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
|
-
from npcpy.npc_compiler import Jinx, load_jinxs_from_directory
|
|
9
|
+
from npcpy.npc_compiler import Jinx, load_jinxs_from_directory, extract_jinx_inputs
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class CommandRouter:
|
|
@@ -55,27 +57,43 @@ class CommandRouter:
|
|
|
55
57
|
|
|
56
58
|
try:
|
|
57
59
|
import shlex
|
|
60
|
+
|
|
58
61
|
parts = shlex.split(command)
|
|
59
62
|
args = parts[1:] if len(parts) > 1 else []
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
# Use extract_jinx_inputs
|
|
65
|
+
input_values = extract_jinx_inputs(args, jinx)
|
|
66
|
+
|
|
67
|
+
# Build extra_globals for jinx execution
|
|
68
|
+
from npcpy.memory.command_history import CommandHistory, load_kg_from_db
|
|
69
|
+
from npcpy.memory.search import execute_rag_command, execute_brainblast_command
|
|
70
|
+
from npcpy.data.load import load_file_contents
|
|
71
|
+
from npcpy.data.web import search_web
|
|
72
|
+
|
|
73
|
+
application_globals_for_jinx = {
|
|
74
|
+
"CommandHistory": CommandHistory,
|
|
75
|
+
"load_kg_from_db": load_kg_from_db,
|
|
76
|
+
"execute_rag_command": execute_rag_command,
|
|
77
|
+
"execute_brainblast_command": execute_brainblast_command,
|
|
78
|
+
"load_file_contents": load_file_contents,
|
|
79
|
+
"search_web": search_web,
|
|
80
|
+
'state': kwargs.get('state')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Add functions from _state module if available
|
|
84
|
+
try:
|
|
85
|
+
from npcsh import _state
|
|
86
|
+
for name, func in inspect.getmembers(_state, inspect.isfunction):
|
|
87
|
+
application_globals_for_jinx[name] = func
|
|
88
|
+
except:
|
|
89
|
+
pass
|
|
73
90
|
|
|
74
91
|
jinx_output = jinx.execute(
|
|
75
92
|
input_values=input_values,
|
|
76
93
|
jinxs_dict=kwargs.get('jinxs_dict', {}),
|
|
77
94
|
npc=npc,
|
|
78
|
-
messages=messages
|
|
95
|
+
messages=messages,
|
|
96
|
+
extra_globals=application_globals_for_jinx
|
|
79
97
|
)
|
|
80
98
|
|
|
81
99
|
if isinstance(jinx_output, dict):
|